summaryrefslogtreecommitdiff
path: root/deps/v8/src/d8
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/src/d8')
-rw-r--r--deps/v8/src/d8/OWNERS5
-rw-r--r--deps/v8/src/d8/async-hooks-wrapper.cc294
-rw-r--r--deps/v8/src/d8/async-hooks-wrapper.h96
-rw-r--r--deps/v8/src/d8/d8-console.cc142
-rw-r--r--deps/v8/src/d8/d8-console.h47
-rw-r--r--deps/v8/src/d8/d8-js.cc101
-rw-r--r--deps/v8/src/d8/d8-platforms.cc309
-rw-r--r--deps/v8/src/d8/d8-platforms.h29
-rw-r--r--deps/v8/src/d8/d8-posix.cc854
-rw-r--r--deps/v8/src/d8/d8-windows.cc16
-rw-r--r--deps/v8/src/d8/d8.cc3572
-rw-r--r--deps/v8/src/d8/d8.h550
12 files changed, 6015 insertions, 0 deletions
diff --git a/deps/v8/src/d8/OWNERS b/deps/v8/src/d8/OWNERS
new file mode 100644
index 0000000000..ff3b6d7372
--- /dev/null
+++ b/deps/v8/src/d8/OWNERS
@@ -0,0 +1,5 @@
+binji@chromium.org
+bmeurer@chromium.org
+clemensh@chromium.org
+verwaest@chromium.org
+yangguo@chromium.org
diff --git a/deps/v8/src/d8/async-hooks-wrapper.cc b/deps/v8/src/d8/async-hooks-wrapper.cc
new file mode 100644
index 0000000000..a3fc9dba1f
--- /dev/null
+++ b/deps/v8/src/d8/async-hooks-wrapper.cc
@@ -0,0 +1,294 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/d8/async-hooks-wrapper.h"
+#include "src/d8/d8.h"
+#include "src/execution/isolate-inl.h"
+
+namespace v8 {
+
+void AsyncHooksWrap::Enable() { enabled_ = true; }
+
+void AsyncHooksWrap::Disable() { enabled_ = false; }
+
+v8::Local<v8::Function> AsyncHooksWrap::init_function() const {
+ return init_function_.Get(isolate_);
+}
+void AsyncHooksWrap::set_init_function(v8::Local<v8::Function> value) {
+ init_function_.Reset(isolate_, value);
+}
+v8::Local<v8::Function> AsyncHooksWrap::before_function() const {
+ return before_function_.Get(isolate_);
+}
+void AsyncHooksWrap::set_before_function(v8::Local<v8::Function> value) {
+ before_function_.Reset(isolate_, value);
+}
+v8::Local<v8::Function> AsyncHooksWrap::after_function() const {
+ return after_function_.Get(isolate_);
+}
+void AsyncHooksWrap::set_after_function(v8::Local<v8::Function> value) {
+ after_function_.Reset(isolate_, value);
+}
+v8::Local<v8::Function> AsyncHooksWrap::promiseResolve_function() const {
+ return promiseResolve_function_.Get(isolate_);
+}
+void AsyncHooksWrap::set_promiseResolve_function(
+ v8::Local<v8::Function> value) {
+ promiseResolve_function_.Reset(isolate_, value);
+}
+
+static AsyncHooksWrap* UnwrapHook(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope scope(isolate);
+ Local<Object> hook = args.This();
+
+ AsyncHooks* hooks = PerIsolateData::Get(isolate)->GetAsyncHooks();
+
+ if (!hooks->async_hook_ctor.Get(isolate)->HasInstance(hook)) {
+ isolate->ThrowException(
+ String::NewFromUtf8(
+ isolate, "Invalid 'this' passed instead of AsyncHooks instance",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return nullptr;
+ }
+
+ Local<External> wrap = Local<External>::Cast(hook->GetInternalField(0));
+ void* ptr = wrap->Value();
+ return static_cast<AsyncHooksWrap*>(ptr);
+}
+
+static void EnableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ AsyncHooksWrap* wrap = UnwrapHook(args);
+ if (wrap) {
+ wrap->Enable();
+ }
+}
+
+static void DisableHook(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ AsyncHooksWrap* wrap = UnwrapHook(args);
+ if (wrap) {
+ wrap->Disable();
+ }
+}
+
+async_id_t AsyncHooks::GetExecutionAsyncId() const {
+ return asyncContexts.top().execution_async_id;
+}
+
+async_id_t AsyncHooks::GetTriggerAsyncId() const {
+ return asyncContexts.top().trigger_async_id;
+}
+
+Local<Object> AsyncHooks::CreateHook(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ EscapableHandleScope handle_scope(isolate);
+
+ Local<Context> currentContext = isolate->GetCurrentContext();
+
+ if (args.Length() != 1 || !args[0]->IsObject()) {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, "Invalid arguments passed to createHook",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return Local<Object>();
+ }
+
+ AsyncHooksWrap* wrap = new AsyncHooksWrap(isolate);
+
+ Local<Object> fn_obj = args[0].As<Object>();
+
+#define SET_HOOK_FN(name) \
+ Local<Value> name##_v = \
+ fn_obj \
+ ->Get(currentContext, \
+ String::NewFromUtf8(isolate, #name, NewStringType::kNormal) \
+ .ToLocalChecked()) \
+ .ToLocalChecked(); \
+ if (name##_v->IsFunction()) { \
+ wrap->set_##name##_function(name##_v.As<Function>()); \
+ }
+
+ SET_HOOK_FN(init);
+ SET_HOOK_FN(before);
+ SET_HOOK_FN(after);
+ SET_HOOK_FN(promiseResolve);
+#undef SET_HOOK_FN
+
+ async_wraps_.push_back(wrap);
+
+ Local<Object> obj = async_hooks_templ.Get(isolate)
+ ->NewInstance(currentContext)
+ .ToLocalChecked();
+ obj->SetInternalField(0, External::New(isolate, wrap));
+
+ return handle_scope.Escape(obj);
+}
+
+void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
+ Local<Value> parent) {
+ AsyncHooks* hooks =
+ PerIsolateData::Get(promise->GetIsolate())->GetAsyncHooks();
+
+ HandleScope handle_scope(hooks->isolate_);
+
+ Local<Context> currentContext = hooks->isolate_->GetCurrentContext();
+
+ if (type == PromiseHookType::kInit) {
+ ++hooks->current_async_id;
+ Local<Integer> async_id =
+ Integer::New(hooks->isolate_, hooks->current_async_id);
+
+ CHECK(!promise
+ ->HasPrivate(currentContext,
+ hooks->async_id_smb.Get(hooks->isolate_))
+ .ToChecked());
+ promise->SetPrivate(currentContext,
+ hooks->async_id_smb.Get(hooks->isolate_), async_id);
+
+ if (parent->IsPromise()) {
+ Local<Promise> parent_promise = parent.As<Promise>();
+ Local<Value> parent_async_id =
+ parent_promise
+ ->GetPrivate(hooks->isolate_->GetCurrentContext(),
+ hooks->async_id_smb.Get(hooks->isolate_))
+ .ToLocalChecked();
+ promise->SetPrivate(currentContext,
+ hooks->trigger_id_smb.Get(hooks->isolate_),
+ parent_async_id);
+ } else {
+ CHECK(parent->IsUndefined());
+ Local<Integer> trigger_id = Integer::New(hooks->isolate_, 0);
+ promise->SetPrivate(currentContext,
+ hooks->trigger_id_smb.Get(hooks->isolate_),
+ trigger_id);
+ }
+ } else if (type == PromiseHookType::kBefore) {
+ AsyncContext ctx;
+ ctx.execution_async_id =
+ promise
+ ->GetPrivate(hooks->isolate_->GetCurrentContext(),
+ hooks->async_id_smb.Get(hooks->isolate_))
+ .ToLocalChecked()
+ .As<Integer>()
+ ->Value();
+ ctx.trigger_async_id =
+ promise
+ ->GetPrivate(hooks->isolate_->GetCurrentContext(),
+ hooks->trigger_id_smb.Get(hooks->isolate_))
+ .ToLocalChecked()
+ .As<Integer>()
+ ->Value();
+ hooks->asyncContexts.push(ctx);
+ } else if (type == PromiseHookType::kAfter) {
+ hooks->asyncContexts.pop();
+ }
+
+ for (AsyncHooksWrap* wrap : hooks->async_wraps_) {
+ PromiseHookDispatch(type, promise, parent, wrap, hooks);
+ }
+}
+
+void AsyncHooks::Initialize() {
+ HandleScope handle_scope(isolate_);
+
+ async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_));
+ async_hook_ctor.Get(isolate_)->SetClassName(
+ String::NewFromUtf8(isolate_, "AsyncHook", NewStringType::kNormal)
+ .ToLocalChecked());
+
+ async_hooks_templ.Reset(isolate_,
+ async_hook_ctor.Get(isolate_)->InstanceTemplate());
+ async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1);
+ async_hooks_templ.Get(isolate_)->Set(
+ String::NewFromUtf8(isolate_, "enable", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate_, EnableHook));
+ async_hooks_templ.Get(isolate_)->Set(
+ String::NewFromUtf8(isolate_, "disable", v8::NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate_, DisableHook));
+
+ async_id_smb.Reset(isolate_, Private::New(isolate_));
+ trigger_id_smb.Reset(isolate_, Private::New(isolate_));
+
+ isolate_->SetPromiseHook(ShellPromiseHook);
+}
+
+void AsyncHooks::Deinitialize() {
+ isolate_->SetPromiseHook(nullptr);
+ for (AsyncHooksWrap* wrap : async_wraps_) {
+ delete wrap;
+ }
+}
+
+void AsyncHooks::PromiseHookDispatch(PromiseHookType type,
+ Local<Promise> promise,
+ Local<Value> parent, AsyncHooksWrap* wrap,
+ AsyncHooks* hooks) {
+ if (!wrap->IsEnabled()) {
+ return;
+ }
+
+ HandleScope handle_scope(hooks->isolate_);
+
+ TryCatch try_catch(hooks->isolate_);
+ try_catch.SetVerbose(true);
+
+ i::Isolate* isolate = reinterpret_cast<i::Isolate*>(hooks->isolate_);
+ if (isolate->has_scheduled_exception()) {
+ isolate->ScheduleThrow(isolate->scheduled_exception());
+
+ DCHECK(try_catch.HasCaught());
+ Shell::ReportException(hooks->isolate_, &try_catch);
+ return;
+ }
+
+ Local<Value> rcv = Undefined(hooks->isolate_);
+ Local<Context> context = hooks->isolate_->GetCurrentContext();
+ Local<Value> async_id =
+ promise->GetPrivate(context, hooks->async_id_smb.Get(hooks->isolate_))
+ .ToLocalChecked();
+ Local<Value> args[1] = {async_id};
+
+ // This is unused. It's here to silence the warning about
+ // not using the MaybeLocal return value from Call.
+ MaybeLocal<Value> result;
+
+ // Sacrifice the brevity for readability and debugfulness
+ if (type == PromiseHookType::kInit) {
+ if (!wrap->init_function().IsEmpty()) {
+ Local<Value> initArgs[4] = {
+ async_id,
+ String::NewFromUtf8(hooks->isolate_, "PROMISE",
+ NewStringType::kNormal)
+ .ToLocalChecked(),
+ promise
+ ->GetPrivate(context, hooks->trigger_id_smb.Get(hooks->isolate_))
+ .ToLocalChecked(),
+ promise};
+ result = wrap->init_function()->Call(context, rcv, 4, initArgs);
+ }
+ } else if (type == PromiseHookType::kBefore) {
+ if (!wrap->before_function().IsEmpty()) {
+ result = wrap->before_function()->Call(context, rcv, 1, args);
+ }
+ } else if (type == PromiseHookType::kAfter) {
+ if (!wrap->after_function().IsEmpty()) {
+ result = wrap->after_function()->Call(context, rcv, 1, args);
+ }
+ } else if (type == PromiseHookType::kResolve) {
+ if (!wrap->promiseResolve_function().IsEmpty()) {
+ result = wrap->promiseResolve_function()->Call(context, rcv, 1, args);
+ }
+ }
+
+ if (try_catch.HasCaught()) {
+ Shell::ReportException(hooks->isolate_, &try_catch);
+ }
+}
+
+} // namespace v8
diff --git a/deps/v8/src/d8/async-hooks-wrapper.h b/deps/v8/src/d8/async-hooks-wrapper.h
new file mode 100644
index 0000000000..f339b6e316
--- /dev/null
+++ b/deps/v8/src/d8/async-hooks-wrapper.h
@@ -0,0 +1,96 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_D8_ASYNC_HOOKS_WRAPPER_H_
+#define V8_D8_ASYNC_HOOKS_WRAPPER_H_
+
+#include <stack>
+
+#include "include/v8.h"
+#include "src/objects/objects.h"
+
+namespace v8 {
+
+using async_id_t = double;
+
+struct AsyncContext {
+ async_id_t execution_async_id;
+ async_id_t trigger_async_id;
+};
+
+class AsyncHooksWrap {
+ public:
+ explicit AsyncHooksWrap(Isolate* isolate) {
+ enabled_ = false;
+ isolate_ = isolate;
+ }
+ void Enable();
+ void Disable();
+ bool IsEnabled() const { return enabled_; }
+
+ inline v8::Local<v8::Function> init_function() const;
+ inline void set_init_function(v8::Local<v8::Function> value);
+ inline v8::Local<v8::Function> before_function() const;
+ inline void set_before_function(v8::Local<v8::Function> value);
+ inline v8::Local<v8::Function> after_function() const;
+ inline void set_after_function(v8::Local<v8::Function> value);
+ inline v8::Local<v8::Function> promiseResolve_function() const;
+ inline void set_promiseResolve_function(v8::Local<v8::Function> value);
+
+ private:
+ Isolate* isolate_;
+
+ Persistent<v8::Function> init_function_;
+ Persistent<v8::Function> before_function_;
+ Persistent<v8::Function> after_function_;
+ Persistent<v8::Function> promiseResolve_function_;
+
+ bool enabled_;
+};
+
+class AsyncHooks {
+ public:
+ explicit AsyncHooks(Isolate* isolate) {
+ isolate_ = isolate;
+
+ AsyncContext ctx;
+ ctx.execution_async_id = 1;
+ ctx.trigger_async_id = 0;
+ asyncContexts.push(ctx);
+ current_async_id = 1;
+
+ Initialize();
+ }
+ ~AsyncHooks() { Deinitialize(); }
+
+ async_id_t GetExecutionAsyncId() const;
+ async_id_t GetTriggerAsyncId() const;
+
+ Local<Object> CreateHook(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ Persistent<FunctionTemplate> async_hook_ctor;
+
+ private:
+ std::vector<AsyncHooksWrap*> async_wraps_;
+ Isolate* isolate_;
+ Persistent<ObjectTemplate> async_hooks_templ;
+ Persistent<Private> async_id_smb;
+ Persistent<Private> trigger_id_smb;
+
+ void Initialize();
+ void Deinitialize();
+
+ static void ShellPromiseHook(PromiseHookType type, Local<Promise> promise,
+ Local<Value> parent);
+ static void PromiseHookDispatch(PromiseHookType type, Local<Promise> promise,
+ Local<Value> parent, AsyncHooksWrap* wrap,
+ AsyncHooks* hooks);
+
+ std::stack<AsyncContext> asyncContexts;
+ async_id_t current_async_id;
+};
+
+} // namespace v8
+
+#endif // V8_D8_ASYNC_HOOKS_WRAPPER_H_
diff --git a/deps/v8/src/d8/d8-console.cc b/deps/v8/src/d8/d8-console.cc
new file mode 100644
index 0000000000..8175d608b4
--- /dev/null
+++ b/deps/v8/src/d8/d8-console.cc
@@ -0,0 +1,142 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/d8/d8-console.h"
+#include "src/d8/d8.h"
+#include "src/execution/isolate.h"
+
+namespace v8 {
+
+namespace {
+void WriteToFile(const char* prefix, FILE* file, Isolate* isolate,
+ const debug::ConsoleCallArguments& args) {
+ if (prefix) fprintf(file, "%s: ", prefix);
+ for (int i = 0; i < args.Length(); i++) {
+ HandleScope handle_scope(isolate);
+ if (i > 0) fprintf(file, " ");
+
+ Local<Value> arg = args[i];
+ Local<String> str_obj;
+
+ if (arg->IsSymbol()) arg = Local<Symbol>::Cast(arg)->Name();
+ if (!arg->ToString(isolate->GetCurrentContext()).ToLocal(&str_obj)) return;
+
+ v8::String::Utf8Value str(isolate, str_obj);
+ int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file));
+ if (n != str.length()) {
+ printf("Error in fwrite\n");
+ base::OS::ExitProcess(1);
+ }
+ }
+ fprintf(file, "\n");
+}
+} // anonymous namespace
+
+D8Console::D8Console(Isolate* isolate) : isolate_(isolate) {
+ default_timer_ = base::TimeTicks::HighResolutionNow();
+}
+
+void D8Console::Assert(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ // If no arguments given, the "first" argument is undefined which is
+ // false-ish.
+ if (args.Length() > 0 && args[0]->BooleanValue(isolate_)) return;
+ WriteToFile("console.assert", stdout, isolate_, args);
+ isolate_->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(isolate_, "console.assert failed",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked()));
+}
+
+void D8Console::Log(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ WriteToFile(nullptr, stdout, isolate_, args);
+}
+
+void D8Console::Error(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ WriteToFile("console.error", stderr, isolate_, args);
+}
+
+void D8Console::Warn(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ WriteToFile("console.warn", stdout, isolate_, args);
+}
+
+void D8Console::Info(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ WriteToFile("console.info", stdout, isolate_, args);
+}
+
+void D8Console::Debug(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ WriteToFile("console.debug", stdout, isolate_, args);
+}
+
+void D8Console::Time(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ if (args.Length() == 0) {
+ default_timer_ = base::TimeTicks::HighResolutionNow();
+ } else {
+ Local<Value> arg = args[0];
+ Local<String> label;
+ v8::TryCatch try_catch(isolate_);
+ if (!arg->ToString(isolate_->GetCurrentContext()).ToLocal(&label)) return;
+ v8::String::Utf8Value utf8(isolate_, label);
+ std::string string(*utf8);
+ auto find = timers_.find(string);
+ if (find != timers_.end()) {
+ find->second = base::TimeTicks::HighResolutionNow();
+ } else {
+ timers_.insert(std::pair<std::string, base::TimeTicks>(
+ string, base::TimeTicks::HighResolutionNow()));
+ }
+ }
+}
+
+void D8Console::TimeEnd(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ base::TimeDelta delta;
+ if (args.Length() == 0) {
+ delta = base::TimeTicks::HighResolutionNow() - default_timer_;
+ printf("console.timeEnd: default, %f\n", delta.InMillisecondsF());
+ } else {
+ base::TimeTicks now = base::TimeTicks::HighResolutionNow();
+ Local<Value> arg = args[0];
+ Local<String> label;
+ v8::TryCatch try_catch(isolate_);
+ if (!arg->ToString(isolate_->GetCurrentContext()).ToLocal(&label)) return;
+ v8::String::Utf8Value utf8(isolate_, label);
+ std::string string(*utf8);
+ auto find = timers_.find(string);
+ if (find != timers_.end()) {
+ delta = now - find->second;
+ }
+ printf("console.timeEnd: %s, %f\n", *utf8, delta.InMillisecondsF());
+ }
+}
+
+void D8Console::TimeStamp(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ base::TimeDelta delta = base::TimeTicks::HighResolutionNow() - default_timer_;
+ if (args.Length() == 0) {
+ printf("console.timeStamp: default, %f\n", delta.InMillisecondsF());
+ } else {
+ Local<Value> arg = args[0];
+ Local<String> label;
+ v8::TryCatch try_catch(isolate_);
+ if (!arg->ToString(isolate_->GetCurrentContext()).ToLocal(&label)) return;
+ v8::String::Utf8Value utf8(isolate_, label);
+ std::string string(*utf8);
+ printf("console.timeStamp: %s, %f\n", *utf8, delta.InMillisecondsF());
+ }
+}
+
+void D8Console::Trace(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) {
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
+ i_isolate->PrintStack(stderr, i::Isolate::kPrintStackConcise);
+}
+
+} // namespace v8
diff --git a/deps/v8/src/d8/d8-console.h b/deps/v8/src/d8/d8-console.h
new file mode 100644
index 0000000000..be14d99219
--- /dev/null
+++ b/deps/v8/src/d8/d8-console.h
@@ -0,0 +1,47 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_D8_D8_CONSOLE_H_
+#define V8_D8_D8_CONSOLE_H_
+
+#include "src/base/platform/time.h"
+#include "src/debug/debug-interface.h"
+#include "src/debug/interface-types.h"
+
+namespace v8 {
+
+class D8Console : public debug::ConsoleDelegate {
+ public:
+ explicit D8Console(Isolate* isolate);
+
+ private:
+ void Assert(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Log(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Error(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Warn(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Info(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Debug(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Time(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void TimeEnd(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void TimeStamp(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+ void Trace(const debug::ConsoleCallArguments& args,
+ const v8::debug::ConsoleContext&) override;
+
+ Isolate* isolate_;
+ std::map<std::string, base::TimeTicks> timers_;
+ base::TimeTicks default_timer_;
+};
+
+} // namespace v8
+
+#endif // V8_D8_D8_CONSOLE_H_
diff --git a/deps/v8/src/d8/d8-js.cc b/deps/v8/src/d8/d8-js.cc
new file mode 100644
index 0000000000..eb18a00ff6
--- /dev/null
+++ b/deps/v8/src/d8/d8-js.cc
@@ -0,0 +1,101 @@
+// Copyright 2008 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/d8/d8.h"
+
+const char* v8::Shell::stringify_source_ = R"D8(
+(function() {
+"use strict";
+
+// A more universal stringify that supports more types than JSON.
+// Used by the d8 shell to output results.
+var stringifyDepthLimit = 4; // To avoid crashing on cyclic objects
+
+// Hacky solution to circumvent forcing --allow-natives-syntax for d8
+function isProxy(o) { return false };
+function JSProxyGetTarget(proxy) { };
+function JSProxyGetHandler(proxy) { };
+
+try {
+ isProxy = Function(['object'], 'return %IsJSProxy(object)');
+ JSProxyGetTarget = Function(['proxy'],
+ 'return %JSProxyGetTarget(proxy)');
+ JSProxyGetHandler = Function(['proxy'],
+ 'return %JSProxyGetHandler(proxy)');
+} catch(e) {};
+
+
+function Stringify(x, depth) {
+ if (depth === undefined)
+ depth = stringifyDepthLimit;
+ else if (depth === 0)
+ return "...";
+ if (isProxy(x)) {
+ return StringifyProxy(x, depth);
+ }
+ switch (typeof x) {
+ case "undefined":
+ return "undefined";
+ case "boolean":
+ case "number":
+ case "function":
+ case "symbol":
+ return x.toString();
+ case "string":
+ return "\"" + x.toString() + "\"";
+ case "bigint":
+ return x.toString() + "n";
+ case "object":
+ if (x === null) return "null";
+ if (x.constructor && x.constructor.name === "Array") {
+ var elems = [];
+ for (var i = 0; i < x.length; ++i) {
+ elems.push(
+ {}.hasOwnProperty.call(x, i) ? Stringify(x[i], depth - 1) : "");
+ }
+ return "[" + elems.join(", ") + "]";
+ }
+ try {
+ var string = String(x);
+ if (string && string !== "[object Object]") return string;
+ } catch(e) {}
+ var props = [];
+ var names = Object.getOwnPropertyNames(x);
+ names = names.concat(Object.getOwnPropertySymbols(x));
+ for (var i in names) {
+ var name = names[i];
+ var desc = Object.getOwnPropertyDescriptor(x, name);
+ if (desc === (void 0)) continue;
+ if (typeof name === 'symbol') name = "[" + Stringify(name) + "]";
+ if ("value" in desc) {
+ props.push(name + ": " + Stringify(desc.value, depth - 1));
+ }
+ if (desc.get) {
+ var getter = Stringify(desc.get);
+ props.push("get " + name + getter.slice(getter.indexOf('(')));
+ }
+ if (desc.set) {
+ var setter = Stringify(desc.set);
+ props.push("set " + name + setter.slice(setter.indexOf('(')));
+ }
+ }
+ return "{" + props.join(", ") + "}";
+ default:
+ return "[crazy non-standard value]";
+ }
+}
+
+function StringifyProxy(proxy, depth) {
+ var proxy_type = typeof proxy;
+ var info_object = {
+ target: JSProxyGetTarget(proxy),
+ handler: JSProxyGetHandler(proxy)
+ }
+ return '[' + proxy_type + ' Proxy ' + Stringify(info_object, depth-1) + ']';
+}
+
+return Stringify;
+})();
+
+)D8";
diff --git a/deps/v8/src/d8/d8-platforms.cc b/deps/v8/src/d8/d8-platforms.cc
new file mode 100644
index 0000000000..42ce14f4f7
--- /dev/null
+++ b/deps/v8/src/d8/d8-platforms.cc
@@ -0,0 +1,309 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <unordered_map>
+
+#include "include/v8-platform.h"
+#include "src/base/logging.h"
+#include "src/base/macros.h"
+#include "src/base/platform/mutex.h"
+#include "src/base/platform/platform.h"
+#include "src/base/platform/time.h"
+#include "src/base/template-utils.h"
+#include "src/base/utils/random-number-generator.h"
+#include "src/d8/d8-platforms.h"
+
+namespace v8 {
+
+class PredictablePlatform : public Platform {
+ public:
+ explicit PredictablePlatform(std::unique_ptr<Platform> platform)
+ : platform_(std::move(platform)) {
+ DCHECK_NOT_NULL(platform_);
+ }
+
+ PageAllocator* GetPageAllocator() override {
+ return platform_->GetPageAllocator();
+ }
+
+ void OnCriticalMemoryPressure() override {
+ platform_->OnCriticalMemoryPressure();
+ }
+
+ bool OnCriticalMemoryPressure(size_t length) override {
+ return platform_->OnCriticalMemoryPressure(length);
+ }
+
+ std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
+ v8::Isolate* isolate) override {
+ return platform_->GetForegroundTaskRunner(isolate);
+ }
+
+ int NumberOfWorkerThreads() override { return 0; }
+
+ void CallOnWorkerThread(std::unique_ptr<Task> task) override {
+ // It's not defined when background tasks are being executed, so we can just
+ // execute them right away.
+ task->Run();
+ }
+
+ void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
+ double delay_in_seconds) override {
+ // Never run delayed tasks.
+ }
+
+ void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
+ // This is a deprecated function and should not be called anymore.
+ UNREACHABLE();
+ }
+
+ void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
+ double delay_in_seconds) override {
+ // This is a deprecated function and should not be called anymore.
+ UNREACHABLE();
+ }
+
+ void CallIdleOnForegroundThread(Isolate* isolate, IdleTask* task) override {
+ UNREACHABLE();
+ }
+
+ bool IdleTasksEnabled(Isolate* isolate) override { return false; }
+
+ double MonotonicallyIncreasingTime() override {
+ return synthetic_time_in_sec_ += 0.00001;
+ }
+
+ double CurrentClockTimeMillis() override {
+ return MonotonicallyIncreasingTime() * base::Time::kMillisecondsPerSecond;
+ }
+
+ v8::TracingController* GetTracingController() override {
+ return platform_->GetTracingController();
+ }
+
+ Platform* platform() const { return platform_.get(); }
+
+ private:
+ double synthetic_time_in_sec_ = 0.0;
+ std::unique_ptr<Platform> platform_;
+
+ DISALLOW_COPY_AND_ASSIGN(PredictablePlatform);
+};
+
+std::unique_ptr<Platform> MakePredictablePlatform(
+ std::unique_ptr<Platform> platform) {
+ return base::make_unique<PredictablePlatform>(std::move(platform));
+}
+
+class DelayedTasksPlatform : public Platform {
+ public:
+ explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform)
+ : platform_(std::move(platform)) {
+ DCHECK_NOT_NULL(platform_);
+ }
+
+ explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform,
+ int64_t random_seed)
+ : platform_(std::move(platform)), rng_(random_seed) {
+ DCHECK_NOT_NULL(platform_);
+ }
+
+ ~DelayedTasksPlatform() {
+ // When the platform shuts down, all task runners must be freed.
+ DCHECK_EQ(0, delayed_task_runners_.size());
+ }
+
+ PageAllocator* GetPageAllocator() override {
+ return platform_->GetPageAllocator();
+ }
+
+ void OnCriticalMemoryPressure() override {
+ platform_->OnCriticalMemoryPressure();
+ }
+
+ bool OnCriticalMemoryPressure(size_t length) override {
+ return platform_->OnCriticalMemoryPressure(length);
+ }
+
+ std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
+ v8::Isolate* isolate) override {
+ std::shared_ptr<TaskRunner> runner =
+ platform_->GetForegroundTaskRunner(isolate);
+
+ base::MutexGuard lock_guard(&mutex_);
+ // Check if we can re-materialize the weak ptr in our map.
+ std::weak_ptr<DelayedTaskRunner>& weak_delayed_runner =
+ delayed_task_runners_[runner.get()];
+ std::shared_ptr<DelayedTaskRunner> delayed_runner =
+ weak_delayed_runner.lock();
+
+ if (!delayed_runner) {
+ // Create a new {DelayedTaskRunner} and keep a weak reference in our map.
+ delayed_runner.reset(new DelayedTaskRunner(runner, this),
+ DelayedTaskRunnerDeleter{});
+ weak_delayed_runner = delayed_runner;
+ }
+
+ return std::move(delayed_runner);
+ }
+
+ int NumberOfWorkerThreads() override {
+ return platform_->NumberOfWorkerThreads();
+ }
+
+ void CallOnWorkerThread(std::unique_ptr<Task> task) override {
+ platform_->CallOnWorkerThread(MakeDelayedTask(std::move(task)));
+ }
+
+ void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
+ double delay_in_seconds) override {
+ platform_->CallDelayedOnWorkerThread(MakeDelayedTask(std::move(task)),
+ delay_in_seconds);
+ }
+
+ void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
+ // This is a deprecated function and should not be called anymore.
+ UNREACHABLE();
+ }
+
+ void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task,
+ double delay_in_seconds) override {
+ // This is a deprecated function and should not be called anymore.
+ UNREACHABLE();
+ }
+
+ void CallIdleOnForegroundThread(Isolate* isolate, IdleTask* task) override {
+ // This is a deprecated function and should not be called anymore.
+ UNREACHABLE();
+ }
+
+ bool IdleTasksEnabled(Isolate* isolate) override {
+ return platform_->IdleTasksEnabled(isolate);
+ }
+
+ double MonotonicallyIncreasingTime() override {
+ return platform_->MonotonicallyIncreasingTime();
+ }
+
+ double CurrentClockTimeMillis() override {
+ return platform_->CurrentClockTimeMillis();
+ }
+
+ v8::TracingController* GetTracingController() override {
+ return platform_->GetTracingController();
+ }
+
+ private:
+ class DelayedTaskRunnerDeleter;
+ class DelayedTaskRunner final : public TaskRunner {
+ public:
+ DelayedTaskRunner(std::shared_ptr<TaskRunner> task_runner,
+ DelayedTasksPlatform* platform)
+ : task_runner_(task_runner), platform_(platform) {}
+
+ void PostTask(std::unique_ptr<Task> task) final {
+ task_runner_->PostTask(platform_->MakeDelayedTask(std::move(task)));
+ }
+
+ void PostDelayedTask(std::unique_ptr<Task> task,
+ double delay_in_seconds) final {
+ task_runner_->PostDelayedTask(platform_->MakeDelayedTask(std::move(task)),
+ delay_in_seconds);
+ }
+
+ void PostIdleTask(std::unique_ptr<IdleTask> task) final {
+ task_runner_->PostIdleTask(
+ platform_->MakeDelayedIdleTask(std::move(task)));
+ }
+
+ bool IdleTasksEnabled() final { return task_runner_->IdleTasksEnabled(); }
+
+ private:
+ friend class DelayedTaskRunnerDeleter;
+ std::shared_ptr<TaskRunner> task_runner_;
+ DelayedTasksPlatform* platform_;
+ };
+
+ class DelayedTaskRunnerDeleter {
+ public:
+ void operator()(DelayedTaskRunner* runner) const {
+ TaskRunner* original_runner = runner->task_runner_.get();
+ base::MutexGuard lock_guard(&runner->platform_->mutex_);
+ auto& delayed_task_runners = runner->platform_->delayed_task_runners_;
+ DCHECK_EQ(1, delayed_task_runners.count(original_runner));
+ delayed_task_runners.erase(original_runner);
+ }
+ };
+
+ class DelayedTask : public Task {
+ public:
+ DelayedTask(std::unique_ptr<Task> task, int32_t delay_ms)
+ : task_(std::move(task)), delay_ms_(delay_ms) {}
+ void Run() final {
+ base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
+ task_->Run();
+ }
+
+ private:
+ std::unique_ptr<Task> task_;
+ int32_t delay_ms_;
+ };
+
+ class DelayedIdleTask : public IdleTask {
+ public:
+ DelayedIdleTask(std::unique_ptr<IdleTask> task, int32_t delay_ms)
+ : task_(std::move(task)), delay_ms_(delay_ms) {}
+ void Run(double deadline_in_seconds) final {
+ base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
+ task_->Run(deadline_in_seconds);
+ }
+
+ private:
+ std::unique_ptr<IdleTask> task_;
+ int32_t delay_ms_;
+ };
+
+ std::unique_ptr<Platform> platform_;
+
+ // The Mutex protects the RNG, which is used by foreground and background
+ // threads, and the {delayed_task_runners_} map might be accessed concurrently
+ // by the shared_ptr destructor.
+ base::Mutex mutex_;
+ base::RandomNumberGenerator rng_;
+ std::unordered_map<TaskRunner*, std::weak_ptr<DelayedTaskRunner>>
+ delayed_task_runners_;
+
+ int32_t GetRandomDelayInMilliseconds() {
+ base::MutexGuard lock_guard(&mutex_);
+ double delay_fraction = rng_.NextDouble();
+ // Sleep up to 100ms (100000us). Square {delay_fraction} to shift
+ // distribution towards shorter sleeps.
+ return 1e5 * (delay_fraction * delay_fraction);
+ }
+
+ std::unique_ptr<Task> MakeDelayedTask(std::unique_ptr<Task> task) {
+ return base::make_unique<DelayedTask>(std::move(task),
+ GetRandomDelayInMilliseconds());
+ }
+
+ std::unique_ptr<IdleTask> MakeDelayedIdleTask(
+ std::unique_ptr<IdleTask> task) {
+ return base::make_unique<DelayedIdleTask>(std::move(task),
+ GetRandomDelayInMilliseconds());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedTasksPlatform);
+};
+
+std::unique_ptr<Platform> MakeDelayedTasksPlatform(
+ std::unique_ptr<Platform> platform, int64_t random_seed) {
+ if (random_seed) {
+ return base::make_unique<DelayedTasksPlatform>(std::move(platform),
+ random_seed);
+ }
+ return base::make_unique<DelayedTasksPlatform>(std::move(platform));
+}
+
+} // namespace v8
diff --git a/deps/v8/src/d8/d8-platforms.h b/deps/v8/src/d8/d8-platforms.h
new file mode 100644
index 0000000000..a658f0a47c
--- /dev/null
+++ b/deps/v8/src/d8/d8-platforms.h
@@ -0,0 +1,29 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_D8_D8_PLATFORMS_H_
+#define V8_D8_D8_PLATFORMS_H_
+
+#include <cstdint>
+#include <memory>
+
+namespace v8 {
+
+class Platform;
+
+// Returns a predictable v8::Platform implementation.
+// orker threads are disabled, idle tasks are disallowed, and the time reported
+// by {MonotonicallyIncreasingTime} is deterministic.
+std::unique_ptr<Platform> MakePredictablePlatform(
+ std::unique_ptr<Platform> platform);
+
+// Returns a v8::Platform implementation which randomly delays tasks (both
+// foreground and background) for stress-testing different interleavings.
+// If {random_seed} is 0, a random seed is chosen.
+std::unique_ptr<Platform> MakeDelayedTasksPlatform(
+ std::unique_ptr<Platform> platform, int64_t random_seed);
+
+} // namespace v8
+
+#endif // V8_D8_D8_PLATFORMS_H_
diff --git a/deps/v8/src/d8/d8-posix.cc b/deps/v8/src/d8/d8-posix.cc
new file mode 100644
index 0000000000..23767ba2b5
--- /dev/null
+++ b/deps/v8/src/d8/d8-posix.cc
@@ -0,0 +1,854 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/ip.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "src/d8/d8.h"
+
+namespace v8 {
+
+// If the buffer ends in the middle of a UTF-8 sequence then we return
+// the length of the string up to but not including the incomplete UTF-8
+// sequence. If the buffer ends with a valid UTF-8 sequence then we
+// return the whole buffer.
+static int LengthWithoutIncompleteUtf8(char* buffer, int len) {
+ int answer = len;
+ // 1-byte encoding.
+ static const int kUtf8SingleByteMask = 0x80;
+ static const int kUtf8SingleByteValue = 0x00;
+ // 2-byte encoding.
+ static const int kUtf8TwoByteMask = 0xE0;
+ static const int kUtf8TwoByteValue = 0xC0;
+ // 3-byte encoding.
+ static const int kUtf8ThreeByteMask = 0xF0;
+ static const int kUtf8ThreeByteValue = 0xE0;
+ // 4-byte encoding.
+ static const int kUtf8FourByteMask = 0xF8;
+ static const int kUtf8FourByteValue = 0xF0;
+ // Subsequent bytes of a multi-byte encoding.
+ static const int kMultiByteMask = 0xC0;
+ static const int kMultiByteValue = 0x80;
+ int multi_byte_bytes_seen = 0;
+ while (answer > 0) {
+ int c = buffer[answer - 1];
+ // Ends in valid single-byte sequence?
+ if ((c & kUtf8SingleByteMask) == kUtf8SingleByteValue) return answer;
+ // Ends in one or more subsequent bytes of a multi-byte value?
+ if ((c & kMultiByteMask) == kMultiByteValue) {
+ multi_byte_bytes_seen++;
+ answer--;
+ } else {
+ if ((c & kUtf8TwoByteMask) == kUtf8TwoByteValue) {
+ if (multi_byte_bytes_seen >= 1) {
+ return answer + 2;
+ }
+ return answer - 1;
+ } else if ((c & kUtf8ThreeByteMask) == kUtf8ThreeByteValue) {
+ if (multi_byte_bytes_seen >= 2) {
+ return answer + 3;
+ }
+ return answer - 1;
+ } else if ((c & kUtf8FourByteMask) == kUtf8FourByteValue) {
+ if (multi_byte_bytes_seen >= 3) {
+ return answer + 4;
+ }
+ return answer - 1;
+ } else {
+ return answer; // Malformed UTF-8.
+ }
+ }
+ }
+ return 0;
+}
+
+// Suspends the thread until there is data available from the child process.
+// Returns false on timeout, true on data ready.
+static bool WaitOnFD(int fd, int read_timeout, int total_timeout,
+ const struct timeval& start_time) {
+ fd_set readfds, writefds, exceptfds;
+ struct timeval timeout;
+ int gone = 0;
+ if (total_timeout != -1) {
+ struct timeval time_now;
+ gettimeofday(&time_now, nullptr);
+ time_t seconds = time_now.tv_sec - start_time.tv_sec;
+ gone = static_cast<int>(seconds * 1000 +
+ (time_now.tv_usec - start_time.tv_usec) / 1000);
+ if (gone >= total_timeout) return false;
+ }
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &exceptfds);
+ if (read_timeout == -1 ||
+ (total_timeout != -1 && total_timeout - gone < read_timeout)) {
+ read_timeout = total_timeout - gone;
+ }
+ timeout.tv_usec = (read_timeout % 1000) * 1000;
+ timeout.tv_sec = read_timeout / 1000;
+ int number_of_fds_ready = select(fd + 1, &readfds, &writefds, &exceptfds,
+ read_timeout != -1 ? &timeout : nullptr);
+ return number_of_fds_ready == 1;
+}
+
+// Checks whether we ran out of time on the timeout. Returns true if we ran out
+// of time, false if we still have time.
+static bool TimeIsOut(const struct timeval& start_time, const int& total_time) {
+ if (total_time == -1) return false;
+ struct timeval time_now;
+ gettimeofday(&time_now, nullptr);
+ // Careful about overflow.
+ int seconds = static_cast<int>(time_now.tv_sec - start_time.tv_sec);
+ if (seconds > 100) {
+ if (seconds * 1000 > total_time) return true;
+ return false;
+ }
+ int useconds = static_cast<int>(time_now.tv_usec - start_time.tv_usec);
+ if (seconds * 1000000 + useconds > total_time * 1000) {
+ return true;
+ }
+ return false;
+}
+
+// A utility class that does a non-hanging waitpid on the child process if we
+// bail out of the System() function early. If you don't ever do a waitpid on
+// a subprocess then it turns into one of those annoying 'zombie processes'.
+class ZombieProtector {
+ public:
+ explicit ZombieProtector(int pid) : pid_(pid) {}
+ ~ZombieProtector() {
+ if (pid_ != 0) waitpid(pid_, nullptr, 0);
+ }
+ void ChildIsDeadNow() { pid_ = 0; }
+
+ private:
+ int pid_;
+};
+
+// A utility class that closes a file descriptor when it goes out of scope.
+class OpenFDCloser {
+ public:
+ explicit OpenFDCloser(int fd) : fd_(fd) {}
+ ~OpenFDCloser() { close(fd_); }
+
+ private:
+ int fd_;
+};
+
+// A utility class that takes the array of command arguments and puts then in an
+// array of new[]ed UTF-8 C strings. Deallocates them again when it goes out of
+// scope.
+class ExecArgs {
+ public:
+ ExecArgs() { exec_args_[0] = nullptr; }
+ bool Init(Isolate* isolate, Local<Value> arg0, Local<Array> command_args) {
+ String::Utf8Value prog(isolate, arg0);
+ if (*prog == nullptr) {
+ const char* message =
+ "os.system(): String conversion of program name failed";
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ int len = prog.length() + 3;
+ char* c_arg = new char[len];
+ snprintf(c_arg, len, "%s", *prog);
+ exec_args_[0] = c_arg;
+ int i = 1;
+ for (unsigned j = 0; j < command_args->Length(); i++, j++) {
+ Local<Value> arg(
+ command_args
+ ->Get(isolate->GetCurrentContext(), Integer::New(isolate, j))
+ .ToLocalChecked());
+ String::Utf8Value utf8_arg(isolate, arg);
+ if (*utf8_arg == nullptr) {
+ exec_args_[i] = nullptr; // Consistent state for destructor.
+ const char* message =
+ "os.system(): String conversion of argument failed.";
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ int len = utf8_arg.length() + 1;
+ char* c_arg = new char[len];
+ snprintf(c_arg, len, "%s", *utf8_arg);
+ exec_args_[i] = c_arg;
+ }
+ exec_args_[i] = nullptr;
+ return true;
+ }
+ ~ExecArgs() {
+ for (unsigned i = 0; i < kMaxArgs; i++) {
+ if (exec_args_[i] == nullptr) {
+ return;
+ }
+ delete[] exec_args_[i];
+ exec_args_[i] = nullptr;
+ }
+ }
+ static const unsigned kMaxArgs = 1000;
+ char* const* arg_array() const { return exec_args_; }
+ const char* arg0() const { return exec_args_[0]; }
+
+ private:
+ char* exec_args_[kMaxArgs + 1];
+};
+
+// Gets the optional timeouts from the arguments to the system() call.
+static bool GetTimeouts(const v8::FunctionCallbackInfo<v8::Value>& args,
+ int* read_timeout, int* total_timeout) {
+ if (args.Length() > 3) {
+ if (args[3]->IsNumber()) {
+ *total_timeout = args[3]
+ ->Int32Value(args.GetIsolate()->GetCurrentContext())
+ .FromJust();
+ } else {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(),
+ "system: Argument 4 must be a number",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ }
+ if (args.Length() > 2) {
+ if (args[2]->IsNumber()) {
+ *read_timeout = args[2]
+ ->Int32Value(args.GetIsolate()->GetCurrentContext())
+ .FromJust();
+ } else {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(),
+ "system: Argument 3 must be a number",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ }
+ return true;
+}
+
+static const int kReadFD = 0;
+static const int kWriteFD = 1;
+
+// This is run in the child process after fork() but before exec(). It normally
+// ends with the child process being replaced with the desired child program.
+// It only returns if an error occurred.
+static void ExecSubprocess(int* exec_error_fds, int* stdout_fds,
+ const ExecArgs& exec_args) {
+ close(exec_error_fds[kReadFD]); // Don't need this in the child.
+ close(stdout_fds[kReadFD]); // Don't need this in the child.
+ close(1); // Close stdout.
+ dup2(stdout_fds[kWriteFD], 1); // Dup pipe fd to stdout.
+ close(stdout_fds[kWriteFD]); // Don't need the original fd now.
+ fcntl(exec_error_fds[kWriteFD], F_SETFD, FD_CLOEXEC);
+ execvp(exec_args.arg0(), exec_args.arg_array());
+ // Only get here if the exec failed. Write errno to the parent to tell
+ // them it went wrong. If it went well the pipe is closed.
+ int err = errno;
+ ssize_t bytes_written;
+ do {
+ bytes_written = write(exec_error_fds[kWriteFD], &err, sizeof(err));
+ } while (bytes_written == -1 && errno == EINTR);
+ // Return (and exit child process).
+}
+
+// Runs in the parent process. Checks that the child was able to exec (closing
+// the file desriptor), or reports an error if it failed.
+static bool ChildLaunchedOK(Isolate* isolate, int* exec_error_fds) {
+ ssize_t bytes_read;
+ int err;
+ do {
+ bytes_read = read(exec_error_fds[kReadFD], &err, sizeof(err));
+ } while (bytes_read == -1 && errno == EINTR);
+ if (bytes_read != 0) {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(err), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ return true;
+}
+
+// Accumulates the output from the child in a string handle. Returns true if it
+// succeeded or false if an exception was thrown.
+static Local<Value> GetStdout(Isolate* isolate, int child_fd,
+ const struct timeval& start_time,
+ int read_timeout, int total_timeout) {
+ Local<String> accumulator = String::Empty(isolate);
+
+ int fullness = 0;
+ static const int kStdoutReadBufferSize = 4096;
+ char buffer[kStdoutReadBufferSize];
+
+ if (fcntl(child_fd, F_SETFL, O_NONBLOCK) != 0) {
+ return isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal)
+ .ToLocalChecked());
+ }
+
+ int bytes_read;
+ do {
+ bytes_read = static_cast<int>(
+ read(child_fd, buffer + fullness, kStdoutReadBufferSize - fullness));
+ if (bytes_read == -1) {
+ if (errno == EAGAIN) {
+ if (!WaitOnFD(child_fd, read_timeout, total_timeout, start_time) ||
+ (TimeIsOut(start_time, total_timeout))) {
+ return isolate->ThrowException(
+ String::NewFromUtf8(isolate, "Timed out waiting for output",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ }
+ continue;
+ } else if (errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ if (bytes_read + fullness > 0) {
+ int length = bytes_read == 0 ? bytes_read + fullness
+ : LengthWithoutIncompleteUtf8(
+ buffer, bytes_read + fullness);
+ Local<String> addition =
+ String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, length)
+ .ToLocalChecked();
+ accumulator = String::Concat(isolate, accumulator, addition);
+ fullness = bytes_read + fullness - length;
+ memcpy(buffer, buffer + length, fullness);
+ }
+ } while (bytes_read != 0);
+ return accumulator;
+}
+
+// Modern Linux has the waitid call, which is like waitpid, but more useful
+// if you want a timeout. If we don't have waitid we can't limit the time
+// waiting for the process to exit without losing the information about
+// whether it exited normally. In the common case this doesn't matter because
+// we don't get here before the child has closed stdout and most programs don't
+// do that before they exit.
+//
+// We're disabling usage of waitid in Mac OS X because it doesn't work for us:
+// a parent process hangs on waiting while a child process is already a zombie.
+// See http://code.google.com/p/v8/issues/detail?id=401.
+#if defined(WNOWAIT) && !defined(ANDROID) && !defined(__APPLE__) && \
+ !defined(__NetBSD__) && !defined(__Fuchsia__)
+#if !defined(__FreeBSD__)
+#define HAS_WAITID 1
+#endif
+#endif
+
+// Get exit status of child.
+static bool WaitForChild(Isolate* isolate, int pid,
+ ZombieProtector& child_waiter, // NOLINT
+ const struct timeval& start_time, int read_timeout,
+ int total_timeout) {
+#ifdef HAS_WAITID
+
+ siginfo_t child_info;
+ child_info.si_pid = 0;
+ int useconds = 1;
+ // Wait for child to exit.
+ while (child_info.si_pid == 0) {
+ waitid(P_PID, pid, &child_info, WEXITED | WNOHANG | WNOWAIT);
+ usleep(useconds);
+ if (useconds < 1000000) useconds <<= 1;
+ if ((read_timeout != -1 && useconds / 1000 > read_timeout) ||
+ (TimeIsOut(start_time, total_timeout))) {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate,
+ "Timed out waiting for process to terminate",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ kill(pid, SIGINT);
+ return false;
+ }
+ }
+ if (child_info.si_code == CLD_KILLED) {
+ char message[999];
+ snprintf(message, sizeof(message), "Child killed by signal %d",
+ child_info.si_status);
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ if (child_info.si_code == CLD_EXITED && child_info.si_status != 0) {
+ char message[999];
+ snprintf(message, sizeof(message), "Child exited with status %d",
+ child_info.si_status);
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+
+#else // No waitid call.
+
+ int child_status;
+ waitpid(pid, &child_status, 0); // We hang here if the child doesn't exit.
+ child_waiter.ChildIsDeadNow();
+ if (WIFSIGNALED(child_status)) {
+ char message[999];
+ snprintf(message, sizeof(message), "Child killed by signal %d",
+ WTERMSIG(child_status));
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ if (WEXITSTATUS(child_status) != 0) {
+ char message[999];
+ int exit_status = WEXITSTATUS(child_status);
+ snprintf(message, sizeof(message), "Child exited with status %d",
+ exit_status);
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+
+#endif // No waitid call.
+
+ return true;
+}
+
+#undef HAS_WAITID
+
+// Implementation of the system() function (see d8.h for details).
+void Shell::System(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ HandleScope scope(args.GetIsolate());
+ int read_timeout = -1;
+ int total_timeout = -1;
+ if (!GetTimeouts(args, &read_timeout, &total_timeout)) return;
+ Local<Array> command_args;
+ if (args.Length() > 1) {
+ if (!args[1]->IsArray()) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(),
+ "system: Argument 2 must be an array",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ command_args = Local<Array>::Cast(args[1]);
+ } else {
+ command_args = Array::New(args.GetIsolate(), 0);
+ }
+ if (command_args->Length() > ExecArgs::kMaxArgs) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), "Too many arguments to system()",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ if (args.Length() < 1) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), "Too few arguments to system()",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+
+ struct timeval start_time;
+ gettimeofday(&start_time, nullptr);
+
+ ExecArgs exec_args;
+ if (!exec_args.Init(args.GetIsolate(), args[0], command_args)) {
+ return;
+ }
+ int exec_error_fds[2];
+ int stdout_fds[2];
+
+ if (pipe(exec_error_fds) != 0) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), "pipe syscall failed.",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ if (pipe(stdout_fds) != 0) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), "pipe syscall failed.",
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+
+ pid_t pid = fork();
+ if (pid == 0) { // Child process.
+ ExecSubprocess(exec_error_fds, stdout_fds, exec_args);
+ exit(1);
+ }
+
+ // Parent process. Ensure that we clean up if we exit this function early.
+ ZombieProtector child_waiter(pid);
+ close(exec_error_fds[kWriteFD]);
+ close(stdout_fds[kWriteFD]);
+ OpenFDCloser error_read_closer(exec_error_fds[kReadFD]);
+ OpenFDCloser stdout_read_closer(stdout_fds[kReadFD]);
+
+ Isolate* isolate = args.GetIsolate();
+ if (!ChildLaunchedOK(isolate, exec_error_fds)) return;
+
+ Local<Value> accumulator = GetStdout(isolate, stdout_fds[kReadFD], start_time,
+ read_timeout, total_timeout);
+ if (accumulator->IsUndefined()) {
+ kill(pid, SIGINT); // On timeout, kill the subprocess.
+ args.GetReturnValue().Set(accumulator);
+ return;
+ }
+
+ if (!WaitForChild(isolate, pid, child_waiter, start_time, read_timeout,
+ total_timeout)) {
+ return;
+ }
+
+ args.GetReturnValue().Set(accumulator);
+}
+
+void Shell::ChangeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1) {
+ const char* message = "chdir() takes one argument";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ String::Utf8Value directory(args.GetIsolate(), args[0]);
+ if (*directory == nullptr) {
+ const char* message = "os.chdir(): String conversion of argument failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ if (chdir(*directory) != 0) {
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), strerror(errno),
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+}
+
+void Shell::SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1) {
+ const char* message = "umask() takes one argument";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ if (args[0]->IsNumber()) {
+ int previous = umask(
+ args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust());
+ args.GetReturnValue().Set(previous);
+ return;
+ } else {
+ const char* message = "umask() argument must be numeric";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+}
+
+static bool CheckItsADirectory(Isolate* isolate, char* directory) {
+ struct stat stat_buf;
+ int stat_result = stat(directory, &stat_buf);
+ if (stat_result != 0) {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ if ((stat_buf.st_mode & S_IFDIR) != 0) return true;
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(EEXIST), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+}
+
+// Returns true for success. Creates intermediate directories as needed. No
+// error if the directory exists already.
+static bool mkdirp(Isolate* isolate, char* directory, mode_t mask) {
+ int result = mkdir(directory, mask);
+ if (result == 0) return true;
+ if (errno == EEXIST) {
+ return CheckItsADirectory(isolate, directory);
+ } else if (errno == ENOENT) { // Intermediate path element is missing.
+ char* last_slash = strrchr(directory, '/');
+ if (last_slash == nullptr) {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+ *last_slash = 0;
+ if (!mkdirp(isolate, directory, mask)) return false;
+ *last_slash = '/';
+ result = mkdir(directory, mask);
+ if (result == 0) return true;
+ if (errno == EEXIST) {
+ return CheckItsADirectory(isolate, directory);
+ }
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ } else {
+ isolate->ThrowException(
+ String::NewFromUtf8(isolate, strerror(errno), NewStringType::kNormal)
+ .ToLocalChecked());
+ return false;
+ }
+}
+
+void Shell::MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ mode_t mask = 0777;
+ if (args.Length() == 2) {
+ if (args[1]->IsNumber()) {
+ mask = args[1]
+ ->Int32Value(args.GetIsolate()->GetCurrentContext())
+ .FromJust();
+ } else {
+ const char* message = "mkdirp() second argument must be numeric";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message,
+ NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ } else if (args.Length() != 1) {
+ const char* message = "mkdirp() takes one or two arguments";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ String::Utf8Value directory(args.GetIsolate(), args[0]);
+ if (*directory == nullptr) {
+ const char* message = "os.mkdirp(): String conversion of argument failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ mkdirp(args.GetIsolate(), *directory, mask);
+}
+
+void Shell::RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1) {
+ const char* message = "rmdir() takes one or two arguments";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ String::Utf8Value directory(args.GetIsolate(), args[0]);
+ if (*directory == nullptr) {
+ const char* message = "os.rmdir(): String conversion of argument failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ rmdir(*directory);
+}
+
+void Shell::SetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 2) {
+ const char* message = "setenv() takes two arguments";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ String::Utf8Value var(args.GetIsolate(), args[0]);
+ String::Utf8Value value(args.GetIsolate(), args[1]);
+ if (*var == nullptr) {
+ const char* message =
+ "os.setenv(): String conversion of variable name failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ if (*value == nullptr) {
+ const char* message =
+ "os.setenv(): String conversion of variable contents failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ setenv(*var, *value, 1);
+}
+
+void Shell::UnsetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1) {
+ const char* message = "unsetenv() takes one argument";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ String::Utf8Value var(args.GetIsolate(), args[0]);
+ if (*var == nullptr) {
+ const char* message =
+ "os.setenv(): String conversion of variable name failed.";
+ args.GetIsolate()->ThrowException(
+ String::NewFromUtf8(args.GetIsolate(), message, NewStringType::kNormal)
+ .ToLocalChecked());
+ return;
+ }
+ unsetenv(*var);
+}
+
+char* Shell::ReadCharsFromTcpPort(const char* name, int* size_out) {
+ DCHECK_GE(Shell::options.read_from_tcp_port, 0);
+
+ int sockfd = socket(PF_INET, SOCK_STREAM, 0);
+ if (sockfd < 0) {
+ fprintf(stderr, "Failed to create IPv4 socket\n");
+ return nullptr;
+ }
+
+ // Create an address for localhost:PORT where PORT is specified by the shell
+ // option --read-from-tcp-port.
+ sockaddr_in serv_addr;
+ memset(&serv_addr, 0, sizeof(sockaddr_in));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ serv_addr.sin_port = htons(Shell::options.read_from_tcp_port);
+
+ if (connect(sockfd, reinterpret_cast<sockaddr*>(&serv_addr),
+ sizeof(serv_addr)) < 0) {
+ fprintf(stderr, "Failed to connect to localhost:%d\n",
+ Shell::options.read_from_tcp_port);
+ close(sockfd);
+ return nullptr;
+ }
+
+ // The file server follows the simple protocol for requesting and receiving
+ // a file with a given filename:
+ //
+ // REQUEST client -> server: {filename}"\0"
+ // RESPONSE server -> client: {4-byte file-length}{file contents}
+ //
+ // i.e. the request sends the filename with a null terminator, and response
+ // sends the file contents by sending the length (as a 4-byte big-endian
+ // value) and the contents.
+
+ // If the file length is <0, there was an error sending the file, and the
+ // rest of the response is undefined (and may, in the future, contain an error
+ // message). The socket should be closed to avoid trying to interpret the
+ // undefined data.
+
+ // REQUEST
+ // Send the filename.
+ size_t sent_len = 0;
+ size_t name_len = strlen(name) + 1; // Includes the null terminator
+ while (sent_len < name_len) {
+ ssize_t sent_now = send(sockfd, name + sent_len, name_len - sent_len, 0);
+ if (sent_now < 0) {
+ fprintf(stderr, "Failed to send %s to localhost:%d\n", name,
+ Shell::options.read_from_tcp_port);
+ close(sockfd);
+ return nullptr;
+ }
+ sent_len += sent_now;
+ }
+
+ // RESPONSE
+ // Receive the file.
+ ssize_t received = 0;
+
+ // First, read the (zero-terminated) file length.
+ uint32_t big_endian_file_length;
+ received = recv(sockfd, &big_endian_file_length, 4, 0);
+ // We need those 4 bytes to read off the file length.
+ if (received < 4) {
+ fprintf(stderr, "Failed to receive %s's length from localhost:%d\n", name,
+ Shell::options.read_from_tcp_port);
+ close(sockfd);
+ return nullptr;
+ }
+ // Reinterpretet the received file length as a signed big-endian integer.
+ int32_t file_length = bit_cast<int32_t>(htonl(big_endian_file_length));
+
+ if (file_length < 0) {
+ fprintf(stderr, "Received length %d for %s from localhost:%d\n",
+ file_length, name, Shell::options.read_from_tcp_port);
+ close(sockfd);
+ return nullptr;
+ }
+
+ // Allocate the output array.
+ char* chars = new char[file_length];
+
+ // Now keep receiving and copying until the whole file is received.
+ ssize_t total_received = 0;
+ while (total_received < file_length) {
+ received =
+ recv(sockfd, chars + total_received, file_length - total_received, 0);
+ if (received < 0) {
+ fprintf(stderr, "Failed to receive %s from localhost:%d\n", name,
+ Shell::options.read_from_tcp_port);
+ close(sockfd);
+ delete[] chars;
+ return nullptr;
+ }
+ total_received += received;
+ }
+
+ close(sockfd);
+ *size_out = file_length;
+ return chars;
+}
+
+void Shell::AddOSMethods(Isolate* isolate, Local<ObjectTemplate> os_templ) {
+ if (options.enable_os_system) {
+ os_templ->Set(String::NewFromUtf8(isolate, "system", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, System));
+ }
+ os_templ->Set(String::NewFromUtf8(isolate, "chdir", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, ChangeDirectory));
+ os_templ->Set(String::NewFromUtf8(isolate, "setenv", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, SetEnvironment));
+ os_templ->Set(String::NewFromUtf8(isolate, "unsetenv", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, UnsetEnvironment));
+ os_templ->Set(String::NewFromUtf8(isolate, "umask", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, SetUMask));
+ os_templ->Set(String::NewFromUtf8(isolate, "mkdirp", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, MakeDirectory));
+ os_templ->Set(String::NewFromUtf8(isolate, "rmdir", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RemoveDirectory));
+}
+
+} // namespace v8
diff --git a/deps/v8/src/d8/d8-windows.cc b/deps/v8/src/d8/d8-windows.cc
new file mode 100644
index 0000000000..12e4f8b513
--- /dev/null
+++ b/deps/v8/src/d8/d8-windows.cc
@@ -0,0 +1,16 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/d8/d8.h"
+
+namespace v8 {
+
+void Shell::AddOSMethods(Isolate* isolate, Local<ObjectTemplate> os_templ) {}
+
+char* Shell::ReadCharsFromTcpPort(const char* name, int* size_out) {
+ // TODO(leszeks): No reason this shouldn't exist on windows.
+ return nullptr;
+}
+
+} // namespace v8
diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc
new file mode 100644
index 0000000000..a29c596909
--- /dev/null
+++ b/deps/v8/src/d8/d8.cc
@@ -0,0 +1,3572 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <fstream>
+#include <iomanip>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#ifdef ENABLE_VTUNE_JIT_INTERFACE
+#include "src/third_party/vtune/v8-vtune.h"
+#endif
+
+#include "include/libplatform/libplatform.h"
+#include "include/libplatform/v8-tracing.h"
+#include "include/v8-inspector.h"
+#include "src/api/api-inl.h"
+#include "src/base/cpu.h"
+#include "src/base/logging.h"
+#include "src/base/platform/platform.h"
+#include "src/base/platform/time.h"
+#include "src/base/sys-info.h"
+#include "src/d8/d8-console.h"
+#include "src/d8/d8-platforms.h"
+#include "src/d8/d8.h"
+#include "src/debug/debug-interface.h"
+#include "src/diagnostics/basic-block-profiler.h"
+#include "src/execution/vm-state-inl.h"
+#include "src/init/v8.h"
+#include "src/interpreter/interpreter.h"
+#include "src/logging/counters.h"
+#include "src/objects/objects-inl.h"
+#include "src/objects/objects.h"
+#include "src/parsing/parse-info.h"
+#include "src/parsing/parsing.h"
+#include "src/parsing/scanner-character-streams.h"
+#include "src/sanitizer/msan.h"
+#include "src/snapshot/natives.h"
+#include "src/trap-handler/trap-handler.h"
+#include "src/utils/ostreams.h"
+#include "src/utils/utils.h"
+#include "src/wasm/wasm-engine.h"
+
+#ifdef V8_INTL_SUPPORT
+#include "unicode/locid.h"
+#endif // V8_INTL_SUPPORT
+
+#if !defined(_WIN32) && !defined(_WIN64)
+#include <unistd.h> // NOLINT
+#else
+#include <windows.h> // NOLINT
+#endif // !defined(_WIN32) && !defined(_WIN64)
+
+#ifndef DCHECK
+#define DCHECK(condition) assert(condition)
+#endif
+
+#ifndef CHECK
+#define CHECK(condition) assert(condition)
+#endif
+
+namespace v8 {
+
+namespace {
+
+const int kMB = 1024 * 1024;
+
+const int kMaxWorkers = 100;
+const int kMaxSerializerMemoryUsage =
+ 1 * kMB; // Arbitrary maximum for testing.
+
+// Base class for shell ArrayBuffer allocators. It forwards all opertions to
+// the default v8 allocator.
+class ArrayBufferAllocatorBase : public v8::ArrayBuffer::Allocator {
+ public:
+ void* Allocate(size_t length) override {
+ return allocator_->Allocate(length);
+ }
+
+ void* AllocateUninitialized(size_t length) override {
+ return allocator_->AllocateUninitialized(length);
+ }
+
+ void Free(void* data, size_t length) override {
+ allocator_->Free(data, length);
+ }
+
+ private:
+ std::unique_ptr<Allocator> allocator_ =
+ std::unique_ptr<Allocator>(NewDefaultAllocator());
+};
+
+// ArrayBuffer allocator that can use virtual memory to improve performance.
+class ShellArrayBufferAllocator : public ArrayBufferAllocatorBase {
+ public:
+ void* Allocate(size_t length) override {
+ if (length >= kVMThreshold) return AllocateVM(length);
+ return ArrayBufferAllocatorBase::Allocate(length);
+ }
+
+ void* AllocateUninitialized(size_t length) override {
+ if (length >= kVMThreshold) return AllocateVM(length);
+ return ArrayBufferAllocatorBase::AllocateUninitialized(length);
+ }
+
+ void Free(void* data, size_t length) override {
+ if (length >= kVMThreshold) {
+ FreeVM(data, length);
+ } else {
+ ArrayBufferAllocatorBase::Free(data, length);
+ }
+ }
+
+ private:
+ static constexpr size_t kVMThreshold = 65536;
+ static constexpr size_t kTwoGB = 2u * 1024u * 1024u * 1024u;
+
+ void* AllocateVM(size_t length) {
+ DCHECK_LE(kVMThreshold, length);
+ // TODO(titzer): allocations should fail if >= 2gb because array buffers
+ // store their lengths as a SMI internally.
+ if (length >= kTwoGB) return nullptr;
+
+ v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator();
+ size_t page_size = page_allocator->AllocatePageSize();
+ size_t allocated = RoundUp(length, page_size);
+ // Rounding up could go over the limit.
+ if (allocated >= kTwoGB) return nullptr;
+ return i::AllocatePages(page_allocator, nullptr, allocated, page_size,
+ PageAllocator::kReadWrite);
+ }
+
+ void FreeVM(void* data, size_t length) {
+ v8::PageAllocator* page_allocator = i::GetPlatformPageAllocator();
+ size_t page_size = page_allocator->AllocatePageSize();
+ size_t allocated = RoundUp(length, page_size);
+ CHECK(i::FreePages(page_allocator, data, allocated));
+ }
+};
+
+// ArrayBuffer allocator that never allocates over 10MB.
+class MockArrayBufferAllocator : public ArrayBufferAllocatorBase {
+ protected:
+ void* Allocate(size_t length) override {
+ return ArrayBufferAllocatorBase::Allocate(Adjust(length));
+ }
+
+ void* AllocateUninitialized(size_t length) override {
+ return ArrayBufferAllocatorBase::AllocateUninitialized(Adjust(length));
+ }
+
+ void Free(void* data, size_t length) override {
+ return ArrayBufferAllocatorBase::Free(data, Adjust(length));
+ }
+
+ private:
+ size_t Adjust(size_t length) {
+ const size_t kAllocationLimit = 10 * kMB;
+ return length > kAllocationLimit ? i::AllocatePageSize() : length;
+ }
+};
+
+// ArrayBuffer allocator that can be equipped with a limit to simulate system
+// OOM.
+class MockArrayBufferAllocatiorWithLimit : public MockArrayBufferAllocator {
+ public:
+ explicit MockArrayBufferAllocatiorWithLimit(size_t allocation_limit)
+ : space_left_(allocation_limit) {}
+
+ protected:
+ void* Allocate(size_t length) override {
+ if (length > space_left_) {
+ return nullptr;
+ }
+ space_left_ -= length;
+ return MockArrayBufferAllocator::Allocate(length);
+ }
+
+ void* AllocateUninitialized(size_t length) override {
+ if (length > space_left_) {
+ return nullptr;
+ }
+ space_left_ -= length;
+ return MockArrayBufferAllocator::AllocateUninitialized(length);
+ }
+
+ void Free(void* data, size_t length) override {
+ space_left_ += length;
+ return MockArrayBufferAllocator::Free(data, length);
+ }
+
+ private:
+ std::atomic<size_t> space_left_;
+};
+
+v8::Platform* g_default_platform;
+std::unique_ptr<v8::Platform> g_platform;
+
+static Local<Value> Throw(Isolate* isolate, const char* message) {
+ return isolate->ThrowException(
+ String::NewFromUtf8(isolate, message, NewStringType::kNormal)
+ .ToLocalChecked());
+}
+
+static Local<Value> GetValue(v8::Isolate* isolate, Local<Context> context,
+ Local<v8::Object> object, const char* property) {
+ Local<String> v8_str =
+ String::NewFromUtf8(isolate, property, NewStringType::kNormal)
+ .ToLocalChecked();
+ return object->Get(context, v8_str).ToLocalChecked();
+}
+
+Worker* GetWorkerFromInternalField(Isolate* isolate, Local<Object> object) {
+ if (object->InternalFieldCount() != 1) {
+ Throw(isolate, "this is not a Worker");
+ return nullptr;
+ }
+
+ Worker* worker =
+ static_cast<Worker*>(object->GetAlignedPointerFromInternalField(0));
+ if (worker == nullptr) {
+ Throw(isolate, "Worker is defunct because main thread is terminating");
+ return nullptr;
+ }
+
+ return worker;
+}
+
+base::Thread::Options GetThreadOptions(const char* name) {
+ // On some systems (OSX 10.6) the stack size default is 0.5Mb or less
+ // which is not enough to parse the big literal expressions used in tests.
+ // The stack size should be at least StackGuard::kLimitSize + some
+ // OS-specific padding for thread startup code. 2Mbytes seems to be enough.
+ return base::Thread::Options(name, 2 * kMB);
+}
+
+} // namespace
+
+namespace tracing {
+
+namespace {
+
+// String options that can be used to initialize TraceOptions.
+const char kRecordUntilFull[] = "record-until-full";
+const char kRecordContinuously[] = "record-continuously";
+const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible";
+
+const char kRecordModeParam[] = "record_mode";
+const char kEnableSystraceParam[] = "enable_systrace";
+const char kEnableArgumentFilterParam[] = "enable_argument_filter";
+const char kIncludedCategoriesParam[] = "included_categories";
+
+class TraceConfigParser {
+ public:
+ static void FillTraceConfig(v8::Isolate* isolate,
+ platform::tracing::TraceConfig* trace_config,
+ const char* json_str) {
+ HandleScope outer_scope(isolate);
+ Local<Context> context = Context::New(isolate);
+ Context::Scope context_scope(context);
+ HandleScope inner_scope(isolate);
+
+ Local<String> source =
+ String::NewFromUtf8(isolate, json_str, NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<Value> result = JSON::Parse(context, source).ToLocalChecked();
+ Local<v8::Object> trace_config_object = Local<v8::Object>::Cast(result);
+
+ trace_config->SetTraceRecordMode(
+ GetTraceRecordMode(isolate, context, trace_config_object));
+ if (GetBoolean(isolate, context, trace_config_object,
+ kEnableSystraceParam)) {
+ trace_config->EnableSystrace();
+ }
+ if (GetBoolean(isolate, context, trace_config_object,
+ kEnableArgumentFilterParam)) {
+ trace_config->EnableArgumentFilter();
+ }
+ UpdateIncludedCategoriesList(isolate, context, trace_config_object,
+ trace_config);
+ }
+
+ private:
+ static bool GetBoolean(v8::Isolate* isolate, Local<Context> context,
+ Local<v8::Object> object, const char* property) {
+ Local<Value> value = GetValue(isolate, context, object, property);
+ if (value->IsNumber()) {
+ return value->BooleanValue(isolate);
+ }
+ return false;
+ }
+
+ static int UpdateIncludedCategoriesList(
+ v8::Isolate* isolate, Local<Context> context, Local<v8::Object> object,
+ platform::tracing::TraceConfig* trace_config) {
+ Local<Value> value =
+ GetValue(isolate, context, object, kIncludedCategoriesParam);
+ if (value->IsArray()) {
+ Local<Array> v8_array = Local<Array>::Cast(value);
+ for (int i = 0, length = v8_array->Length(); i < length; ++i) {
+ Local<Value> v = v8_array->Get(context, i)
+ .ToLocalChecked()
+ ->ToString(context)
+ .ToLocalChecked();
+ String::Utf8Value str(isolate, v->ToString(context).ToLocalChecked());
+ trace_config->AddIncludedCategory(*str);
+ }
+ return v8_array->Length();
+ }
+ return 0;
+ }
+
+ static platform::tracing::TraceRecordMode GetTraceRecordMode(
+ v8::Isolate* isolate, Local<Context> context, Local<v8::Object> object) {
+ Local<Value> value = GetValue(isolate, context, object, kRecordModeParam);
+ if (value->IsString()) {
+ Local<String> v8_string = value->ToString(context).ToLocalChecked();
+ String::Utf8Value str(isolate, v8_string);
+ if (strcmp(kRecordUntilFull, *str) == 0) {
+ return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL;
+ } else if (strcmp(kRecordContinuously, *str) == 0) {
+ return platform::tracing::TraceRecordMode::RECORD_CONTINUOUSLY;
+ } else if (strcmp(kRecordAsMuchAsPossible, *str) == 0) {
+ return platform::tracing::TraceRecordMode::RECORD_AS_MUCH_AS_POSSIBLE;
+ }
+ }
+ return platform::tracing::TraceRecordMode::RECORD_UNTIL_FULL;
+ }
+};
+
+} // namespace
+
+static platform::tracing::TraceConfig* CreateTraceConfigFromJSON(
+ v8::Isolate* isolate, const char* json_str) {
+ platform::tracing::TraceConfig* trace_config =
+ new platform::tracing::TraceConfig();
+ TraceConfigParser::FillTraceConfig(isolate, trace_config, json_str);
+ return trace_config;
+}
+
+} // namespace tracing
+
+class ExternalOwningOneByteStringResource
+ : public String::ExternalOneByteStringResource {
+ public:
+ ExternalOwningOneByteStringResource() {}
+ ExternalOwningOneByteStringResource(
+ std::unique_ptr<base::OS::MemoryMappedFile> file)
+ : file_(std::move(file)) {}
+ const char* data() const override {
+ return static_cast<char*>(file_->memory());
+ }
+ size_t length() const override { return file_->size(); }
+
+ private:
+ std::unique_ptr<base::OS::MemoryMappedFile> file_;
+};
+
+CounterMap* Shell::counter_map_;
+base::OS::MemoryMappedFile* Shell::counters_file_ = nullptr;
+CounterCollection Shell::local_counters_;
+CounterCollection* Shell::counters_ = &local_counters_;
+base::LazyMutex Shell::context_mutex_;
+const base::TimeTicks Shell::kInitialTicks =
+ base::TimeTicks::HighResolutionNow();
+Global<Function> Shell::stringify_function_;
+base::LazyMutex Shell::workers_mutex_;
+bool Shell::allow_new_workers_ = true;
+std::vector<Worker*> Shell::workers_;
+std::vector<ExternalizedContents> Shell::externalized_contents_;
+std::atomic<bool> Shell::script_executed_{false};
+base::LazyMutex Shell::isolate_status_lock_;
+std::map<v8::Isolate*, bool> Shell::isolate_status_;
+base::LazyMutex Shell::cached_code_mutex_;
+std::map<std::string, std::unique_ptr<ScriptCompiler::CachedData>>
+ Shell::cached_code_map_;
+
+Global<Context> Shell::evaluation_context_;
+ArrayBuffer::Allocator* Shell::array_buffer_allocator;
+ShellOptions Shell::options;
+base::OnceType Shell::quit_once_ = V8_ONCE_INIT;
+
+// Dummy external source stream which returns the whole source in one go.
+class DummySourceStream : public v8::ScriptCompiler::ExternalSourceStream {
+ public:
+ DummySourceStream(Local<String> source, Isolate* isolate) : done_(false) {
+ source_length_ = source->Utf8Length(isolate);
+ source_buffer_.reset(new uint8_t[source_length_]);
+ source->WriteUtf8(isolate, reinterpret_cast<char*>(source_buffer_.get()),
+ source_length_);
+ }
+
+ size_t GetMoreData(const uint8_t** src) override {
+ if (done_) {
+ return 0;
+ }
+ *src = source_buffer_.release();
+ done_ = true;
+
+ return source_length_;
+ }
+
+ private:
+ int source_length_;
+ std::unique_ptr<uint8_t[]> source_buffer_;
+ bool done_;
+};
+
+class BackgroundCompileThread : public base::Thread {
+ public:
+ BackgroundCompileThread(Isolate* isolate, Local<String> source)
+ : base::Thread(GetThreadOptions("BackgroundCompileThread")),
+ source_(source),
+ streamed_source_(base::make_unique<DummySourceStream>(source, isolate),
+ v8::ScriptCompiler::StreamedSource::UTF8),
+ task_(v8::ScriptCompiler::StartStreamingScript(isolate,
+ &streamed_source_)) {}
+
+ void Run() override { task_->Run(); }
+
+ v8::ScriptCompiler::StreamedSource* streamed_source() {
+ return &streamed_source_;
+ }
+
+ private:
+ Local<String> source_;
+ v8::ScriptCompiler::StreamedSource streamed_source_;
+ std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> task_;
+};
+
+ScriptCompiler::CachedData* Shell::LookupCodeCache(Isolate* isolate,
+ Local<Value> source) {
+ base::MutexGuard lock_guard(cached_code_mutex_.Pointer());
+ CHECK(source->IsString());
+ v8::String::Utf8Value key(isolate, source);
+ DCHECK(*key);
+ auto entry = cached_code_map_.find(*key);
+ if (entry != cached_code_map_.end() && entry->second) {
+ int length = entry->second->length;
+ uint8_t* cache = new uint8_t[length];
+ memcpy(cache, entry->second->data, length);
+ ScriptCompiler::CachedData* cached_data = new ScriptCompiler::CachedData(
+ cache, length, ScriptCompiler::CachedData::BufferOwned);
+ return cached_data;
+ }
+ return nullptr;
+}
+
+void Shell::StoreInCodeCache(Isolate* isolate, Local<Value> source,
+ const ScriptCompiler::CachedData* cache_data) {
+ base::MutexGuard lock_guard(cached_code_mutex_.Pointer());
+ CHECK(source->IsString());
+ if (cache_data == nullptr) return;
+ v8::String::Utf8Value key(isolate, source);
+ DCHECK(*key);
+ int length = cache_data->length;
+ uint8_t* cache = new uint8_t[length];
+ memcpy(cache, cache_data->data, length);
+ cached_code_map_[*key] = std::unique_ptr<ScriptCompiler::CachedData>(
+ new ScriptCompiler::CachedData(cache, length,
+ ScriptCompiler::CachedData::BufferOwned));
+}
+
+// Executes a string within the current v8 context.
+bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
+ Local<Value> name, PrintResult print_result,
+ ReportExceptions report_exceptions,
+ ProcessMessageQueue process_message_queue) {
+ if (i::FLAG_parse_only) {
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
+ i::VMState<PARSER> state(i_isolate);
+ i::Handle<i::String> str = Utils::OpenHandle(*(source));
+
+ // Set up ParseInfo.
+ i::ParseInfo parse_info(i_isolate);
+ parse_info.set_toplevel();
+ parse_info.set_allow_lazy_parsing();
+ parse_info.set_language_mode(
+ i::construct_language_mode(i::FLAG_use_strict));
+ parse_info.set_script(
+ parse_info.CreateScript(i_isolate, str, options.compile_options));
+
+ if (!i::parsing::ParseProgram(&parse_info, i_isolate)) {
+ fprintf(stderr, "Failed parsing\n");
+ return false;
+ }
+ return true;
+ }
+
+ HandleScope handle_scope(isolate);
+ TryCatch try_catch(isolate);
+ try_catch.SetVerbose(true);
+
+ MaybeLocal<Value> maybe_result;
+ bool success = true;
+ {
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ Local<Context> realm =
+ Local<Context>::New(isolate, data->realms_[data->realm_current_]);
+ Context::Scope context_scope(realm);
+ MaybeLocal<Script> maybe_script;
+ Local<Context> context(isolate->GetCurrentContext());
+ ScriptOrigin origin(name);
+
+ if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
+ ScriptCompiler::CachedData* cached_code =
+ LookupCodeCache(isolate, source);
+ if (cached_code != nullptr) {
+ ScriptCompiler::Source script_source(source, origin, cached_code);
+ maybe_script = ScriptCompiler::Compile(context, &script_source,
+ options.compile_options);
+ CHECK(!cached_code->rejected);
+ } else {
+ ScriptCompiler::Source script_source(source, origin);
+ maybe_script = ScriptCompiler::Compile(
+ context, &script_source, ScriptCompiler::kNoCompileOptions);
+ }
+ } else if (options.stress_background_compile) {
+ // Start a background thread compiling the script.
+ BackgroundCompileThread background_compile_thread(isolate, source);
+ background_compile_thread.Start();
+
+ // In parallel, compile on the main thread to flush out any data races.
+ {
+ TryCatch ignore_try_catch(isolate);
+ ScriptCompiler::Source script_source(source, origin);
+ USE(ScriptCompiler::Compile(context, &script_source,
+ ScriptCompiler::kNoCompileOptions));
+ }
+
+ // Join with background thread and finalize compilation.
+ background_compile_thread.Join();
+ maybe_script = v8::ScriptCompiler::Compile(
+ context, background_compile_thread.streamed_source(), source, origin);
+ } else {
+ ScriptCompiler::Source script_source(source, origin);
+ maybe_script = ScriptCompiler::Compile(context, &script_source,
+ options.compile_options);
+ }
+
+ Local<Script> script;
+ if (!maybe_script.ToLocal(&script)) {
+ // Print errors that happened during compilation.
+ if (report_exceptions) ReportException(isolate, &try_catch);
+ return false;
+ }
+
+ if (options.code_cache_options ==
+ ShellOptions::CodeCacheOptions::kProduceCache) {
+ // Serialize and store it in memory for the next execution.
+ ScriptCompiler::CachedData* cached_data =
+ ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
+ StoreInCodeCache(isolate, source, cached_data);
+ delete cached_data;
+ }
+ maybe_result = script->Run(realm);
+ if (options.code_cache_options ==
+ ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
+ // Serialize and store it in memory for the next execution.
+ ScriptCompiler::CachedData* cached_data =
+ ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
+ StoreInCodeCache(isolate, source, cached_data);
+ delete cached_data;
+ }
+ if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
+ data->realm_current_ = data->realm_switch_;
+ }
+ Local<Value> result;
+ if (!maybe_result.ToLocal(&result)) {
+ DCHECK(try_catch.HasCaught());
+ // Print errors that happened during execution.
+ if (report_exceptions) ReportException(isolate, &try_catch);
+ return false;
+ }
+ DCHECK(!try_catch.HasCaught());
+ if (print_result) {
+ if (options.test_shell) {
+ if (!result->IsUndefined()) {
+ // If all went well and the result wasn't undefined then print
+ // the returned value.
+ v8::String::Utf8Value str(isolate, result);
+ fwrite(*str, sizeof(**str), str.length(), stdout);
+ printf("\n");
+ }
+ } else {
+ v8::String::Utf8Value str(isolate, Stringify(isolate, result));
+ fwrite(*str, sizeof(**str), str.length(), stdout);
+ printf("\n");
+ }
+ }
+ return success;
+}
+
+namespace {
+
+std::string ToSTLString(Isolate* isolate, Local<String> v8_str) {
+ String::Utf8Value utf8(isolate, v8_str);
+ // Should not be able to fail since the input is a String.
+ CHECK(*utf8);
+ return *utf8;
+}
+
+bool IsAbsolutePath(const std::string& path) {
+#if defined(_WIN32) || defined(_WIN64)
+ // TODO(adamk): This is an incorrect approximation, but should
+ // work for all our test-running cases.
+ return path.find(':') != std::string::npos;
+#else
+ return path[0] == '/';
+#endif
+}
+
+std::string GetWorkingDirectory() {
+#if defined(_WIN32) || defined(_WIN64)
+ char system_buffer[MAX_PATH];
+ // TODO(adamk): Support Unicode paths.
+ DWORD len = GetCurrentDirectoryA(MAX_PATH, system_buffer);
+ CHECK_GT(len, 0);
+ return system_buffer;
+#else
+ char curdir[PATH_MAX];
+ CHECK_NOT_NULL(getcwd(curdir, PATH_MAX));
+ return curdir;
+#endif
+}
+
+// Returns the directory part of path, without the trailing '/'.
+std::string DirName(const std::string& path) {
+ DCHECK(IsAbsolutePath(path));
+ size_t last_slash = path.find_last_of('/');
+ DCHECK(last_slash != std::string::npos);
+ return path.substr(0, last_slash);
+}
+
+// Resolves path to an absolute path if necessary, and does some
+// normalization (eliding references to the current directory
+// and replacing backslashes with slashes).
+std::string NormalizePath(const std::string& path,
+ const std::string& dir_name) {
+ std::string result;
+ if (IsAbsolutePath(path)) {
+ result = path;
+ } else {
+ result = dir_name + '/' + path;
+ }
+ std::replace(result.begin(), result.end(), '\\', '/');
+ size_t i;
+ while ((i = result.find("/./")) != std::string::npos) {
+ result.erase(i, 2);
+ }
+ return result;
+}
+
+// Per-context Module data, allowing sharing of module maps
+// across top-level module loads.
+class ModuleEmbedderData {
+ private:
+ class ModuleGlobalHash {
+ public:
+ explicit ModuleGlobalHash(Isolate* isolate) : isolate_(isolate) {}
+ size_t operator()(const Global<Module>& module) const {
+ return module.Get(isolate_)->GetIdentityHash();
+ }
+
+ private:
+ Isolate* isolate_;
+ };
+
+ public:
+ explicit ModuleEmbedderData(Isolate* isolate)
+ : module_to_specifier_map(10, ModuleGlobalHash(isolate)) {}
+
+ // Map from normalized module specifier to Module.
+ std::unordered_map<std::string, Global<Module>> specifier_to_module_map;
+ // Map from Module to its URL as defined in the ScriptOrigin
+ std::unordered_map<Global<Module>, std::string, ModuleGlobalHash>
+ module_to_specifier_map;
+};
+
+enum { kModuleEmbedderDataIndex, kInspectorClientIndex };
+
+void InitializeModuleEmbedderData(Local<Context> context) {
+ context->SetAlignedPointerInEmbedderData(
+ kModuleEmbedderDataIndex, new ModuleEmbedderData(context->GetIsolate()));
+}
+
+ModuleEmbedderData* GetModuleDataFromContext(Local<Context> context) {
+ return static_cast<ModuleEmbedderData*>(
+ context->GetAlignedPointerFromEmbedderData(kModuleEmbedderDataIndex));
+}
+
+void DisposeModuleEmbedderData(Local<Context> context) {
+ delete GetModuleDataFromContext(context);
+ context->SetAlignedPointerInEmbedderData(kModuleEmbedderDataIndex, nullptr);
+}
+
+MaybeLocal<Module> ResolveModuleCallback(Local<Context> context,
+ Local<String> specifier,
+ Local<Module> referrer) {
+ Isolate* isolate = context->GetIsolate();
+ ModuleEmbedderData* d = GetModuleDataFromContext(context);
+ auto specifier_it =
+ d->module_to_specifier_map.find(Global<Module>(isolate, referrer));
+ CHECK(specifier_it != d->module_to_specifier_map.end());
+ std::string absolute_path = NormalizePath(ToSTLString(isolate, specifier),
+ DirName(specifier_it->second));
+ auto module_it = d->specifier_to_module_map.find(absolute_path);
+ CHECK(module_it != d->specifier_to_module_map.end());
+ return module_it->second.Get(isolate);
+}
+
+} // anonymous namespace
+
+MaybeLocal<Module> Shell::FetchModuleTree(Local<Context> context,
+ const std::string& file_name) {
+ DCHECK(IsAbsolutePath(file_name));
+ Isolate* isolate = context->GetIsolate();
+ Local<String> source_text = ReadFile(isolate, file_name.c_str());
+ if (source_text.IsEmpty()) {
+ std::string msg = "Error reading: " + file_name;
+ Throw(isolate, msg.c_str());
+ return MaybeLocal<Module>();
+ }
+ ScriptOrigin origin(
+ String::NewFromUtf8(isolate, file_name.c_str(), NewStringType::kNormal)
+ .ToLocalChecked(),
+ Local<Integer>(), Local<Integer>(), Local<Boolean>(), Local<Integer>(),
+ Local<Value>(), Local<Boolean>(), Local<Boolean>(), True(isolate));
+ ScriptCompiler::Source source(source_text, origin);
+ Local<Module> module;
+ if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
+ return MaybeLocal<Module>();
+ }
+
+ ModuleEmbedderData* d = GetModuleDataFromContext(context);
+ CHECK(d->specifier_to_module_map
+ .insert(std::make_pair(file_name, Global<Module>(isolate, module)))
+ .second);
+ CHECK(d->module_to_specifier_map
+ .insert(std::make_pair(Global<Module>(isolate, module), file_name))
+ .second);
+
+ std::string dir_name = DirName(file_name);
+
+ for (int i = 0, length = module->GetModuleRequestsLength(); i < length; ++i) {
+ Local<String> name = module->GetModuleRequest(i);
+ std::string absolute_path =
+ NormalizePath(ToSTLString(isolate, name), dir_name);
+ if (!d->specifier_to_module_map.count(absolute_path)) {
+ if (FetchModuleTree(context, absolute_path).IsEmpty()) {
+ return MaybeLocal<Module>();
+ }
+ }
+ }
+
+ return module;
+}
+
+namespace {
+
+struct DynamicImportData {
+ DynamicImportData(Isolate* isolate_, Local<String> referrer_,
+ Local<String> specifier_,
+ Local<Promise::Resolver> resolver_)
+ : isolate(isolate_) {
+ referrer.Reset(isolate, referrer_);
+ specifier.Reset(isolate, specifier_);
+ resolver.Reset(isolate, resolver_);
+ }
+
+ Isolate* isolate;
+ Global<String> referrer;
+ Global<String> specifier;
+ Global<Promise::Resolver> resolver;
+};
+
+} // namespace
+
+MaybeLocal<Promise> Shell::HostImportModuleDynamically(
+ Local<Context> context, Local<ScriptOrModule> referrer,
+ Local<String> specifier) {
+ Isolate* isolate = context->GetIsolate();
+
+ MaybeLocal<Promise::Resolver> maybe_resolver =
+ Promise::Resolver::New(context);
+ Local<Promise::Resolver> resolver;
+ if (maybe_resolver.ToLocal(&resolver)) {
+ DynamicImportData* data = new DynamicImportData(
+ isolate, Local<String>::Cast(referrer->GetResourceName()), specifier,
+ resolver);
+ isolate->EnqueueMicrotask(Shell::DoHostImportModuleDynamically, data);
+ return resolver->GetPromise();
+ }
+
+ return MaybeLocal<Promise>();
+}
+
+void Shell::HostInitializeImportMetaObject(Local<Context> context,
+ Local<Module> module,
+ Local<Object> meta) {
+ Isolate* isolate = context->GetIsolate();
+ HandleScope handle_scope(isolate);
+
+ ModuleEmbedderData* d = GetModuleDataFromContext(context);
+ auto specifier_it =
+ d->module_to_specifier_map.find(Global<Module>(isolate, module));
+ CHECK(specifier_it != d->module_to_specifier_map.end());
+
+ Local<String> url_key =
+ String::NewFromUtf8(isolate, "url", NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> url = String::NewFromUtf8(isolate, specifier_it->second.c_str(),
+ NewStringType::kNormal)
+ .ToLocalChecked();
+ meta->CreateDataProperty(context, url_key, url).ToChecked();
+}
+
+void Shell::DoHostImportModuleDynamically(void* import_data) {
+ std::unique_ptr<DynamicImportData> import_data_(
+ static_cast<DynamicImportData*>(import_data));
+ Isolate* isolate(import_data_->isolate);
+ HandleScope handle_scope(isolate);
+
+ Local<String> referrer(import_data_->referrer.Get(isolate));
+ Local<String> specifier(import_data_->specifier.Get(isolate));
+ Local<Promise::Resolver> resolver(import_data_->resolver.Get(isolate));
+
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
+ Context::Scope context_scope(realm);
+
+ std::string source_url = ToSTLString(isolate, referrer);
+ std::string dir_name =
+ DirName(NormalizePath(source_url, GetWorkingDirectory()));
+ std::string file_name = ToSTLString(isolate, specifier);
+ std::string absolute_path = NormalizePath(file_name, dir_name);
+
+ TryCatch try_catch(isolate);
+ try_catch.SetVerbose(true);
+
+ ModuleEmbedderData* d = GetModuleDataFromContext(realm);
+ Local<Module> root_module;
+ auto module_it = d->specifier_to_module_map.find(absolute_path);
+ if (module_it != d->specifier_to_module_map.end()) {
+ root_module = module_it->second.Get(isolate);
+ } else if (!FetchModuleTree(realm, absolute_path).ToLocal(&root_module)) {
+ CHECK(try_catch.HasCaught());
+ resolver->Reject(realm, try_catch.Exception()).ToChecked();
+ return;
+ }
+
+ MaybeLocal<Value> maybe_result;
+ if (root_module->InstantiateModule(realm, ResolveModuleCallback)
+ .FromMaybe(false)) {
+ maybe_result = root_module->Evaluate(realm);
+ EmptyMessageQueues(isolate);
+ }
+
+ Local<Value> module;
+ if (!maybe_result.ToLocal(&module)) {
+ DCHECK(try_catch.HasCaught());
+ resolver->Reject(realm, try_catch.Exception()).ToChecked();
+ return;
+ }
+
+ DCHECK(!try_catch.HasCaught());
+ Local<Value> module_namespace = root_module->GetModuleNamespace();
+ resolver->Resolve(realm, module_namespace).ToChecked();
+}
+
+bool Shell::ExecuteModule(Isolate* isolate, const char* file_name) {
+ HandleScope handle_scope(isolate);
+
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ Local<Context> realm = data->realms_[data->realm_current_].Get(isolate);
+ Context::Scope context_scope(realm);
+
+ std::string absolute_path = NormalizePath(file_name, GetWorkingDirectory());
+
+ TryCatch try_catch(isolate);
+ try_catch.SetVerbose(true);
+
+ Local<Module> root_module;
+ MaybeLocal<Value> maybe_exception;
+
+ if (!FetchModuleTree(realm, absolute_path).ToLocal(&root_module)) {
+ CHECK(try_catch.HasCaught());
+ ReportException(isolate, &try_catch);
+ return false;
+ }
+
+ MaybeLocal<Value> maybe_result;
+ if (root_module->InstantiateModule(realm, ResolveModuleCallback)
+ .FromMaybe(false)) {
+ maybe_result = root_module->Evaluate(realm);
+ EmptyMessageQueues(isolate);
+ }
+ Local<Value> result;
+ if (!maybe_result.ToLocal(&result)) {
+ DCHECK(try_catch.HasCaught());
+ // Print errors that happened during execution.
+ ReportException(isolate, &try_catch);
+ return false;
+ }
+ DCHECK(!try_catch.HasCaught());
+ return true;
+}
+
+PerIsolateData::PerIsolateData(Isolate* isolate)
+ : isolate_(isolate), realms_(nullptr) {
+ isolate->SetData(0, this);
+ if (i::FLAG_expose_async_hooks) {
+ async_hooks_wrapper_ = new AsyncHooks(isolate);
+ }
+}
+
+PerIsolateData::~PerIsolateData() {
+ isolate_->SetData(0, nullptr); // Not really needed, just to be sure...
+ if (i::FLAG_expose_async_hooks) {
+ delete async_hooks_wrapper_; // This uses the isolate
+ }
+}
+
+void PerIsolateData::SetTimeout(Local<Function> callback,
+ Local<Context> context) {
+ set_timeout_callbacks_.emplace(isolate_, callback);
+ set_timeout_contexts_.emplace(isolate_, context);
+}
+
+MaybeLocal<Function> PerIsolateData::GetTimeoutCallback() {
+ if (set_timeout_callbacks_.empty()) return MaybeLocal<Function>();
+ Local<Function> result = set_timeout_callbacks_.front().Get(isolate_);
+ set_timeout_callbacks_.pop();
+ return result;
+}
+
+MaybeLocal<Context> PerIsolateData::GetTimeoutContext() {
+ if (set_timeout_contexts_.empty()) return MaybeLocal<Context>();
+ Local<Context> result = set_timeout_contexts_.front().Get(isolate_);
+ set_timeout_contexts_.pop();
+ return result;
+}
+
+PerIsolateData::RealmScope::RealmScope(PerIsolateData* data) : data_(data) {
+ data_->realm_count_ = 1;
+ data_->realm_current_ = 0;
+ data_->realm_switch_ = 0;
+ data_->realms_ = new Global<Context>[1];
+ data_->realms_[0].Reset(data_->isolate_,
+ data_->isolate_->GetEnteredOrMicrotaskContext());
+}
+
+PerIsolateData::RealmScope::~RealmScope() {
+ // Drop realms to avoid keeping them alive. We don't dispose the
+ // module embedder data for the first realm here, but instead do
+ // it in RunShell or in RunMain, if not running in interactive mode
+ for (int i = 1; i < data_->realm_count_; ++i) {
+ Global<Context>& realm = data_->realms_[i];
+ if (realm.IsEmpty()) continue;
+ DisposeModuleEmbedderData(realm.Get(data_->isolate_));
+ }
+ data_->realm_count_ = 0;
+ delete[] data_->realms_;
+}
+
+int PerIsolateData::RealmFind(Local<Context> context) {
+ for (int i = 0; i < realm_count_; ++i) {
+ if (realms_[i] == context) return i;
+ }
+ return -1;
+}
+
+int PerIsolateData::RealmIndexOrThrow(
+ const v8::FunctionCallbackInfo<v8::Value>& args, int arg_offset) {
+ if (args.Length() < arg_offset || !args[arg_offset]->IsNumber()) {
+ Throw(args.GetIsolate(), "Invalid argument");
+ return -1;
+ }
+ int index = args[arg_offset]
+ ->Int32Value(args.GetIsolate()->GetCurrentContext())
+ .FromMaybe(-1);
+ if (index < 0 || index >= realm_count_ || realms_[index].IsEmpty()) {
+ Throw(args.GetIsolate(), "Invalid realm index");
+ return -1;
+ }
+ return index;
+}
+
+// performance.now() returns a time stamp as double, measured in milliseconds.
+// When FLAG_verify_predictable mode is enabled it returns result of
+// v8::Platform::MonotonicallyIncreasingTime().
+void Shell::PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (i::FLAG_verify_predictable) {
+ args.GetReturnValue().Set(g_platform->MonotonicallyIncreasingTime());
+ } else {
+ base::TimeDelta delta =
+ base::TimeTicks::HighResolutionNow() - kInitialTicks;
+ args.GetReturnValue().Set(delta.InMillisecondsF());
+ }
+}
+
+// Realm.current() returns the index of the currently active realm.
+void Shell::RealmCurrent(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmFind(isolate->GetEnteredOrMicrotaskContext());
+ if (index == -1) return;
+ args.GetReturnValue().Set(index);
+}
+
+// Realm.owner(o) returns the index of the realm that created o.
+void Shell::RealmOwner(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ if (args.Length() < 1 || !args[0]->IsObject()) {
+ Throw(args.GetIsolate(), "Invalid argument");
+ return;
+ }
+ int index = data->RealmFind(args[0]
+ ->ToObject(isolate->GetCurrentContext())
+ .ToLocalChecked()
+ ->CreationContext());
+ if (index == -1) return;
+ args.GetReturnValue().Set(index);
+}
+
+// Realm.global(i) returns the global object of realm i.
+// (Note that properties of global objects cannot be read/written cross-realm.)
+void Shell::RealmGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ PerIsolateData* data = PerIsolateData::Get(args.GetIsolate());
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ args.GetReturnValue().Set(
+ Local<Context>::New(args.GetIsolate(), data->realms_[index])->Global());
+}
+
+MaybeLocal<Context> Shell::CreateRealm(
+ const v8::FunctionCallbackInfo<v8::Value>& args, int index,
+ v8::MaybeLocal<Value> global_object) {
+ Isolate* isolate = args.GetIsolate();
+ TryCatch try_catch(isolate);
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ if (index < 0) {
+ Global<Context>* old_realms = data->realms_;
+ index = data->realm_count_;
+ data->realms_ = new Global<Context>[++data->realm_count_];
+ for (int i = 0; i < index; ++i) {
+ data->realms_[i].Reset(isolate, old_realms[i]);
+ old_realms[i].Reset();
+ }
+ delete[] old_realms;
+ }
+ Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
+ Local<Context> context =
+ Context::New(isolate, nullptr, global_template, global_object);
+ DCHECK(!try_catch.HasCaught());
+ if (context.IsEmpty()) return MaybeLocal<Context>();
+ InitializeModuleEmbedderData(context);
+ data->realms_[index].Reset(isolate, context);
+ args.GetReturnValue().Set(index);
+ return context;
+}
+
+void Shell::DisposeRealm(const v8::FunctionCallbackInfo<v8::Value>& args,
+ int index) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ DisposeModuleEmbedderData(data->realms_[index].Get(isolate));
+ data->realms_[index].Reset();
+ isolate->ContextDisposedNotification();
+ isolate->IdleNotificationDeadline(g_platform->MonotonicallyIncreasingTime());
+}
+
+// Realm.create() creates a new realm with a distinct security token
+// and returns its index.
+void Shell::RealmCreate(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CreateRealm(args, -1, v8::MaybeLocal<Value>());
+}
+
+// Realm.createAllowCrossRealmAccess() creates a new realm with the same
+// security token as the current realm.
+void Shell::RealmCreateAllowCrossRealmAccess(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Local<Context> context;
+ if (CreateRealm(args, -1, v8::MaybeLocal<Value>()).ToLocal(&context)) {
+ context->SetSecurityToken(
+ args.GetIsolate()->GetEnteredOrMicrotaskContext()->GetSecurityToken());
+ }
+}
+
+// Realm.navigate(i) creates a new realm with a distinct security token
+// in place of realm i.
+void Shell::RealmNavigate(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ if (index == 0 || index == data->realm_current_ ||
+ index == data->realm_switch_) {
+ Throw(args.GetIsolate(), "Invalid realm index");
+ return;
+ }
+
+ Local<Context> context = Local<Context>::New(isolate, data->realms_[index]);
+ v8::MaybeLocal<Value> global_object = context->Global();
+
+ // Context::Global doesn't return JSGlobalProxy if DetachGlobal is called in
+ // advance.
+ if (!global_object.IsEmpty()) {
+ HandleScope scope(isolate);
+ if (!Utils::OpenHandle(*global_object.ToLocalChecked())
+ ->IsJSGlobalProxy()) {
+ global_object = v8::MaybeLocal<Value>();
+ }
+ }
+
+ DisposeRealm(args, index);
+ CreateRealm(args, index, global_object);
+}
+
+// Realm.detachGlobal(i) detaches the global objects of realm i from realm i.
+void Shell::RealmDetachGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ if (index == 0 || index == data->realm_current_ ||
+ index == data->realm_switch_) {
+ Throw(args.GetIsolate(), "Invalid realm index");
+ return;
+ }
+
+ HandleScope scope(isolate);
+ Local<Context> realm = Local<Context>::New(isolate, data->realms_[index]);
+ realm->DetachGlobal();
+}
+
+// Realm.dispose(i) disposes the reference to the realm i.
+void Shell::RealmDispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ if (index == 0 || index == data->realm_current_ ||
+ index == data->realm_switch_) {
+ Throw(args.GetIsolate(), "Invalid realm index");
+ return;
+ }
+ DisposeRealm(args, index);
+}
+
+// Realm.switch(i) switches to the realm i for consecutive interactive inputs.
+void Shell::RealmSwitch(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ data->realm_switch_ = index;
+}
+
+// Realm.eval(i, s) evaluates s in realm i and returns the result.
+void Shell::RealmEval(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ int index = data->RealmIndexOrThrow(args, 0);
+ if (index == -1) return;
+ if (args.Length() < 2 || !args[1]->IsString()) {
+ Throw(args.GetIsolate(), "Invalid argument");
+ return;
+ }
+ ScriptCompiler::Source script_source(
+ args[1]->ToString(isolate->GetCurrentContext()).ToLocalChecked());
+ Local<UnboundScript> script;
+ if (!ScriptCompiler::CompileUnboundScript(isolate, &script_source)
+ .ToLocal(&script)) {
+ return;
+ }
+ Local<Context> realm = Local<Context>::New(isolate, data->realms_[index]);
+ realm->Enter();
+ int previous_index = data->realm_current_;
+ data->realm_current_ = data->realm_switch_ = index;
+ Local<Value> result;
+ if (!script->BindToCurrentContext()->Run(realm).ToLocal(&result)) {
+ realm->Exit();
+ data->realm_current_ = data->realm_switch_ = previous_index;
+ return;
+ }
+ realm->Exit();
+ data->realm_current_ = data->realm_switch_ = previous_index;
+ args.GetReturnValue().Set(result);
+}
+
+// Realm.shared is an accessor for a single shared value across realms.
+void Shell::RealmSharedGet(Local<String> property,
+ const PropertyCallbackInfo<Value>& info) {
+ Isolate* isolate = info.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ if (data->realm_shared_.IsEmpty()) return;
+ info.GetReturnValue().Set(data->realm_shared_);
+}
+
+void Shell::RealmSharedSet(Local<String> property, Local<Value> value,
+ const PropertyCallbackInfo<void>& info) {
+ Isolate* isolate = info.GetIsolate();
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ data->realm_shared_.Reset(isolate, value);
+}
+
+// async_hooks.createHook() registers functions to be called for different
+// lifetime events of each async operation.
+void Shell::AsyncHooksCreateHook(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Local<Object> wrap =
+ PerIsolateData::Get(args.GetIsolate())->GetAsyncHooks()->CreateHook(args);
+ args.GetReturnValue().Set(wrap);
+}
+
+// async_hooks.executionAsyncId() returns the asyncId of the current execution
+// context.
+void Shell::AsyncHooksExecutionAsyncId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+ args.GetReturnValue().Set(v8::Number::New(
+ isolate,
+ PerIsolateData::Get(isolate)->GetAsyncHooks()->GetExecutionAsyncId()));
+}
+
+void Shell::AsyncHooksTriggerAsyncId(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+ args.GetReturnValue().Set(v8::Number::New(
+ isolate,
+ PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId()));
+}
+
+void WriteToFile(FILE* file, const v8::FunctionCallbackInfo<v8::Value>& args) {
+ for (int i = 0; i < args.Length(); i++) {
+ HandleScope handle_scope(args.GetIsolate());
+ if (i != 0) {
+ fprintf(file, " ");
+ }
+
+ // Explicitly catch potential exceptions in toString().
+ v8::TryCatch try_catch(args.GetIsolate());
+ Local<Value> arg = args[i];
+ Local<String> str_obj;
+
+ if (arg->IsSymbol()) {
+ arg = Local<Symbol>::Cast(arg)->Name();
+ }
+ if (!arg->ToString(args.GetIsolate()->GetCurrentContext())
+ .ToLocal(&str_obj)) {
+ try_catch.ReThrow();
+ return;
+ }
+
+ v8::String::Utf8Value str(args.GetIsolate(), str_obj);
+ int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file));
+ if (n != str.length()) {
+ printf("Error in fwrite\n");
+ base::OS::ExitProcess(1);
+ }
+ }
+}
+
+void WriteAndFlush(FILE* file,
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ WriteToFile(file, args);
+ fprintf(file, "\n");
+ fflush(file);
+}
+
+void Shell::Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ WriteAndFlush(stdout, args);
+}
+
+void Shell::PrintErr(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ WriteAndFlush(stderr, args);
+}
+
+void Shell::Write(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ WriteToFile(stdout, args);
+}
+
+void Shell::Read(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ String::Utf8Value file(args.GetIsolate(), args[0]);
+ if (*file == nullptr) {
+ Throw(args.GetIsolate(), "Error loading file");
+ return;
+ }
+ if (args.Length() == 2) {
+ String::Utf8Value format(args.GetIsolate(), args[1]);
+ if (*format && std::strcmp(*format, "binary") == 0) {
+ ReadBuffer(args);
+ return;
+ }
+ }
+ Local<String> source = ReadFile(args.GetIsolate(), *file);
+ if (source.IsEmpty()) {
+ Throw(args.GetIsolate(), "Error loading file");
+ return;
+ }
+ args.GetReturnValue().Set(source);
+}
+
+Local<String> Shell::ReadFromStdin(Isolate* isolate) {
+ static const int kBufferSize = 256;
+ char buffer[kBufferSize];
+ Local<String> accumulator =
+ String::NewFromUtf8(isolate, "", NewStringType::kNormal).ToLocalChecked();
+ int length;
+ while (true) {
+ // Continue reading if the line ends with an escape '\\' or the line has
+ // not been fully read into the buffer yet (does not end with '\n').
+ // If fgets gets an error, just give up.
+ char* input = nullptr;
+ input = fgets(buffer, kBufferSize, stdin);
+ if (input == nullptr) return Local<String>();
+ length = static_cast<int>(strlen(buffer));
+ if (length == 0) {
+ return accumulator;
+ } else if (buffer[length - 1] != '\n') {
+ accumulator = String::Concat(
+ isolate, accumulator,
+ String::NewFromUtf8(isolate, buffer, NewStringType::kNormal, length)
+ .ToLocalChecked());
+ } else if (length > 1 && buffer[length - 2] == '\\') {
+ buffer[length - 2] = '\n';
+ accumulator =
+ String::Concat(isolate, accumulator,
+ String::NewFromUtf8(isolate, buffer,
+ NewStringType::kNormal, length - 1)
+ .ToLocalChecked());
+ } else {
+ return String::Concat(
+ isolate, accumulator,
+ String::NewFromUtf8(isolate, buffer, NewStringType::kNormal,
+ length - 1)
+ .ToLocalChecked());
+ }
+ }
+}
+
+void Shell::Load(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ for (int i = 0; i < args.Length(); i++) {
+ HandleScope handle_scope(args.GetIsolate());
+ String::Utf8Value file(args.GetIsolate(), args[i]);
+ if (*file == nullptr) {
+ Throw(args.GetIsolate(), "Error loading file");
+ return;
+ }
+ Local<String> source = ReadFile(args.GetIsolate(), *file);
+ if (source.IsEmpty()) {
+ Throw(args.GetIsolate(), "Error loading file");
+ return;
+ }
+ if (!ExecuteString(
+ args.GetIsolate(), source,
+ String::NewFromUtf8(args.GetIsolate(), *file,
+ NewStringType::kNormal)
+ .ToLocalChecked(),
+ kNoPrintResult,
+ options.quiet_load ? kNoReportExceptions : kReportExceptions,
+ kNoProcessMessageQueue)) {
+ Throw(args.GetIsolate(), "Error executing file");
+ return;
+ }
+ }
+}
+
+void Shell::SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ args.GetReturnValue().Set(v8::Number::New(isolate, 0));
+ if (args.Length() == 0 || !args[0]->IsFunction()) return;
+ Local<Function> callback = Local<Function>::Cast(args[0]);
+ Local<Context> context = isolate->GetCurrentContext();
+ PerIsolateData::Get(isolate)->SetTimeout(callback, context);
+}
+
+void Shell::WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+ if (args.Length() < 1 || !args[0]->IsString()) {
+ Throw(args.GetIsolate(), "1st argument must be string");
+ return;
+ }
+
+ // d8 honors `options={type: string}`, which means the first argument is
+ // not a filename but string of script to be run.
+ bool load_from_file = true;
+ if (args.Length() > 1 && args[1]->IsObject()) {
+ Local<Object> object = args[1].As<Object>();
+ Local<Context> context = isolate->GetCurrentContext();
+ Local<Value> value = GetValue(args.GetIsolate(), context, object, "type");
+ if (value->IsString()) {
+ Local<String> worker_type = value->ToString(context).ToLocalChecked();
+ String::Utf8Value str(isolate, worker_type);
+ if (strcmp("string", *str) == 0) {
+ load_from_file = false;
+ } else if (strcmp("classic", *str) == 0) {
+ load_from_file = true;
+ } else {
+ Throw(args.GetIsolate(), "Unsupported worker type");
+ return;
+ }
+ }
+ }
+
+ Local<Value> source;
+ if (load_from_file) {
+ String::Utf8Value filename(args.GetIsolate(), args[0]);
+ source = ReadFile(args.GetIsolate(), *filename);
+ if (source.IsEmpty()) {
+ Throw(args.GetIsolate(), "Error loading worker script");
+ return;
+ }
+ } else {
+ source = args[0];
+ }
+
+ if (!args.IsConstructCall()) {
+ Throw(args.GetIsolate(), "Worker must be constructed with new");
+ return;
+ }
+
+ {
+ base::MutexGuard lock_guard(workers_mutex_.Pointer());
+ if (workers_.size() >= kMaxWorkers) {
+ Throw(args.GetIsolate(), "Too many workers, I won't let you create more");
+ return;
+ }
+
+ // Initialize the embedder field to nullptr; if we return early without
+ // creating a new Worker (because the main thread is terminating) we can
+ // early-out from the instance calls.
+ args.Holder()->SetAlignedPointerInInternalField(0, nullptr);
+
+ if (!allow_new_workers_) return;
+
+ Worker* worker = new Worker;
+ args.Holder()->SetAlignedPointerInInternalField(0, worker);
+ workers_.push_back(worker);
+
+ String::Utf8Value script(args.GetIsolate(), source);
+ if (!*script) {
+ Throw(args.GetIsolate(), "Can't get worker script");
+ return;
+ }
+ worker->StartExecuteInThread(*script);
+ }
+}
+
+void Shell::WorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+
+ if (args.Length() < 1) {
+ Throw(isolate, "Invalid argument");
+ return;
+ }
+
+ Worker* worker = GetWorkerFromInternalField(isolate, args.Holder());
+ if (!worker) {
+ return;
+ }
+
+ Local<Value> message = args[0];
+ Local<Value> transfer =
+ args.Length() >= 2 ? args[1] : Local<Value>::Cast(Undefined(isolate));
+ std::unique_ptr<SerializationData> data =
+ Shell::SerializeValue(isolate, message, transfer);
+ if (data) {
+ worker->PostMessage(std::move(data));
+ }
+}
+
+void Shell::WorkerGetMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+ Worker* worker = GetWorkerFromInternalField(isolate, args.Holder());
+ if (!worker) {
+ return;
+ }
+
+ std::unique_ptr<SerializationData> data = worker->GetMessage();
+ if (data) {
+ Local<Value> value;
+ if (Shell::DeserializeValue(isolate, std::move(data)).ToLocal(&value)) {
+ args.GetReturnValue().Set(value);
+ }
+ }
+}
+
+void Shell::WorkerTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+ Worker* worker = GetWorkerFromInternalField(isolate, args.Holder());
+ if (!worker) {
+ return;
+ }
+
+ worker->Terminate();
+}
+
+void Shell::QuitOnce(v8::FunctionCallbackInfo<v8::Value>* args) {
+ int exit_code = (*args)[0]
+ ->Int32Value(args->GetIsolate()->GetCurrentContext())
+ .FromMaybe(0);
+ CleanupWorkers();
+ args->GetIsolate()->Exit();
+ OnExit(args->GetIsolate());
+ base::OS::ExitProcess(exit_code);
+}
+
+void Shell::Quit(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ base::CallOnce(&quit_once_, &QuitOnce,
+ const_cast<v8::FunctionCallbackInfo<v8::Value>*>(&args));
+}
+
+void Shell::WaitUntilDone(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ SetWaitUntilDone(args.GetIsolate(), true);
+}
+
+void Shell::NotifyDone(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ SetWaitUntilDone(args.GetIsolate(), false);
+}
+
+void Shell::Version(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(String::NewFromUtf8(args.GetIsolate(),
+ V8::GetVersion(),
+ NewStringType::kNormal)
+ .ToLocalChecked());
+}
+
+void Shell::ReportException(Isolate* isolate, v8::TryCatch* try_catch) {
+ HandleScope handle_scope(isolate);
+ Local<Context> context = isolate->GetCurrentContext();
+ bool enter_context = context.IsEmpty();
+ if (enter_context) {
+ context = Local<Context>::New(isolate, evaluation_context_);
+ context->Enter();
+ }
+ // Converts a V8 value to a C string.
+ auto ToCString = [](const v8::String::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+ };
+
+ v8::String::Utf8Value exception(isolate, try_catch->Exception());
+ const char* exception_string = ToCString(exception);
+ Local<Message> message = try_catch->Message();
+ if (message.IsEmpty()) {
+ // V8 didn't provide any extra information about this error; just
+ // print the exception.
+ printf("%s\n", exception_string);
+ } else if (message->GetScriptOrigin().Options().IsWasm()) {
+ // Print wasm-function[(function index)]:(offset): (message).
+ int function_index = message->GetLineNumber(context).FromJust() - 1;
+ int offset = message->GetStartColumn(context).FromJust();
+ printf("wasm-function[%d]:%d: %s\n", function_index, offset,
+ exception_string);
+ } else {
+ // Print (filename):(line number): (message).
+ v8::String::Utf8Value filename(isolate,
+ message->GetScriptOrigin().ResourceName());
+ const char* filename_string = ToCString(filename);
+ int linenum = message->GetLineNumber(context).FromMaybe(-1);
+ printf("%s:%i: %s\n", filename_string, linenum, exception_string);
+ Local<String> sourceline;
+ if (message->GetSourceLine(context).ToLocal(&sourceline)) {
+ // Print line of source code.
+ v8::String::Utf8Value sourcelinevalue(isolate, sourceline);
+ const char* sourceline_string = ToCString(sourcelinevalue);
+ printf("%s\n", sourceline_string);
+ // Print wavy underline (GetUnderline is deprecated).
+ int start = message->GetStartColumn(context).FromJust();
+ for (int i = 0; i < start; i++) {
+ printf(" ");
+ }
+ int end = message->GetEndColumn(context).FromJust();
+ for (int i = start; i < end; i++) {
+ printf("^");
+ }
+ printf("\n");
+ }
+ }
+ Local<Value> stack_trace_string;
+ if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
+ stack_trace_string->IsString()) {
+ v8::String::Utf8Value stack_trace(isolate,
+ Local<String>::Cast(stack_trace_string));
+ printf("%s\n", ToCString(stack_trace));
+ }
+ printf("\n");
+ if (enter_context) context->Exit();
+}
+
+int32_t* Counter::Bind(const char* name, bool is_histogram) {
+ int i;
+ for (i = 0; i < kMaxNameSize - 1 && name[i]; i++)
+ name_[i] = static_cast<char>(name[i]);
+ name_[i] = '\0';
+ is_histogram_ = is_histogram;
+ return ptr();
+}
+
+void Counter::AddSample(int32_t sample) {
+ count_++;
+ sample_total_ += sample;
+}
+
+CounterCollection::CounterCollection() {
+ magic_number_ = 0xDEADFACE;
+ max_counters_ = kMaxCounters;
+ max_name_size_ = Counter::kMaxNameSize;
+ counters_in_use_ = 0;
+}
+
+Counter* CounterCollection::GetNextCounter() {
+ if (counters_in_use_ == kMaxCounters) return nullptr;
+ return &counters_[counters_in_use_++];
+}
+
+void Shell::MapCounters(v8::Isolate* isolate, const char* name) {
+ counters_file_ = base::OS::MemoryMappedFile::create(
+ name, sizeof(CounterCollection), &local_counters_);
+ void* memory =
+ (counters_file_ == nullptr) ? nullptr : counters_file_->memory();
+ if (memory == nullptr) {
+ printf("Could not map counters file %s\n", name);
+ base::OS::ExitProcess(1);
+ }
+ counters_ = static_cast<CounterCollection*>(memory);
+ isolate->SetCounterFunction(LookupCounter);
+ isolate->SetCreateHistogramFunction(CreateHistogram);
+ isolate->SetAddHistogramSampleFunction(AddHistogramSample);
+}
+
+Counter* Shell::GetCounter(const char* name, bool is_histogram) {
+ auto map_entry = counter_map_->find(name);
+ Counter* counter =
+ map_entry != counter_map_->end() ? map_entry->second : nullptr;
+
+ if (counter == nullptr) {
+ counter = counters_->GetNextCounter();
+ if (counter != nullptr) {
+ (*counter_map_)[name] = counter;
+ counter->Bind(name, is_histogram);
+ }
+ } else {
+ DCHECK(counter->is_histogram() == is_histogram);
+ }
+ return counter;
+}
+
+int* Shell::LookupCounter(const char* name) {
+ Counter* counter = GetCounter(name, false);
+
+ if (counter != nullptr) {
+ return counter->ptr();
+ } else {
+ return nullptr;
+ }
+}
+
+void* Shell::CreateHistogram(const char* name, int min, int max,
+ size_t buckets) {
+ return GetCounter(name, true);
+}
+
+void Shell::AddHistogramSample(void* histogram, int sample) {
+ Counter* counter = reinterpret_cast<Counter*>(histogram);
+ counter->AddSample(sample);
+}
+
+// Turn a value into a human-readable string.
+Local<String> Shell::Stringify(Isolate* isolate, Local<Value> value) {
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate, evaluation_context_);
+ if (stringify_function_.IsEmpty()) {
+ Local<String> source =
+ String::NewFromUtf8(isolate, stringify_source_, NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> name =
+ String::NewFromUtf8(isolate, "d8-stringify", NewStringType::kNormal)
+ .ToLocalChecked();
+ ScriptOrigin origin(name);
+ Local<Script> script =
+ Script::Compile(context, source, &origin).ToLocalChecked();
+ stringify_function_.Reset(
+ isolate, script->Run(context).ToLocalChecked().As<Function>());
+ }
+ Local<Function> fun = Local<Function>::New(isolate, stringify_function_);
+ Local<Value> argv[1] = {value};
+ v8::TryCatch try_catch(isolate);
+ MaybeLocal<Value> result = fun->Call(context, Undefined(isolate), 1, argv);
+ if (result.IsEmpty()) return String::Empty(isolate);
+ return result.ToLocalChecked().As<String>();
+}
+
+Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
+ Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
+ global_template->Set(
+ String::NewFromUtf8(isolate, "print", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Print));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "printErr", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, PrintErr));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "write", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Write));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "read", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Read));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "readbuffer", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, ReadBuffer));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "readline", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, ReadLine));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "load", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Load));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "setTimeout", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, SetTimeout));
+ // Some Emscripten-generated code tries to call 'quit', which in turn would
+ // call C's exit(). This would lead to memory leaks, because there is no way
+ // we can terminate cleanly then, so we need a way to hide 'quit'.
+ if (!options.omit_quit) {
+ global_template->Set(
+ String::NewFromUtf8(isolate, "quit", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Quit));
+ }
+ Local<ObjectTemplate> test_template = ObjectTemplate::New(isolate);
+ global_template->Set(
+ String::NewFromUtf8(isolate, "testRunner", NewStringType::kNormal)
+ .ToLocalChecked(),
+ test_template);
+ test_template->Set(
+ String::NewFromUtf8(isolate, "notifyDone", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, NotifyDone));
+ test_template->Set(
+ String::NewFromUtf8(isolate, "waitUntilDone", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, WaitUntilDone));
+ // Reliable access to quit functionality. The "quit" method function
+ // installed on the global object can be hidden with the --omit-quit flag
+ // (e.g. on asan bots).
+ test_template->Set(
+ String::NewFromUtf8(isolate, "quit", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Quit));
+
+ global_template->Set(
+ String::NewFromUtf8(isolate, "version", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, Version));
+ global_template->Set(
+ Symbol::GetToStringTag(isolate),
+ String::NewFromUtf8(isolate, "global", NewStringType::kNormal)
+ .ToLocalChecked());
+
+ // Bind the Realm object.
+ Local<ObjectTemplate> realm_template = ObjectTemplate::New(isolate);
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "current", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmCurrent));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "owner", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmOwner));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "global", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmGlobal));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "create", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmCreate));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "createAllowCrossRealmAccess",
+ NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmCreateAllowCrossRealmAccess));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "navigate", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmNavigate));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "detachGlobal", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmDetachGlobal));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "dispose", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmDispose));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "switch", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmSwitch));
+ realm_template->Set(
+ String::NewFromUtf8(isolate, "eval", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, RealmEval));
+ realm_template->SetAccessor(
+ String::NewFromUtf8(isolate, "shared", NewStringType::kNormal)
+ .ToLocalChecked(),
+ RealmSharedGet, RealmSharedSet);
+ global_template->Set(
+ String::NewFromUtf8(isolate, "Realm", NewStringType::kNormal)
+ .ToLocalChecked(),
+ realm_template);
+
+ Local<ObjectTemplate> performance_template = ObjectTemplate::New(isolate);
+ performance_template->Set(
+ String::NewFromUtf8(isolate, "now", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, PerformanceNow));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "performance", NewStringType::kNormal)
+ .ToLocalChecked(),
+ performance_template);
+
+ Local<FunctionTemplate> worker_fun_template =
+ FunctionTemplate::New(isolate, WorkerNew);
+ Local<Signature> worker_signature =
+ Signature::New(isolate, worker_fun_template);
+ worker_fun_template->SetClassName(
+ String::NewFromUtf8(isolate, "Worker", NewStringType::kNormal)
+ .ToLocalChecked());
+ worker_fun_template->ReadOnlyPrototype();
+ worker_fun_template->PrototypeTemplate()->Set(
+ String::NewFromUtf8(isolate, "terminate", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, WorkerTerminate, Local<Value>(),
+ worker_signature));
+ worker_fun_template->PrototypeTemplate()->Set(
+ String::NewFromUtf8(isolate, "postMessage", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, WorkerPostMessage, Local<Value>(),
+ worker_signature));
+ worker_fun_template->PrototypeTemplate()->Set(
+ String::NewFromUtf8(isolate, "getMessage", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, WorkerGetMessage, Local<Value>(),
+ worker_signature));
+ worker_fun_template->InstanceTemplate()->SetInternalFieldCount(1);
+ global_template->Set(
+ String::NewFromUtf8(isolate, "Worker", NewStringType::kNormal)
+ .ToLocalChecked(),
+ worker_fun_template);
+
+ Local<ObjectTemplate> os_templ = ObjectTemplate::New(isolate);
+ AddOSMethods(isolate, os_templ);
+ global_template->Set(
+ String::NewFromUtf8(isolate, "os", NewStringType::kNormal)
+ .ToLocalChecked(),
+ os_templ);
+
+ if (i::FLAG_expose_async_hooks) {
+ Local<ObjectTemplate> async_hooks_templ = ObjectTemplate::New(isolate);
+ async_hooks_templ->Set(
+ String::NewFromUtf8(isolate, "createHook", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, AsyncHooksCreateHook));
+ async_hooks_templ->Set(
+ String::NewFromUtf8(isolate, "executionAsyncId", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, AsyncHooksExecutionAsyncId));
+ async_hooks_templ->Set(
+ String::NewFromUtf8(isolate, "triggerAsyncId", NewStringType::kNormal)
+ .ToLocalChecked(),
+ FunctionTemplate::New(isolate, AsyncHooksTriggerAsyncId));
+ global_template->Set(
+ String::NewFromUtf8(isolate, "async_hooks", NewStringType::kNormal)
+ .ToLocalChecked(),
+ async_hooks_templ);
+ }
+
+ return global_template;
+}
+
+static void PrintNonErrorsMessageCallback(Local<Message> message,
+ Local<Value> error) {
+ // Nothing to do here for errors, exceptions thrown up to the shell will be
+ // reported
+ // separately by {Shell::ReportException} after they are caught.
+ // Do print other kinds of messages.
+ switch (message->ErrorLevel()) {
+ case v8::Isolate::kMessageWarning:
+ case v8::Isolate::kMessageLog:
+ case v8::Isolate::kMessageInfo:
+ case v8::Isolate::kMessageDebug: {
+ break;
+ }
+
+ case v8::Isolate::kMessageError: {
+ // Ignore errors, printed elsewhere.
+ return;
+ }
+
+ default: {
+ UNREACHABLE();
+ }
+ }
+ // Converts a V8 value to a C string.
+ auto ToCString = [](const v8::String::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+ };
+ Isolate* isolate = Isolate::GetCurrent();
+ v8::String::Utf8Value msg(isolate, message->Get());
+ const char* msg_string = ToCString(msg);
+ // Print (filename):(line number): (message).
+ v8::String::Utf8Value filename(isolate,
+ message->GetScriptOrigin().ResourceName());
+ const char* filename_string = ToCString(filename);
+ Maybe<int> maybeline = message->GetLineNumber(isolate->GetCurrentContext());
+ int linenum = maybeline.IsJust() ? maybeline.FromJust() : -1;
+ printf("%s:%i: %s\n", filename_string, linenum, msg_string);
+}
+
+void Shell::Initialize(Isolate* isolate) {
+ // Set up counters
+ if (i::FLAG_map_counters[0] != '\0') {
+ MapCounters(isolate, i::FLAG_map_counters);
+ }
+ // Disable default message reporting.
+ isolate->AddMessageListenerWithErrorLevel(
+ PrintNonErrorsMessageCallback,
+ v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
+ v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
+ v8::Isolate::kMessageLog);
+}
+
+Local<Context> Shell::CreateEvaluationContext(Isolate* isolate) {
+ // This needs to be a critical section since this is not thread-safe
+ base::MutexGuard lock_guard(context_mutex_.Pointer());
+ // Initialize the global objects
+ Local<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
+ EscapableHandleScope handle_scope(isolate);
+ Local<Context> context = Context::New(isolate, nullptr, global_template);
+ DCHECK(!context.IsEmpty());
+ InitializeModuleEmbedderData(context);
+ if (options.include_arguments) {
+ Context::Scope scope(context);
+ const std::vector<const char*>& args = options.arguments;
+ int size = static_cast<int>(args.size());
+ Local<Array> array = Array::New(isolate, size);
+ for (int i = 0; i < size; i++) {
+ Local<String> arg =
+ v8::String::NewFromUtf8(isolate, args[i], v8::NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<Number> index = v8::Number::New(isolate, i);
+ array->Set(context, index, arg).FromJust();
+ }
+ Local<String> name =
+ String::NewFromUtf8(isolate, "arguments", NewStringType::kInternalized)
+ .ToLocalChecked();
+ context->Global()->Set(context, name, array).FromJust();
+ }
+ return handle_scope.Escape(context);
+}
+
+void Shell::WriteIgnitionDispatchCountersFile(v8::Isolate* isolate) {
+ HandleScope handle_scope(isolate);
+ Local<Context> context = Context::New(isolate);
+ Context::Scope context_scope(context);
+
+ Local<Object> dispatch_counters = reinterpret_cast<i::Isolate*>(isolate)
+ ->interpreter()
+ ->GetDispatchCountersObject();
+ std::ofstream dispatch_counters_stream(
+ i::FLAG_trace_ignition_dispatches_output_file);
+ dispatch_counters_stream << *String::Utf8Value(
+ isolate, JSON::Stringify(context, dispatch_counters).ToLocalChecked());
+}
+
+namespace {
+int LineFromOffset(Local<debug::Script> script, int offset) {
+ debug::Location location = script->GetSourceLocation(offset);
+ return location.GetLineNumber();
+}
+
+void WriteLcovDataForRange(std::vector<uint32_t>& lines, int start_line,
+ int end_line, uint32_t count) {
+ // Ensure space in the array.
+ lines.resize(std::max(static_cast<size_t>(end_line + 1), lines.size()), 0);
+ // Boundary lines could be shared between two functions with different
+ // invocation counts. Take the maximum.
+ lines[start_line] = std::max(lines[start_line], count);
+ lines[end_line] = std::max(lines[end_line], count);
+ // Invocation counts for non-boundary lines are overwritten.
+ for (int k = start_line + 1; k < end_line; k++) lines[k] = count;
+}
+
+void WriteLcovDataForNamedRange(std::ostream& sink,
+ std::vector<uint32_t>& lines,
+ const std::string& name, int start_line,
+ int end_line, uint32_t count) {
+ WriteLcovDataForRange(lines, start_line, end_line, count);
+ sink << "FN:" << start_line + 1 << "," << name << std::endl;
+ sink << "FNDA:" << count << "," << name << std::endl;
+}
+} // namespace
+
+// Write coverage data in LCOV format. See man page for geninfo(1).
+void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) {
+ if (!file) return;
+ HandleScope handle_scope(isolate);
+ debug::Coverage coverage = debug::Coverage::CollectPrecise(isolate);
+ std::ofstream sink(file, std::ofstream::app);
+ for (size_t i = 0; i < coverage.ScriptCount(); i++) {
+ debug::Coverage::ScriptData script_data = coverage.GetScriptData(i);
+ Local<debug::Script> script = script_data.GetScript();
+ // Skip unnamed scripts.
+ Local<String> name;
+ if (!script->Name().ToLocal(&name)) continue;
+ std::string file_name = ToSTLString(isolate, name);
+ // Skip scripts not backed by a file.
+ if (!std::ifstream(file_name).good()) continue;
+ sink << "SF:";
+ sink << NormalizePath(file_name, GetWorkingDirectory()) << std::endl;
+ std::vector<uint32_t> lines;
+ for (size_t j = 0; j < script_data.FunctionCount(); j++) {
+ debug::Coverage::FunctionData function_data =
+ script_data.GetFunctionData(j);
+
+ // Write function stats.
+ {
+ debug::Location start =
+ script->GetSourceLocation(function_data.StartOffset());
+ debug::Location end =
+ script->GetSourceLocation(function_data.EndOffset());
+ int start_line = start.GetLineNumber();
+ int end_line = end.GetLineNumber();
+ uint32_t count = function_data.Count();
+
+ Local<String> name;
+ std::stringstream name_stream;
+ if (function_data.Name().ToLocal(&name)) {
+ name_stream << ToSTLString(isolate, name);
+ } else {
+ name_stream << "<" << start_line + 1 << "-";
+ name_stream << start.GetColumnNumber() << ">";
+ }
+
+ WriteLcovDataForNamedRange(sink, lines, name_stream.str(), start_line,
+ end_line, count);
+ }
+
+ // Process inner blocks.
+ for (size_t k = 0; k < function_data.BlockCount(); k++) {
+ debug::Coverage::BlockData block_data = function_data.GetBlockData(k);
+ int start_line = LineFromOffset(script, block_data.StartOffset());
+ int end_line = LineFromOffset(script, block_data.EndOffset() - 1);
+ uint32_t count = block_data.Count();
+ WriteLcovDataForRange(lines, start_line, end_line, count);
+ }
+ }
+ // Write per-line coverage. LCOV uses 1-based line numbers.
+ for (size_t i = 0; i < lines.size(); i++) {
+ sink << "DA:" << (i + 1) << "," << lines[i] << std::endl;
+ }
+ sink << "end_of_record" << std::endl;
+ }
+}
+
+void Shell::OnExit(v8::Isolate* isolate) {
+ // Dump basic block profiling data.
+ if (i::FLAG_turbo_profiling) {
+ i::BasicBlockProfiler* profiler = i::BasicBlockProfiler::Get();
+ i::StdoutStream{} << *profiler;
+ }
+ isolate->Dispose();
+
+ if (i::FLAG_dump_counters || i::FLAG_dump_counters_nvp) {
+ std::vector<std::pair<std::string, Counter*>> counters(
+ counter_map_->begin(), counter_map_->end());
+ std::sort(counters.begin(), counters.end());
+
+ if (i::FLAG_dump_counters_nvp) {
+ // Dump counters as name-value pairs.
+ for (auto pair : counters) {
+ std::string key = pair.first;
+ Counter* counter = pair.second;
+ if (counter->is_histogram()) {
+ std::cout << "\"c:" << key << "\"=" << counter->count() << "\n";
+ std::cout << "\"t:" << key << "\"=" << counter->sample_total()
+ << "\n";
+ } else {
+ std::cout << "\"" << key << "\"=" << counter->count() << "\n";
+ }
+ }
+ } else {
+ // Dump counters in formatted boxes.
+ constexpr int kNameBoxSize = 64;
+ constexpr int kValueBoxSize = 13;
+ std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
+ << std::string(kValueBoxSize, '-') << "+\n";
+ std::cout << "| Name" << std::string(kNameBoxSize - 5, ' ') << "| Value"
+ << std::string(kValueBoxSize - 6, ' ') << "|\n";
+ std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
+ << std::string(kValueBoxSize, '-') << "+\n";
+ for (auto pair : counters) {
+ std::string key = pair.first;
+ Counter* counter = pair.second;
+ if (counter->is_histogram()) {
+ std::cout << "| c:" << std::setw(kNameBoxSize - 4) << std::left << key
+ << " | " << std::setw(kValueBoxSize - 2) << std::right
+ << counter->count() << " |\n";
+ std::cout << "| t:" << std::setw(kNameBoxSize - 4) << std::left << key
+ << " | " << std::setw(kValueBoxSize - 2) << std::right
+ << counter->sample_total() << " |\n";
+ } else {
+ std::cout << "| " << std::setw(kNameBoxSize - 2) << std::left << key
+ << " | " << std::setw(kValueBoxSize - 2) << std::right
+ << counter->count() << " |\n";
+ }
+ }
+ std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
+ << std::string(kValueBoxSize, '-') << "+\n";
+ }
+ }
+
+ delete counters_file_;
+ delete counter_map_;
+}
+
+static FILE* FOpen(const char* path, const char* mode) {
+#if defined(_MSC_VER) && (defined(_WIN32) || defined(_WIN64))
+ FILE* result;
+ if (fopen_s(&result, path, mode) == 0) {
+ return result;
+ } else {
+ return nullptr;
+ }
+#else
+ FILE* file = fopen(path, mode);
+ if (file == nullptr) return nullptr;
+ struct stat file_stat;
+ if (fstat(fileno(file), &file_stat) != 0) return nullptr;
+ bool is_regular_file = ((file_stat.st_mode & S_IFREG) != 0);
+ if (is_regular_file) return file;
+ fclose(file);
+ return nullptr;
+#endif
+}
+
+static char* ReadChars(const char* name, int* size_out) {
+ if (Shell::options.read_from_tcp_port >= 0) {
+ return Shell::ReadCharsFromTcpPort(name, size_out);
+ }
+
+ FILE* file = FOpen(name, "rb");
+ if (file == nullptr) return nullptr;
+
+ fseek(file, 0, SEEK_END);
+ size_t size = ftell(file);
+ rewind(file);
+
+ char* chars = new char[size + 1];
+ chars[size] = '\0';
+ for (size_t i = 0; i < size;) {
+ i += fread(&chars[i], 1, size - i, file);
+ if (ferror(file)) {
+ fclose(file);
+ delete[] chars;
+ return nullptr;
+ }
+ }
+ fclose(file);
+ *size_out = static_cast<int>(size);
+ return chars;
+}
+
+struct DataAndPersistent {
+ uint8_t* data;
+ int byte_length;
+ Global<ArrayBuffer> handle;
+};
+
+static void ReadBufferWeakCallback(
+ const v8::WeakCallbackInfo<DataAndPersistent>& data) {
+ int byte_length = data.GetParameter()->byte_length;
+ data.GetIsolate()->AdjustAmountOfExternalAllocatedMemory(
+ -static_cast<intptr_t>(byte_length));
+
+ delete[] data.GetParameter()->data;
+ data.GetParameter()->handle.Reset();
+ delete data.GetParameter();
+}
+
+void Shell::ReadBuffer(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ static_assert(sizeof(char) == sizeof(uint8_t),
+ "char and uint8_t should both have 1 byte");
+ Isolate* isolate = args.GetIsolate();
+ String::Utf8Value filename(isolate, args[0]);
+ int length;
+ if (*filename == nullptr) {
+ Throw(isolate, "Error loading file");
+ return;
+ }
+
+ DataAndPersistent* data = new DataAndPersistent;
+ data->data = reinterpret_cast<uint8_t*>(ReadChars(*filename, &length));
+ if (data->data == nullptr) {
+ delete data;
+ Throw(isolate, "Error reading file");
+ return;
+ }
+ data->byte_length = length;
+ Local<v8::ArrayBuffer> buffer = ArrayBuffer::New(isolate, data->data, length);
+ data->handle.Reset(isolate, buffer);
+ data->handle.SetWeak(data, ReadBufferWeakCallback,
+ v8::WeakCallbackType::kParameter);
+ isolate->AdjustAmountOfExternalAllocatedMemory(length);
+
+ args.GetReturnValue().Set(buffer);
+}
+
+// Reads a file into a v8 string.
+Local<String> Shell::ReadFile(Isolate* isolate, const char* name) {
+ std::unique_ptr<base::OS::MemoryMappedFile> file(
+ base::OS::MemoryMappedFile::open(
+ name, base::OS::MemoryMappedFile::FileMode::kReadOnly));
+ if (!file) return Local<String>();
+
+ int size = static_cast<int>(file->size());
+ char* chars = static_cast<char*>(file->memory());
+ Local<String> result;
+ if (i::FLAG_use_external_strings && i::String::IsAscii(chars, size)) {
+ String::ExternalOneByteStringResource* resource =
+ new ExternalOwningOneByteStringResource(std::move(file));
+ result = String::NewExternalOneByte(isolate, resource).ToLocalChecked();
+ } else {
+ result = String::NewFromUtf8(isolate, chars, NewStringType::kNormal, size)
+ .ToLocalChecked();
+ }
+ return result;
+}
+
+void Shell::RunShell(Isolate* isolate) {
+ HandleScope outer_scope(isolate);
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate, evaluation_context_);
+ v8::Context::Scope context_scope(context);
+ PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
+ Local<String> name =
+ String::NewFromUtf8(isolate, "(d8)", NewStringType::kNormal)
+ .ToLocalChecked();
+ printf("V8 version %s\n", V8::GetVersion());
+ while (true) {
+ HandleScope inner_scope(isolate);
+ printf("d8> ");
+ Local<String> input = Shell::ReadFromStdin(isolate);
+ if (input.IsEmpty()) break;
+ ExecuteString(isolate, input, name, kPrintResult, kReportExceptions,
+ kProcessMessageQueue);
+ }
+ printf("\n");
+ // We need to explicitly clean up the module embedder data for
+ // the interative shell context.
+ DisposeModuleEmbedderData(context);
+}
+
+class InspectorFrontend final : public v8_inspector::V8Inspector::Channel {
+ public:
+ explicit InspectorFrontend(Local<Context> context) {
+ isolate_ = context->GetIsolate();
+ context_.Reset(isolate_, context);
+ }
+ ~InspectorFrontend() override = default;
+
+ private:
+ void sendResponse(
+ int callId,
+ std::unique_ptr<v8_inspector::StringBuffer> message) override {
+ Send(message->string());
+ }
+ void sendNotification(
+ std::unique_ptr<v8_inspector::StringBuffer> message) override {
+ Send(message->string());
+ }
+ void flushProtocolNotifications() override {}
+
+ void Send(const v8_inspector::StringView& string) {
+ v8::Isolate::AllowJavascriptExecutionScope allow_script(isolate_);
+ v8::HandleScope handle_scope(isolate_);
+ int length = static_cast<int>(string.length());
+ DCHECK_LT(length, v8::String::kMaxLength);
+ Local<String> message =
+ (string.is8Bit()
+ ? v8::String::NewFromOneByte(
+ isolate_,
+ reinterpret_cast<const uint8_t*>(string.characters8()),
+ v8::NewStringType::kNormal, length)
+ : v8::String::NewFromTwoByte(
+ isolate_,
+ reinterpret_cast<const uint16_t*>(string.characters16()),
+ v8::NewStringType::kNormal, length))
+ .ToLocalChecked();
+ Local<String> callback_name =
+ v8::String::NewFromUtf8(isolate_, "receive", v8::NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<Context> context = context_.Get(isolate_);
+ Local<Value> callback =
+ context->Global()->Get(context, callback_name).ToLocalChecked();
+ if (callback->IsFunction()) {
+ v8::TryCatch try_catch(isolate_);
+ Local<Value> args[] = {message};
+ USE(Local<Function>::Cast(callback)->Call(context, Undefined(isolate_), 1,
+ args));
+#ifdef DEBUG
+ if (try_catch.HasCaught()) {
+ Local<Object> exception = Local<Object>::Cast(try_catch.Exception());
+ Local<String> key = v8::String::NewFromUtf8(isolate_, "message",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> expected =
+ v8::String::NewFromUtf8(isolate_,
+ "Maximum call stack size exceeded",
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<Value> value = exception->Get(context, key).ToLocalChecked();
+ DCHECK(value->StrictEquals(expected));
+ }
+#endif
+ }
+ }
+
+ Isolate* isolate_;
+ Global<Context> context_;
+};
+
+class InspectorClient : public v8_inspector::V8InspectorClient {
+ public:
+ InspectorClient(Local<Context> context, bool connect) {
+ if (!connect) return;
+ isolate_ = context->GetIsolate();
+ channel_.reset(new InspectorFrontend(context));
+ inspector_ = v8_inspector::V8Inspector::create(isolate_, this);
+ session_ =
+ inspector_->connect(1, channel_.get(), v8_inspector::StringView());
+ context->SetAlignedPointerInEmbedderData(kInspectorClientIndex, this);
+ inspector_->contextCreated(v8_inspector::V8ContextInfo(
+ context, kContextGroupId, v8_inspector::StringView()));
+
+ Local<Value> function =
+ FunctionTemplate::New(isolate_, SendInspectorMessage)
+ ->GetFunction(context)
+ .ToLocalChecked();
+ Local<String> function_name =
+ String::NewFromUtf8(isolate_, "send", NewStringType::kNormal)
+ .ToLocalChecked();
+ CHECK(context->Global()->Set(context, function_name, function).FromJust());
+
+ context_.Reset(isolate_, context);
+ }
+
+ private:
+ static v8_inspector::V8InspectorSession* GetSession(Local<Context> context) {
+ InspectorClient* inspector_client = static_cast<InspectorClient*>(
+ context->GetAlignedPointerFromEmbedderData(kInspectorClientIndex));
+ return inspector_client->session_.get();
+ }
+
+ Local<Context> ensureDefaultContextInGroup(int group_id) override {
+ DCHECK(isolate_);
+ DCHECK_EQ(kContextGroupId, group_id);
+ return context_.Get(isolate_);
+ }
+
+ static void SendInspectorMessage(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ Local<Context> context = isolate->GetCurrentContext();
+ args.GetReturnValue().Set(Undefined(isolate));
+ Local<String> message = args[0]->ToString(context).ToLocalChecked();
+ v8_inspector::V8InspectorSession* session =
+ InspectorClient::GetSession(context);
+ int length = message->Length();
+ std::unique_ptr<uint16_t[]> buffer(new uint16_t[length]);
+ message->Write(isolate, buffer.get(), 0, length);
+ v8_inspector::StringView message_view(buffer.get(), length);
+ {
+ v8::SealHandleScope seal_handle_scope(isolate);
+ session->dispatchProtocolMessage(message_view);
+ }
+ args.GetReturnValue().Set(True(isolate));
+ }
+
+ static const int kContextGroupId = 1;
+
+ std::unique_ptr<v8_inspector::V8Inspector> inspector_;
+ std::unique_ptr<v8_inspector::V8InspectorSession> session_;
+ std::unique_ptr<v8_inspector::V8Inspector::Channel> channel_;
+ Global<Context> context_;
+ Isolate* isolate_;
+};
+
+SourceGroup::~SourceGroup() {
+ delete thread_;
+ thread_ = nullptr;
+}
+
+bool ends_with(const char* input, const char* suffix) {
+ size_t input_length = strlen(input);
+ size_t suffix_length = strlen(suffix);
+ if (suffix_length <= input_length) {
+ return strcmp(input + input_length - suffix_length, suffix) == 0;
+ }
+ return false;
+}
+
+bool SourceGroup::Execute(Isolate* isolate) {
+ bool success = true;
+ for (int i = begin_offset_; i < end_offset_; ++i) {
+ const char* arg = argv_[i];
+ if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
+ // Execute argument given to -e option directly.
+ HandleScope handle_scope(isolate);
+ Local<String> file_name =
+ String::NewFromUtf8(isolate, "unnamed", NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> source =
+ String::NewFromUtf8(isolate, argv_[i + 1], NewStringType::kNormal)
+ .ToLocalChecked();
+ Shell::set_script_executed();
+ if (!Shell::ExecuteString(isolate, source, file_name,
+ Shell::kNoPrintResult, Shell::kReportExceptions,
+ Shell::kNoProcessMessageQueue)) {
+ success = false;
+ break;
+ }
+ ++i;
+ continue;
+ } else if (ends_with(arg, ".mjs")) {
+ Shell::set_script_executed();
+ if (!Shell::ExecuteModule(isolate, arg)) {
+ success = false;
+ break;
+ }
+ continue;
+ } else if (strcmp(arg, "--module") == 0 && i + 1 < end_offset_) {
+ // Treat the next file as a module.
+ arg = argv_[++i];
+ Shell::set_script_executed();
+ if (!Shell::ExecuteModule(isolate, arg)) {
+ success = false;
+ break;
+ }
+ continue;
+ } else if (arg[0] == '-') {
+ // Ignore other options. They have been parsed already.
+ continue;
+ }
+
+ // Use all other arguments as names of files to load and run.
+ HandleScope handle_scope(isolate);
+ Local<String> file_name =
+ String::NewFromUtf8(isolate, arg, NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> source = ReadFile(isolate, arg);
+ if (source.IsEmpty()) {
+ printf("Error reading '%s'\n", arg);
+ base::OS::ExitProcess(1);
+ }
+ Shell::set_script_executed();
+ if (!Shell::ExecuteString(isolate, source, file_name, Shell::kNoPrintResult,
+ Shell::kReportExceptions,
+ Shell::kProcessMessageQueue)) {
+ success = false;
+ break;
+ }
+ }
+ return success;
+}
+
+Local<String> SourceGroup::ReadFile(Isolate* isolate, const char* name) {
+ return Shell::ReadFile(isolate, name);
+}
+
+SourceGroup::IsolateThread::IsolateThread(SourceGroup* group)
+ : base::Thread(GetThreadOptions("IsolateThread")), group_(group) {}
+
+void SourceGroup::ExecuteInThread() {
+ Isolate::CreateParams create_params;
+ create_params.array_buffer_allocator = Shell::array_buffer_allocator;
+ Isolate* isolate = Isolate::New(create_params);
+ isolate->SetHostImportModuleDynamicallyCallback(
+ Shell::HostImportModuleDynamically);
+ isolate->SetHostInitializeImportMetaObjectCallback(
+ Shell::HostInitializeImportMetaObject);
+ Shell::SetWaitUntilDone(isolate, false);
+ D8Console console(isolate);
+ debug::SetConsoleDelegate(isolate, &console);
+ for (int i = 0; i < Shell::options.stress_runs; ++i) {
+ next_semaphore_.Wait();
+ {
+ Isolate::Scope iscope(isolate);
+ PerIsolateData data(isolate);
+ {
+ HandleScope scope(isolate);
+ Local<Context> context = Shell::CreateEvaluationContext(isolate);
+ {
+ Context::Scope cscope(context);
+ InspectorClient inspector_client(context,
+ Shell::options.enable_inspector);
+ PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
+ Execute(isolate);
+ Shell::CompleteMessageLoop(isolate);
+ }
+ DisposeModuleEmbedderData(context);
+ }
+ Shell::CollectGarbage(isolate);
+ }
+ done_semaphore_.Signal();
+ }
+
+ isolate->Dispose();
+}
+
+void SourceGroup::StartExecuteInThread() {
+ if (thread_ == nullptr) {
+ thread_ = new IsolateThread(this);
+ thread_->Start();
+ }
+ next_semaphore_.Signal();
+}
+
+void SourceGroup::WaitForThread() {
+ if (thread_ == nullptr) return;
+ done_semaphore_.Wait();
+}
+
+void SourceGroup::JoinThread() {
+ if (thread_ == nullptr) return;
+ thread_->Join();
+}
+
+ExternalizedContents::~ExternalizedContents() {
+ if (data_ != nullptr) {
+ deleter_(data_, length_, deleter_data_);
+ }
+}
+
+void SerializationDataQueue::Enqueue(std::unique_ptr<SerializationData> data) {
+ base::MutexGuard lock_guard(&mutex_);
+ data_.push_back(std::move(data));
+}
+
+bool SerializationDataQueue::Dequeue(
+ std::unique_ptr<SerializationData>* out_data) {
+ out_data->reset();
+ base::MutexGuard lock_guard(&mutex_);
+ if (data_.empty()) return false;
+ *out_data = std::move(data_[0]);
+ data_.erase(data_.begin());
+ return true;
+}
+
+bool SerializationDataQueue::IsEmpty() {
+ base::MutexGuard lock_guard(&mutex_);
+ return data_.empty();
+}
+
+void SerializationDataQueue::Clear() {
+ base::MutexGuard lock_guard(&mutex_);
+ data_.clear();
+}
+
+Worker::Worker()
+ : in_semaphore_(0),
+ out_semaphore_(0),
+ thread_(nullptr),
+ script_(nullptr),
+ running_(false) {}
+
+Worker::~Worker() {
+ delete thread_;
+ thread_ = nullptr;
+ delete[] script_;
+ script_ = nullptr;
+ in_queue_.Clear();
+ out_queue_.Clear();
+}
+
+void Worker::StartExecuteInThread(const char* script) {
+ running_ = true;
+ script_ = i::StrDup(script);
+ thread_ = new WorkerThread(this);
+ thread_->Start();
+}
+
+void Worker::PostMessage(std::unique_ptr<SerializationData> data) {
+ in_queue_.Enqueue(std::move(data));
+ in_semaphore_.Signal();
+}
+
+std::unique_ptr<SerializationData> Worker::GetMessage() {
+ std::unique_ptr<SerializationData> result;
+ while (!out_queue_.Dequeue(&result)) {
+ // If the worker is no longer running, and there are no messages in the
+ // queue, don't expect any more messages from it.
+ if (!base::Relaxed_Load(&running_)) break;
+ out_semaphore_.Wait();
+ }
+ return result;
+}
+
+void Worker::Terminate() {
+ base::Relaxed_Store(&running_, false);
+ // Post nullptr to wake the Worker thread message loop, and tell it to stop
+ // running.
+ PostMessage(nullptr);
+}
+
+void Worker::WaitForThread() {
+ Terminate();
+ thread_->Join();
+}
+
+void Worker::ExecuteInThread() {
+ Isolate::CreateParams create_params;
+ create_params.array_buffer_allocator = Shell::array_buffer_allocator;
+ Isolate* isolate = Isolate::New(create_params);
+ isolate->SetHostImportModuleDynamicallyCallback(
+ Shell::HostImportModuleDynamically);
+ isolate->SetHostInitializeImportMetaObjectCallback(
+ Shell::HostInitializeImportMetaObject);
+ D8Console console(isolate);
+ debug::SetConsoleDelegate(isolate, &console);
+ {
+ Isolate::Scope iscope(isolate);
+ {
+ HandleScope scope(isolate);
+ PerIsolateData data(isolate);
+ Local<Context> context = Shell::CreateEvaluationContext(isolate);
+ {
+ Context::Scope cscope(context);
+ PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
+
+ Local<Object> global = context->Global();
+ Local<Value> this_value = External::New(isolate, this);
+ Local<FunctionTemplate> postmessage_fun_template =
+ FunctionTemplate::New(isolate, PostMessageOut, this_value);
+
+ Local<Function> postmessage_fun;
+ if (postmessage_fun_template->GetFunction(context).ToLocal(
+ &postmessage_fun)) {
+ global
+ ->Set(context,
+ String::NewFromUtf8(isolate, "postMessage",
+ NewStringType::kNormal)
+ .ToLocalChecked(),
+ postmessage_fun)
+ .FromJust();
+ }
+
+ // First run the script
+ Local<String> file_name =
+ String::NewFromUtf8(isolate, "unnamed", NewStringType::kNormal)
+ .ToLocalChecked();
+ Local<String> source =
+ String::NewFromUtf8(isolate, script_, NewStringType::kNormal)
+ .ToLocalChecked();
+ if (Shell::ExecuteString(
+ isolate, source, file_name, Shell::kNoPrintResult,
+ Shell::kReportExceptions, Shell::kProcessMessageQueue)) {
+ // Get the message handler
+ Local<Value> onmessage =
+ global
+ ->Get(context, String::NewFromUtf8(isolate, "onmessage",
+ NewStringType::kNormal)
+ .ToLocalChecked())
+ .ToLocalChecked();
+ if (onmessage->IsFunction()) {
+ Local<Function> onmessage_fun = Local<Function>::Cast(onmessage);
+ // Now wait for messages
+ while (true) {
+ in_semaphore_.Wait();
+ std::unique_ptr<SerializationData> data;
+ if (!in_queue_.Dequeue(&data)) continue;
+ if (!data) {
+ break;
+ }
+ v8::TryCatch try_catch(isolate);
+ Local<Value> value;
+ if (Shell::DeserializeValue(isolate, std::move(data))
+ .ToLocal(&value)) {
+ Local<Value> argv[] = {value};
+ MaybeLocal<Value> result =
+ onmessage_fun->Call(context, global, 1, argv);
+ USE(result);
+ }
+ if (try_catch.HasCaught()) {
+ Shell::ReportException(isolate, &try_catch);
+ }
+ }
+ }
+ }
+ }
+ DisposeModuleEmbedderData(context);
+ }
+ Shell::CollectGarbage(isolate);
+ }
+ isolate->Dispose();
+
+ // Post nullptr to wake the thread waiting on GetMessage() if there is one.
+ out_queue_.Enqueue(nullptr);
+ out_semaphore_.Signal();
+}
+
+void Worker::PostMessageOut(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ HandleScope handle_scope(isolate);
+
+ if (args.Length() < 1) {
+ Throw(isolate, "Invalid argument");
+ return;
+ }
+
+ Local<Value> message = args[0];
+ Local<Value> transfer = Undefined(isolate);
+ std::unique_ptr<SerializationData> data =
+ Shell::SerializeValue(isolate, message, transfer);
+ if (data) {
+ DCHECK(args.Data()->IsExternal());
+ Local<External> this_value = Local<External>::Cast(args.Data());
+ Worker* worker = static_cast<Worker*>(this_value->Value());
+ worker->out_queue_.Enqueue(std::move(data));
+ worker->out_semaphore_.Signal();
+ }
+}
+
+bool Shell::SetOptions(int argc, char* argv[]) {
+ bool logfile_per_isolate = false;
+ for (int i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "--") == 0) {
+ argv[i] = nullptr;
+ for (int j = i + 1; j < argc; j++) {
+ options.arguments.push_back(argv[j]);
+ argv[j] = nullptr;
+ }
+ break;
+ } else if (strcmp(argv[i], "--no-arguments") == 0) {
+ options.include_arguments = false;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--stress-opt") == 0) {
+ options.stress_opt = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--nostress-opt") == 0 ||
+ strcmp(argv[i], "--no-stress-opt") == 0) {
+ options.stress_opt = false;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--stress-deopt") == 0) {
+ options.stress_deopt = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--stress-background-compile") == 0) {
+ options.stress_background_compile = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--nostress-background-compile") == 0 ||
+ strcmp(argv[i], "--no-stress-background-compile") == 0) {
+ options.stress_background_compile = false;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--noalways-opt") == 0 ||
+ strcmp(argv[i], "--no-always-opt") == 0) {
+ // No support for stressing if we can't use --always-opt.
+ options.stress_opt = false;
+ options.stress_deopt = false;
+ } else if (strcmp(argv[i], "--logfile-per-isolate") == 0) {
+ logfile_per_isolate = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--shell") == 0) {
+ options.interactive_shell = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--test") == 0) {
+ options.test_shell = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--notest") == 0 ||
+ strcmp(argv[i], "--no-test") == 0) {
+ options.test_shell = false;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--send-idle-notification") == 0) {
+ options.send_idle_notification = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--invoke-weak-callbacks") == 0) {
+ options.invoke_weak_callbacks = true;
+ // TODO(jochen) See issue 3351
+ options.send_idle_notification = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--omit-quit") == 0) {
+ options.omit_quit = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--no-wait-for-wasm") == 0) {
+ // TODO(herhut) Remove this flag once wasm compilation is fully
+ // isolate-independent.
+ options.wait_for_wasm = false;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "-f") == 0) {
+ // Ignore any -f flags for compatibility with other stand-alone
+ // JavaScript engines.
+ continue;
+ } else if (strcmp(argv[i], "--isolate") == 0) {
+ options.num_isolates++;
+ } else if (strcmp(argv[i], "--throws") == 0) {
+ options.expected_to_throw = true;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--icu-data-file=", 16) == 0) {
+ options.icu_data_file = argv[i] + 16;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--icu-locale=", 13) == 0) {
+ options.icu_locale = argv[i] + 13;
+ argv[i] = nullptr;
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+ } else if (strncmp(argv[i], "--natives_blob=", 15) == 0) {
+ options.natives_blob = argv[i] + 15;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--snapshot_blob=", 16) == 0) {
+ options.snapshot_blob = argv[i] + 16;
+ argv[i] = nullptr;
+#endif // V8_USE_EXTERNAL_STARTUP_DATA
+ } else if (strcmp(argv[i], "--cache") == 0 ||
+ strncmp(argv[i], "--cache=", 8) == 0) {
+ const char* value = argv[i] + 7;
+ if (!*value || strncmp(value, "=code", 6) == 0) {
+ options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
+ options.code_cache_options =
+ ShellOptions::CodeCacheOptions::kProduceCache;
+ } else if (strncmp(value, "=none", 6) == 0) {
+ options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
+ options.code_cache_options =
+ ShellOptions::CodeCacheOptions::kNoProduceCache;
+ } else if (strncmp(value, "=after-execute", 15) == 0) {
+ options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
+ options.code_cache_options =
+ ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute;
+ } else if (strncmp(value, "=full-code-cache", 17) == 0) {
+ options.compile_options = v8::ScriptCompiler::kEagerCompile;
+ options.code_cache_options =
+ ShellOptions::CodeCacheOptions::kProduceCache;
+ } else {
+ printf("Unknown option to --cache.\n");
+ return false;
+ }
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--enable-tracing") == 0) {
+ options.trace_enabled = true;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--trace-path=", 13) == 0) {
+ options.trace_path = argv[i] + 13;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--trace-config=", 15) == 0) {
+ options.trace_config = argv[i] + 15;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--enable-inspector") == 0) {
+ options.enable_inspector = true;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--lcov=", 7) == 0) {
+ options.lcov_file = argv[i] + 7;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--disable-in-process-stack-traces") == 0) {
+ options.disable_in_process_stack_traces = true;
+ argv[i] = nullptr;
+#ifdef V8_OS_POSIX
+ } else if (strncmp(argv[i], "--read-from-tcp-port=", 21) == 0) {
+ options.read_from_tcp_port = atoi(argv[i] + 21);
+ argv[i] = nullptr;
+#endif // V8_OS_POSIX
+ } else if (strcmp(argv[i], "--enable-os-system") == 0) {
+ options.enable_os_system = true;
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--quiet-load") == 0) {
+ options.quiet_load = true;
+ argv[i] = nullptr;
+ } else if (strncmp(argv[i], "--thread-pool-size=", 19) == 0) {
+ options.thread_pool_size = atoi(argv[i] + 19);
+ argv[i] = nullptr;
+ } else if (strcmp(argv[i], "--stress-delay-tasks") == 0) {
+ // Delay execution of tasks by 0-100ms randomly (based on --random-seed).
+ options.stress_delay_tasks = true;
+ argv[i] = nullptr;
+ }
+ }
+
+ v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
+ options.mock_arraybuffer_allocator = i::FLAG_mock_arraybuffer_allocator;
+ options.mock_arraybuffer_allocator_limit =
+ i::FLAG_mock_arraybuffer_allocator_limit;
+
+ // Set up isolated source groups.
+ options.isolate_sources = new SourceGroup[options.num_isolates];
+ SourceGroup* current = options.isolate_sources;
+ current->Begin(argv, 1);
+ for (int i = 1; i < argc; i++) {
+ const char* str = argv[i];
+ if (strcmp(str, "--isolate") == 0) {
+ current->End(i);
+ current++;
+ current->Begin(argv, i + 1);
+ } else if (strcmp(str, "--module") == 0) {
+ // Pass on to SourceGroup, which understands this option.
+ } else if (strncmp(str, "--", 2) == 0) {
+ printf("Warning: unknown flag %s.\nTry --help for options\n", str);
+ } else if (strcmp(str, "-e") == 0 && i + 1 < argc) {
+ set_script_executed();
+ } else if (strncmp(str, "-", 1) != 0) {
+ // Not a flag, so it must be a script to execute.
+ set_script_executed();
+ }
+ }
+ current->End(argc);
+
+ if (!logfile_per_isolate && options.num_isolates) {
+ V8::SetFlagsFromString("--no-logfile-per-isolate");
+ }
+
+ return true;
+}
+
+int Shell::RunMain(Isolate* isolate, int argc, char* argv[], bool last_run) {
+ for (int i = 1; i < options.num_isolates; ++i) {
+ options.isolate_sources[i].StartExecuteInThread();
+ }
+ bool success = true;
+ {
+ SetWaitUntilDone(isolate, false);
+ if (options.lcov_file) {
+ debug::Coverage::SelectMode(isolate, debug::CoverageMode::kBlockCount);
+ }
+ HandleScope scope(isolate);
+ Local<Context> context = CreateEvaluationContext(isolate);
+ bool use_existing_context = last_run && use_interactive_shell();
+ if (use_existing_context) {
+ // Keep using the same context in the interactive shell.
+ evaluation_context_.Reset(isolate, context);
+ }
+ {
+ Context::Scope cscope(context);
+ InspectorClient inspector_client(context, options.enable_inspector);
+ PerIsolateData::RealmScope realm_scope(PerIsolateData::Get(isolate));
+ if (!options.isolate_sources[0].Execute(isolate)) success = false;
+ if (!CompleteMessageLoop(isolate)) success = false;
+ }
+ if (!use_existing_context) {
+ DisposeModuleEmbedderData(context);
+ }
+ WriteLcovData(isolate, options.lcov_file);
+ }
+ CollectGarbage(isolate);
+ for (int i = 1; i < options.num_isolates; ++i) {
+ if (last_run) {
+ options.isolate_sources[i].JoinThread();
+ } else {
+ options.isolate_sources[i].WaitForThread();
+ }
+ }
+ CleanupWorkers();
+ // In order to finish successfully, success must be != expected_to_throw.
+ return success == Shell::options.expected_to_throw ? 1 : 0;
+}
+
+void Shell::CollectGarbage(Isolate* isolate) {
+ if (options.send_idle_notification) {
+ const double kLongIdlePauseInSeconds = 1.0;
+ isolate->ContextDisposedNotification();
+ isolate->IdleNotificationDeadline(
+ g_platform->MonotonicallyIncreasingTime() + kLongIdlePauseInSeconds);
+ }
+ if (options.invoke_weak_callbacks) {
+ // By sending a low memory notifications, we will try hard to collect all
+ // garbage and will therefore also invoke all weak callbacks of actually
+ // unreachable persistent handles.
+ isolate->LowMemoryNotification();
+ }
+}
+
+void Shell::SetWaitUntilDone(Isolate* isolate, bool value) {
+ base::MutexGuard guard(isolate_status_lock_.Pointer());
+ if (isolate_status_.count(isolate) == 0) {
+ isolate_status_.insert(std::make_pair(isolate, value));
+ } else {
+ isolate_status_[isolate] = value;
+ }
+}
+
+namespace {
+bool ProcessMessages(
+ Isolate* isolate,
+ const std::function<platform::MessageLoopBehavior()>& behavior) {
+ while (true) {
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
+ i::SaveAndSwitchContext saved_context(i_isolate, i::Context());
+ SealHandleScope shs(isolate);
+ while (v8::platform::PumpMessageLoop(g_default_platform, isolate,
+ behavior())) {
+ MicrotasksScope::PerformCheckpoint(isolate);
+ }
+ if (g_default_platform->IdleTasksEnabled(isolate)) {
+ v8::platform::RunIdleTasks(g_default_platform, isolate,
+ 50.0 / base::Time::kMillisecondsPerSecond);
+ }
+ HandleScope handle_scope(isolate);
+ PerIsolateData* data = PerIsolateData::Get(isolate);
+ Local<Function> callback;
+ if (!data->GetTimeoutCallback().ToLocal(&callback)) break;
+ Local<Context> context;
+ if (!data->GetTimeoutContext().ToLocal(&context)) break;
+ TryCatch try_catch(isolate);
+ try_catch.SetVerbose(true);
+ Context::Scope context_scope(context);
+ if (callback->Call(context, Undefined(isolate), 0, nullptr).IsEmpty()) {
+ Shell::ReportException(isolate, &try_catch);
+ return false;
+ }
+ }
+ return true;
+}
+} // anonymous namespace
+
+bool Shell::CompleteMessageLoop(Isolate* isolate) {
+ auto get_waiting_behaviour = [isolate]() {
+ base::MutexGuard guard(isolate_status_lock_.Pointer());
+ DCHECK_GT(isolate_status_.count(isolate), 0);
+ i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
+ i::wasm::WasmEngine* wasm_engine = i_isolate->wasm_engine();
+ bool should_wait = (options.wait_for_wasm &&
+ wasm_engine->HasRunningCompileJob(i_isolate)) ||
+ isolate_status_[isolate];
+ return should_wait ? platform::MessageLoopBehavior::kWaitForWork
+ : platform::MessageLoopBehavior::kDoNotWait;
+ };
+ return ProcessMessages(isolate, get_waiting_behaviour);
+}
+
+bool Shell::EmptyMessageQueues(Isolate* isolate) {
+ return ProcessMessages(
+ isolate, []() { return platform::MessageLoopBehavior::kDoNotWait; });
+}
+
+class Serializer : public ValueSerializer::Delegate {
+ public:
+ explicit Serializer(Isolate* isolate)
+ : isolate_(isolate),
+ serializer_(isolate, this),
+ current_memory_usage_(0) {}
+
+ Maybe<bool> WriteValue(Local<Context> context, Local<Value> value,
+ Local<Value> transfer) {
+ bool ok;
+ DCHECK(!data_);
+ data_.reset(new SerializationData);
+ if (!PrepareTransfer(context, transfer).To(&ok)) {
+ return Nothing<bool>();
+ }
+ serializer_.WriteHeader();
+
+ if (!serializer_.WriteValue(context, value).To(&ok)) {
+ data_.reset();
+ return Nothing<bool>();
+ }
+
+ if (!FinalizeTransfer().To(&ok)) {
+ return Nothing<bool>();
+ }
+
+ std::pair<uint8_t*, size_t> pair = serializer_.Release();
+ data_->data_.reset(pair.first);
+ data_->size_ = pair.second;
+ return Just(true);
+ }
+
+ std::unique_ptr<SerializationData> Release() { return std::move(data_); }
+
+ void AppendExternalizedContentsTo(std::vector<ExternalizedContents>* to) {
+ to->insert(to->end(),
+ std::make_move_iterator(externalized_contents_.begin()),
+ std::make_move_iterator(externalized_contents_.end()));
+ externalized_contents_.clear();
+ }
+
+ protected:
+ // Implements ValueSerializer::Delegate.
+ void ThrowDataCloneError(Local<String> message) override {
+ isolate_->ThrowException(Exception::Error(message));
+ }
+
+ Maybe<uint32_t> GetSharedArrayBufferId(
+ Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override {
+ DCHECK_NOT_NULL(data_);
+ for (size_t index = 0; index < shared_array_buffers_.size(); ++index) {
+ if (shared_array_buffers_[index] == shared_array_buffer) {
+ return Just<uint32_t>(static_cast<uint32_t>(index));
+ }
+ }
+
+ size_t index = shared_array_buffers_.size();
+ shared_array_buffers_.emplace_back(isolate_, shared_array_buffer);
+ data_->shared_array_buffer_contents_.push_back(
+ MaybeExternalize(shared_array_buffer));
+ return Just<uint32_t>(static_cast<uint32_t>(index));
+ }
+
+ Maybe<uint32_t> GetWasmModuleTransferId(
+ Isolate* isolate, Local<WasmModuleObject> module) override {
+ DCHECK_NOT_NULL(data_);
+ for (size_t index = 0; index < wasm_modules_.size(); ++index) {
+ if (wasm_modules_[index] == module) {
+ return Just<uint32_t>(static_cast<uint32_t>(index));
+ }
+ }
+
+ size_t index = wasm_modules_.size();
+ wasm_modules_.emplace_back(isolate_, module);
+ data_->transferrable_modules_.push_back(module->GetTransferrableModule());
+ return Just<uint32_t>(static_cast<uint32_t>(index));
+ }
+
+ void* ReallocateBufferMemory(void* old_buffer, size_t size,
+ size_t* actual_size) override {
+ // Not accurate, because we don't take into account reallocated buffers,
+ // but this is fine for testing.
+ current_memory_usage_ += size;
+ if (current_memory_usage_ > kMaxSerializerMemoryUsage) return nullptr;
+
+ void* result = realloc(old_buffer, size);
+ *actual_size = result ? size : 0;
+ return result;
+ }
+
+ void FreeBufferMemory(void* buffer) override { free(buffer); }
+
+ private:
+ Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) {
+ if (transfer->IsArray()) {
+ Local<Array> transfer_array = Local<Array>::Cast(transfer);
+ uint32_t length = transfer_array->Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ Local<Value> element;
+ if (transfer_array->Get(context, i).ToLocal(&element)) {
+ if (!element->IsArrayBuffer()) {
+ Throw(isolate_, "Transfer array elements must be an ArrayBuffer");
+ return Nothing<bool>();
+ }
+
+ Local<ArrayBuffer> array_buffer = Local<ArrayBuffer>::Cast(element);
+
+ if (std::find(array_buffers_.begin(), array_buffers_.end(),
+ array_buffer) != array_buffers_.end()) {
+ Throw(isolate_,
+ "ArrayBuffer occurs in the transfer array more than once");
+ return Nothing<bool>();
+ }
+
+ serializer_.TransferArrayBuffer(
+ static_cast<uint32_t>(array_buffers_.size()), array_buffer);
+ array_buffers_.emplace_back(isolate_, array_buffer);
+ } else {
+ return Nothing<bool>();
+ }
+ }
+ return Just(true);
+ } else if (transfer->IsUndefined()) {
+ return Just(true);
+ } else {
+ Throw(isolate_, "Transfer list must be an Array or undefined");
+ return Nothing<bool>();
+ }
+ }
+
+ template <typename T>
+ typename T::Contents MaybeExternalize(Local<T> array_buffer) {
+ if (array_buffer->IsExternal()) {
+ return array_buffer->GetContents();
+ } else {
+ typename T::Contents contents = array_buffer->Externalize();
+ externalized_contents_.emplace_back(contents);
+ return contents;
+ }
+ }
+
+ Maybe<bool> FinalizeTransfer() {
+ for (const auto& global_array_buffer : array_buffers_) {
+ Local<ArrayBuffer> array_buffer =
+ Local<ArrayBuffer>::New(isolate_, global_array_buffer);
+ if (!array_buffer->IsDetachable()) {
+ Throw(isolate_, "ArrayBuffer could not be transferred");
+ return Nothing<bool>();
+ }
+
+ ArrayBuffer::Contents contents = MaybeExternalize(array_buffer);
+ array_buffer->Detach();
+ data_->array_buffer_contents_.push_back(contents);
+ }
+
+ return Just(true);
+ }
+
+ Isolate* isolate_;
+ ValueSerializer serializer_;
+ std::unique_ptr<SerializationData> data_;
+ std::vector<Global<ArrayBuffer>> array_buffers_;
+ std::vector<Global<SharedArrayBuffer>> shared_array_buffers_;
+ std::vector<Global<WasmModuleObject>> wasm_modules_;
+ std::vector<ExternalizedContents> externalized_contents_;
+ size_t current_memory_usage_;
+
+ DISALLOW_COPY_AND_ASSIGN(Serializer);
+};
+
+class Deserializer : public ValueDeserializer::Delegate {
+ public:
+ Deserializer(Isolate* isolate, std::unique_ptr<SerializationData> data)
+ : isolate_(isolate),
+ deserializer_(isolate, data->data(), data->size(), this),
+ data_(std::move(data)) {
+ deserializer_.SetSupportsLegacyWireFormat(true);
+ }
+
+ MaybeLocal<Value> ReadValue(Local<Context> context) {
+ bool read_header;
+ if (!deserializer_.ReadHeader(context).To(&read_header)) {
+ return MaybeLocal<Value>();
+ }
+
+ uint32_t index = 0;
+ for (const auto& contents : data_->array_buffer_contents()) {
+ Local<ArrayBuffer> array_buffer =
+ ArrayBuffer::New(isolate_, contents.Data(), contents.ByteLength());
+ deserializer_.TransferArrayBuffer(index++, array_buffer);
+ }
+
+ return deserializer_.ReadValue(context);
+ }
+
+ MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
+ Isolate* isolate, uint32_t clone_id) override {
+ DCHECK_NOT_NULL(data_);
+ if (clone_id < data_->shared_array_buffer_contents().size()) {
+ const SharedArrayBuffer::Contents contents =
+ data_->shared_array_buffer_contents().at(clone_id);
+ return SharedArrayBuffer::New(isolate_, contents.Data(),
+ contents.ByteLength());
+ }
+ return MaybeLocal<SharedArrayBuffer>();
+ }
+
+ MaybeLocal<WasmModuleObject> GetWasmModuleFromId(
+ Isolate* isolate, uint32_t transfer_id) override {
+ DCHECK_NOT_NULL(data_);
+ if (transfer_id < data_->transferrable_modules().size()) {
+ return WasmModuleObject::FromTransferrableModule(
+ isolate_, data_->transferrable_modules().at(transfer_id));
+ }
+ return MaybeLocal<WasmModuleObject>();
+ }
+
+ private:
+ Isolate* isolate_;
+ ValueDeserializer deserializer_;
+ std::unique_ptr<SerializationData> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(Deserializer);
+};
+
+std::unique_ptr<SerializationData> Shell::SerializeValue(
+ Isolate* isolate, Local<Value> value, Local<Value> transfer) {
+ bool ok;
+ Local<Context> context = isolate->GetCurrentContext();
+ Serializer serializer(isolate);
+ std::unique_ptr<SerializationData> data;
+ if (serializer.WriteValue(context, value, transfer).To(&ok)) {
+ data = serializer.Release();
+ }
+ // Append externalized contents even when WriteValue fails.
+ base::MutexGuard lock_guard(workers_mutex_.Pointer());
+ serializer.AppendExternalizedContentsTo(&externalized_contents_);
+ return data;
+}
+
+MaybeLocal<Value> Shell::DeserializeValue(
+ Isolate* isolate, std::unique_ptr<SerializationData> data) {
+ Local<Value> value;
+ Local<Context> context = isolate->GetCurrentContext();
+ Deserializer deserializer(isolate, std::move(data));
+ return deserializer.ReadValue(context);
+}
+
+void Shell::CleanupWorkers() {
+ // Make a copy of workers_, because we don't want to call Worker::Terminate
+ // while holding the workers_mutex_ lock. Otherwise, if a worker is about to
+ // create a new Worker, it would deadlock.
+ std::vector<Worker*> workers_copy;
+ {
+ base::MutexGuard lock_guard(workers_mutex_.Pointer());
+ allow_new_workers_ = false;
+ workers_copy.swap(workers_);
+ }
+
+ for (Worker* worker : workers_copy) {
+ worker->WaitForThread();
+ delete worker;
+ }
+
+ // Now that all workers are terminated, we can re-enable Worker creation.
+ base::MutexGuard lock_guard(workers_mutex_.Pointer());
+ allow_new_workers_ = true;
+ externalized_contents_.clear();
+}
+
+int Shell::Main(int argc, char* argv[]) {
+ v8::base::EnsureConsoleOutput();
+ if (!SetOptions(argc, argv)) return 1;
+
+ v8::V8::InitializeICUDefaultLocation(argv[0], options.icu_data_file);
+
+#ifdef V8_INTL_SUPPORT
+ if (options.icu_locale != nullptr) {
+ icu::Locale locale(options.icu_locale);
+ UErrorCode error_code = U_ZERO_ERROR;
+ icu::Locale::setDefault(locale, error_code);
+ }
+#endif // V8_INTL_SUPPORT
+
+ v8::platform::InProcessStackDumping in_process_stack_dumping =
+ options.disable_in_process_stack_traces
+ ? v8::platform::InProcessStackDumping::kDisabled
+ : v8::platform::InProcessStackDumping::kEnabled;
+
+ std::unique_ptr<platform::tracing::TracingController> tracing;
+ std::ofstream trace_file;
+#ifdef V8_USE_PERFETTO
+ std::ofstream perfetto_trace_file;
+#endif // V8_USE_PERFETTO
+ if (options.trace_enabled && !i::FLAG_verify_predictable) {
+ tracing = base::make_unique<platform::tracing::TracingController>();
+
+ trace_file.open(options.trace_path ? options.trace_path : "v8_trace.json");
+ DCHECK(trace_file.good());
+ platform::tracing::TraceBuffer* trace_buffer =
+ platform::tracing::TraceBuffer::CreateTraceBufferRingBuffer(
+ platform::tracing::TraceBuffer::kRingBufferChunks,
+ platform::tracing::TraceWriter::CreateJSONTraceWriter(trace_file));
+ tracing->Initialize(trace_buffer);
+
+#ifdef V8_USE_PERFETTO
+ perfetto_trace_file.open("v8_perfetto_trace.json");
+ DCHECK(trace_file.good());
+ tracing->InitializeForPerfetto(&perfetto_trace_file);
+#endif // V8_USE_PERFETTO
+ }
+
+ platform::tracing::TracingController* tracing_controller = tracing.get();
+ g_platform = v8::platform::NewDefaultPlatform(
+ options.thread_pool_size, v8::platform::IdleTaskSupport::kEnabled,
+ in_process_stack_dumping, std::move(tracing));
+ g_default_platform = g_platform.get();
+ if (i::FLAG_verify_predictable) {
+ g_platform = MakePredictablePlatform(std::move(g_platform));
+ }
+ if (options.stress_delay_tasks) {
+ int64_t random_seed = i::FLAG_fuzzer_random_seed;
+ if (!random_seed) random_seed = i::FLAG_random_seed;
+ // If random_seed is still 0 here, the {DelayedTasksPlatform} will choose a
+ // random seed.
+ g_platform = MakeDelayedTasksPlatform(std::move(g_platform), random_seed);
+ }
+
+ if (i::FLAG_trace_turbo_cfg_file == nullptr) {
+ V8::SetFlagsFromString("--trace-turbo-cfg-file=turbo.cfg");
+ }
+ if (i::FLAG_redirect_code_traces_to == nullptr) {
+ V8::SetFlagsFromString("--redirect-code-traces-to=code.asm");
+ }
+ v8::V8::InitializePlatform(g_platform.get());
+ v8::V8::Initialize();
+ if (options.natives_blob || options.snapshot_blob) {
+ v8::V8::InitializeExternalStartupData(options.natives_blob,
+ options.snapshot_blob);
+ } else {
+ v8::V8::InitializeExternalStartupData(argv[0]);
+ }
+ int result = 0;
+ Isolate::CreateParams create_params;
+ ShellArrayBufferAllocator shell_array_buffer_allocator;
+ MockArrayBufferAllocator mock_arraybuffer_allocator;
+ const size_t memory_limit =
+ options.mock_arraybuffer_allocator_limit * options.num_isolates;
+ MockArrayBufferAllocatiorWithLimit mock_arraybuffer_allocator_with_limit(
+ memory_limit >= options.mock_arraybuffer_allocator_limit
+ ? memory_limit
+ : std::numeric_limits<size_t>::max());
+ if (options.mock_arraybuffer_allocator) {
+ if (memory_limit) {
+ Shell::array_buffer_allocator = &mock_arraybuffer_allocator_with_limit;
+ } else {
+ Shell::array_buffer_allocator = &mock_arraybuffer_allocator;
+ }
+ } else {
+ Shell::array_buffer_allocator = &shell_array_buffer_allocator;
+ }
+ create_params.array_buffer_allocator = Shell::array_buffer_allocator;
+#ifdef ENABLE_VTUNE_JIT_INTERFACE
+ create_params.code_event_handler = vTune::GetVtuneCodeEventHandler();
+#endif
+ create_params.constraints.ConfigureDefaults(
+ base::SysInfo::AmountOfPhysicalMemory(),
+ base::SysInfo::AmountOfVirtualMemory());
+
+ Shell::counter_map_ = new CounterMap();
+ if (i::FLAG_dump_counters || i::FLAG_dump_counters_nvp ||
+ i::TracingFlags::is_gc_stats_enabled()) {
+ create_params.counter_lookup_callback = LookupCounter;
+ create_params.create_histogram_callback = CreateHistogram;
+ create_params.add_histogram_sample_callback = AddHistogramSample;
+ }
+
+ if (V8_TRAP_HANDLER_SUPPORTED && i::FLAG_wasm_trap_handler) {
+ constexpr bool use_default_trap_handler = true;
+ if (!v8::V8::EnableWebAssemblyTrapHandler(use_default_trap_handler)) {
+ FATAL("Could not register trap handler");
+ }
+ }
+
+ Isolate* isolate = Isolate::New(create_params);
+ isolate->SetHostImportModuleDynamicallyCallback(
+ Shell::HostImportModuleDynamically);
+ isolate->SetHostInitializeImportMetaObjectCallback(
+ Shell::HostInitializeImportMetaObject);
+
+ D8Console console(isolate);
+ {
+ Isolate::Scope scope(isolate);
+ Initialize(isolate);
+ PerIsolateData data(isolate);
+ debug::SetConsoleDelegate(isolate, &console);
+
+ if (options.trace_enabled) {
+ platform::tracing::TraceConfig* trace_config;
+ if (options.trace_config) {
+ int size = 0;
+ char* trace_config_json_str = ReadChars(options.trace_config, &size);
+ trace_config =
+ tracing::CreateTraceConfigFromJSON(isolate, trace_config_json_str);
+ delete[] trace_config_json_str;
+ } else {
+ trace_config =
+ platform::tracing::TraceConfig::CreateDefaultTraceConfig();
+ }
+ tracing_controller->StartTracing(trace_config);
+ }
+
+ if (options.stress_opt || options.stress_deopt) {
+ Testing::SetStressRunType(options.stress_opt ? Testing::kStressTypeOpt
+ : Testing::kStressTypeDeopt);
+ options.stress_runs = Testing::GetStressRuns();
+ for (int i = 0; i < options.stress_runs && result == 0; i++) {
+ printf("============ Stress %d/%d ============\n", i + 1,
+ options.stress_runs);
+ Testing::PrepareStressRun(i);
+ bool last_run = i == options.stress_runs - 1;
+ result = RunMain(isolate, argc, argv, last_run);
+ }
+ printf("======== Full Deoptimization =======\n");
+ Testing::DeoptimizeAll(isolate);
+ } else if (i::FLAG_stress_runs > 0) {
+ options.stress_runs = i::FLAG_stress_runs;
+ for (int i = 0; i < options.stress_runs && result == 0; i++) {
+ printf("============ Run %d/%d ============\n", i + 1,
+ options.stress_runs);
+ bool last_run = i == options.stress_runs - 1;
+ result = RunMain(isolate, argc, argv, last_run);
+ }
+ } else if (options.code_cache_options !=
+ ShellOptions::CodeCacheOptions::kNoProduceCache) {
+ printf("============ Run: Produce code cache ============\n");
+ // First run to produce the cache
+ Isolate::CreateParams create_params;
+ create_params.array_buffer_allocator = Shell::array_buffer_allocator;
+ i::FLAG_hash_seed ^= 1337; // Use a different hash seed.
+ Isolate* isolate2 = Isolate::New(create_params);
+ i::FLAG_hash_seed ^= 1337; // Restore old hash seed.
+ isolate2->SetHostImportModuleDynamicallyCallback(
+ Shell::HostImportModuleDynamically);
+ isolate2->SetHostInitializeImportMetaObjectCallback(
+ Shell::HostInitializeImportMetaObject);
+ {
+ D8Console console(isolate2);
+ Initialize(isolate2);
+ debug::SetConsoleDelegate(isolate2, &console);
+ PerIsolateData data(isolate2);
+ Isolate::Scope isolate_scope(isolate2);
+
+ result = RunMain(isolate2, argc, argv, false);
+ }
+ isolate2->Dispose();
+
+ // Change the options to consume cache
+ DCHECK(options.compile_options == v8::ScriptCompiler::kEagerCompile ||
+ options.compile_options == v8::ScriptCompiler::kNoCompileOptions);
+ options.compile_options = v8::ScriptCompiler::kConsumeCodeCache;
+ options.code_cache_options =
+ ShellOptions::CodeCacheOptions::kNoProduceCache;
+
+ printf("============ Run: Consume code cache ============\n");
+ // Second run to consume the cache in current isolate
+ result = RunMain(isolate, argc, argv, true);
+ options.compile_options = v8::ScriptCompiler::kNoCompileOptions;
+ } else {
+ bool last_run = true;
+ result = RunMain(isolate, argc, argv, last_run);
+ }
+
+ // Run interactive shell if explicitly requested or if no script has been
+ // executed, but never on --test
+ if (use_interactive_shell()) {
+ RunShell(isolate);
+ }
+
+ if (i::FLAG_trace_ignition_dispatches &&
+ i::FLAG_trace_ignition_dispatches_output_file != nullptr) {
+ WriteIgnitionDispatchCountersFile(isolate);
+ }
+
+ // Shut down contexts and collect garbage.
+ cached_code_map_.clear();
+ evaluation_context_.Reset();
+ stringify_function_.Reset();
+ CollectGarbage(isolate);
+ }
+ OnExit(isolate);
+ V8::Dispose();
+ V8::ShutdownPlatform();
+
+ // Delete the platform explicitly here to write the tracing output to the
+ // tracing file.
+ if (options.trace_enabled) {
+ tracing_controller->StopTracing();
+ }
+ g_platform.reset();
+ return result;
+}
+
+} // namespace v8
+
+#ifndef GOOGLE3
+int main(int argc, char* argv[]) { return v8::Shell::Main(argc, argv); }
+#endif
+
+#undef CHECK
+#undef DCHECK
diff --git a/deps/v8/src/d8/d8.h b/deps/v8/src/d8/d8.h
new file mode 100644
index 0000000000..1e0dd43c2d
--- /dev/null
+++ b/deps/v8/src/d8/d8.h
@@ -0,0 +1,550 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_D8_D8_H_
+#define V8_D8_D8_H_
+
+#include <iterator>
+#include <map>
+#include <memory>
+#include <queue>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "src/base/once.h"
+#include "src/base/platform/time.h"
+#include "src/d8/async-hooks-wrapper.h"
+#include "src/strings/string-hasher.h"
+#include "src/utils/allocation.h"
+#include "src/utils/utils.h"
+
+namespace v8 {
+
+// A single counter in a counter collection.
+class Counter {
+ public:
+ static const int kMaxNameSize = 64;
+ int32_t* Bind(const char* name, bool histogram);
+ int32_t* ptr() { return &count_; }
+ int32_t count() { return count_; }
+ int32_t sample_total() { return sample_total_; }
+ bool is_histogram() { return is_histogram_; }
+ void AddSample(int32_t sample);
+
+ private:
+ int32_t count_;
+ int32_t sample_total_;
+ bool is_histogram_;
+ uint8_t name_[kMaxNameSize];
+};
+
+// A set of counters and associated information. An instance of this
+// class is stored directly in the memory-mapped counters file if
+// the --map-counters options is used
+class CounterCollection {
+ public:
+ CounterCollection();
+ Counter* GetNextCounter();
+
+ private:
+ static const unsigned kMaxCounters = 512;
+ uint32_t magic_number_;
+ uint32_t max_counters_;
+ uint32_t max_name_size_;
+ uint32_t counters_in_use_;
+ Counter counters_[kMaxCounters];
+};
+
+using CounterMap = std::unordered_map<std::string, Counter*>;
+
+class SourceGroup {
+ public:
+ SourceGroup()
+ : next_semaphore_(0),
+ done_semaphore_(0),
+ thread_(nullptr),
+ argv_(nullptr),
+ begin_offset_(0),
+ end_offset_(0) {}
+
+ ~SourceGroup();
+
+ void Begin(char** argv, int offset) {
+ argv_ = const_cast<const char**>(argv);
+ begin_offset_ = offset;
+ }
+
+ void End(int offset) { end_offset_ = offset; }
+
+ // Returns true on success, false if an uncaught exception was thrown.
+ bool Execute(Isolate* isolate);
+
+ void StartExecuteInThread();
+ void WaitForThread();
+ void JoinThread();
+
+ private:
+ class IsolateThread : public base::Thread {
+ public:
+ explicit IsolateThread(SourceGroup* group);
+
+ void Run() override { group_->ExecuteInThread(); }
+
+ private:
+ SourceGroup* group_;
+ };
+
+ void ExecuteInThread();
+
+ base::Semaphore next_semaphore_;
+ base::Semaphore done_semaphore_;
+ base::Thread* thread_;
+
+ void ExitShell(int exit_code);
+ Local<String> ReadFile(Isolate* isolate, const char* name);
+
+ const char** argv_;
+ int begin_offset_;
+ int end_offset_;
+};
+
+// The backing store of an ArrayBuffer or SharedArrayBuffer, after
+// Externalize() has been called on it.
+class ExternalizedContents {
+ public:
+ explicit ExternalizedContents(const ArrayBuffer::Contents& contents)
+ : data_(contents.Data()),
+ length_(contents.ByteLength()),
+ deleter_(contents.Deleter()),
+ deleter_data_(contents.DeleterData()) {}
+ explicit ExternalizedContents(const SharedArrayBuffer::Contents& contents)
+ : data_(contents.Data()),
+ length_(contents.ByteLength()),
+ deleter_(contents.Deleter()),
+ deleter_data_(contents.DeleterData()) {}
+ ExternalizedContents(ExternalizedContents&& other) V8_NOEXCEPT
+ : data_(other.data_),
+ length_(other.length_),
+ deleter_(other.deleter_),
+ deleter_data_(other.deleter_data_) {
+ other.data_ = nullptr;
+ other.length_ = 0;
+ other.deleter_ = nullptr;
+ other.deleter_data_ = nullptr;
+ }
+ ExternalizedContents& operator=(ExternalizedContents&& other) V8_NOEXCEPT {
+ if (this != &other) {
+ data_ = other.data_;
+ length_ = other.length_;
+ deleter_ = other.deleter_;
+ deleter_data_ = other.deleter_data_;
+ other.data_ = nullptr;
+ other.length_ = 0;
+ other.deleter_ = nullptr;
+ other.deleter_data_ = nullptr;
+ }
+ return *this;
+ }
+ ~ExternalizedContents();
+
+ private:
+ void* data_;
+ size_t length_;
+ ArrayBuffer::Contents::DeleterCallback deleter_;
+ void* deleter_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalizedContents);
+};
+
+class SerializationData {
+ public:
+ SerializationData() : size_(0) {}
+
+ uint8_t* data() { return data_.get(); }
+ size_t size() { return size_; }
+ const std::vector<ArrayBuffer::Contents>& array_buffer_contents() {
+ return array_buffer_contents_;
+ }
+ const std::vector<SharedArrayBuffer::Contents>&
+ shared_array_buffer_contents() {
+ return shared_array_buffer_contents_;
+ }
+ const std::vector<WasmModuleObject::TransferrableModule>&
+ transferrable_modules() {
+ return transferrable_modules_;
+ }
+
+ private:
+ struct DataDeleter {
+ void operator()(uint8_t* p) const { free(p); }
+ };
+
+ std::unique_ptr<uint8_t, DataDeleter> data_;
+ size_t size_;
+ std::vector<ArrayBuffer::Contents> array_buffer_contents_;
+ std::vector<SharedArrayBuffer::Contents> shared_array_buffer_contents_;
+ std::vector<WasmModuleObject::TransferrableModule> transferrable_modules_;
+
+ private:
+ friend class Serializer;
+
+ DISALLOW_COPY_AND_ASSIGN(SerializationData);
+};
+
+class SerializationDataQueue {
+ public:
+ void Enqueue(std::unique_ptr<SerializationData> data);
+ bool Dequeue(std::unique_ptr<SerializationData>* data);
+ bool IsEmpty();
+ void Clear();
+
+ private:
+ base::Mutex mutex_;
+ std::vector<std::unique_ptr<SerializationData>> data_;
+};
+
+class Worker {
+ public:
+ Worker();
+ ~Worker();
+
+ // Run the given script on this Worker. This function should only be called
+ // once, and should only be called by the thread that created the Worker.
+ void StartExecuteInThread(const char* script);
+ // Post a message to the worker's incoming message queue. The worker will
+ // take ownership of the SerializationData.
+ // This function should only be called by the thread that created the Worker.
+ void PostMessage(std::unique_ptr<SerializationData> data);
+ // Synchronously retrieve messages from the worker's outgoing message queue.
+ // If there is no message in the queue, block until a message is available.
+ // If there are no messages in the queue and the worker is no longer running,
+ // return nullptr.
+ // This function should only be called by the thread that created the Worker.
+ std::unique_ptr<SerializationData> GetMessage();
+ // Terminate the worker's event loop. Messages from the worker that have been
+ // queued can still be read via GetMessage().
+ // This function can be called by any thread.
+ void Terminate();
+ // Terminate and join the thread.
+ // This function can be called by any thread.
+ void WaitForThread();
+
+ private:
+ class WorkerThread : public base::Thread {
+ public:
+ explicit WorkerThread(Worker* worker)
+ : base::Thread(base::Thread::Options("WorkerThread")),
+ worker_(worker) {}
+
+ void Run() override { worker_->ExecuteInThread(); }
+
+ private:
+ Worker* worker_;
+ };
+
+ void ExecuteInThread();
+ static void PostMessageOut(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ base::Semaphore in_semaphore_;
+ base::Semaphore out_semaphore_;
+ SerializationDataQueue in_queue_;
+ SerializationDataQueue out_queue_;
+ base::Thread* thread_;
+ char* script_;
+ base::Atomic32 running_;
+};
+
+class PerIsolateData {
+ public:
+ explicit PerIsolateData(Isolate* isolate);
+
+ ~PerIsolateData();
+
+ inline static PerIsolateData* Get(Isolate* isolate) {
+ return reinterpret_cast<PerIsolateData*>(isolate->GetData(0));
+ }
+
+ class RealmScope {
+ public:
+ explicit RealmScope(PerIsolateData* data);
+ ~RealmScope();
+
+ private:
+ PerIsolateData* data_;
+ };
+
+ inline void SetTimeout(Local<Function> callback, Local<Context> context);
+ inline MaybeLocal<Function> GetTimeoutCallback();
+ inline MaybeLocal<Context> GetTimeoutContext();
+
+ AsyncHooks* GetAsyncHooks() { return async_hooks_wrapper_; }
+
+ private:
+ friend class Shell;
+ friend class RealmScope;
+ Isolate* isolate_;
+ int realm_count_;
+ int realm_current_;
+ int realm_switch_;
+ Global<Context>* realms_;
+ Global<Value> realm_shared_;
+ std::queue<Global<Function>> set_timeout_callbacks_;
+ std::queue<Global<Context>> set_timeout_contexts_;
+ AsyncHooks* async_hooks_wrapper_;
+
+ int RealmIndexOrThrow(const v8::FunctionCallbackInfo<v8::Value>& args,
+ int arg_offset);
+ int RealmFind(Local<Context> context);
+};
+
+class ShellOptions {
+ public:
+ enum CodeCacheOptions {
+ kNoProduceCache,
+ kProduceCache,
+ kProduceCacheAfterExecute
+ };
+
+ ~ShellOptions() { delete[] isolate_sources; }
+
+ bool send_idle_notification = false;
+ bool invoke_weak_callbacks = false;
+ bool omit_quit = false;
+ bool wait_for_wasm = true;
+ bool stress_opt = false;
+ bool stress_deopt = false;
+ int stress_runs = 1;
+ bool interactive_shell = false;
+ bool test_shell = false;
+ bool expected_to_throw = false;
+ bool mock_arraybuffer_allocator = false;
+ size_t mock_arraybuffer_allocator_limit = 0;
+ bool enable_inspector = false;
+ int num_isolates = 1;
+ v8::ScriptCompiler::CompileOptions compile_options =
+ v8::ScriptCompiler::kNoCompileOptions;
+ bool stress_background_compile = false;
+ CodeCacheOptions code_cache_options = CodeCacheOptions::kNoProduceCache;
+ SourceGroup* isolate_sources = nullptr;
+ const char* icu_data_file = nullptr;
+ const char* icu_locale = nullptr;
+ const char* natives_blob = nullptr;
+ const char* snapshot_blob = nullptr;
+ bool trace_enabled = false;
+ const char* trace_path = nullptr;
+ const char* trace_config = nullptr;
+ const char* lcov_file = nullptr;
+ bool disable_in_process_stack_traces = false;
+ int read_from_tcp_port = -1;
+ bool enable_os_system = false;
+ bool quiet_load = false;
+ int thread_pool_size = 0;
+ bool stress_delay_tasks = false;
+ std::vector<const char*> arguments;
+ bool include_arguments = true;
+};
+
+class Shell : public i::AllStatic {
+ public:
+ enum PrintResult : bool { kPrintResult = true, kNoPrintResult = false };
+ enum ReportExceptions : bool {
+ kReportExceptions = true,
+ kNoReportExceptions = false
+ };
+ enum ProcessMessageQueue : bool {
+ kProcessMessageQueue = true,
+ kNoProcessMessageQueue = false
+ };
+
+ static bool ExecuteString(Isolate* isolate, Local<String> source,
+ Local<Value> name, PrintResult print_result,
+ ReportExceptions report_exceptions,
+ ProcessMessageQueue process_message_queue);
+ static bool ExecuteModule(Isolate* isolate, const char* file_name);
+ static void ReportException(Isolate* isolate, TryCatch* try_catch);
+ static Local<String> ReadFile(Isolate* isolate, const char* name);
+ static Local<Context> CreateEvaluationContext(Isolate* isolate);
+ static int RunMain(Isolate* isolate, int argc, char* argv[], bool last_run);
+ static int Main(int argc, char* argv[]);
+ static void Exit(int exit_code);
+ static void OnExit(Isolate* isolate);
+ static void CollectGarbage(Isolate* isolate);
+ static bool EmptyMessageQueues(Isolate* isolate);
+ static bool CompleteMessageLoop(Isolate* isolate);
+
+ static std::unique_ptr<SerializationData> SerializeValue(
+ Isolate* isolate, Local<Value> value, Local<Value> transfer);
+ static MaybeLocal<Value> DeserializeValue(
+ Isolate* isolate, std::unique_ptr<SerializationData> data);
+ static void CleanupWorkers();
+ static int* LookupCounter(const char* name);
+ static void* CreateHistogram(const char* name, int min, int max,
+ size_t buckets);
+ static void AddHistogramSample(void* histogram, int sample);
+ static void MapCounters(v8::Isolate* isolate, const char* name);
+
+ static void PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ static void RealmCurrent(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmOwner(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmGlobal(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmCreate(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmNavigate(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmCreateAllowCrossRealmAccess(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmDetachGlobal(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmDispose(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmSwitch(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmEval(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RealmSharedGet(Local<String> property,
+ const PropertyCallbackInfo<Value>& info);
+ static void RealmSharedSet(Local<String> property, Local<Value> value,
+ const PropertyCallbackInfo<void>& info);
+
+ static void AsyncHooksCreateHook(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void AsyncHooksExecutionAsyncId(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void AsyncHooksTriggerAsyncId(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+
+ static void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void PrintErr(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Write(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void WaitUntilDone(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void NotifyDone(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void QuitOnce(v8::FunctionCallbackInfo<v8::Value>* args);
+ static void Quit(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Version(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Read(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void ReadBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static Local<String> ReadFromStdin(Isolate* isolate);
+ static void ReadLine(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ args.GetReturnValue().Set(ReadFromStdin(args.GetIsolate()));
+ }
+ static void Load(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void WorkerNew(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void WorkerPostMessage(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void WorkerGetMessage(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void WorkerTerminate(const v8::FunctionCallbackInfo<v8::Value>& args);
+ // The OS object on the global object contains methods for performing
+ // operating system calls:
+ //
+ // os.system("program_name", ["arg1", "arg2", ...], timeout1, timeout2) will
+ // run the command, passing the arguments to the program. The standard output
+ // of the program will be picked up and returned as a multiline string. If
+ // timeout1 is present then it should be a number. -1 indicates no timeout
+ // and a positive number is used as a timeout in milliseconds that limits the
+ // time spent waiting between receiving output characters from the program.
+ // timeout2, if present, should be a number indicating the limit in
+ // milliseconds on the total running time of the program. Exceptions are
+ // thrown on timeouts or other errors or if the exit status of the program
+ // indicates an error.
+ //
+ // os.chdir(dir) changes directory to the given directory. Throws an
+ // exception/ on error.
+ //
+ // os.setenv(variable, value) sets an environment variable. Repeated calls to
+ // this method leak memory due to the API of setenv in the standard C library.
+ //
+ // os.umask(alue) calls the umask system call and returns the old umask.
+ //
+ // os.mkdirp(name, mask) creates a directory. The mask (if present) is anded
+ // with the current umask. Intermediate directories are created if necessary.
+ // An exception is not thrown if the directory already exists. Analogous to
+ // the "mkdir -p" command.
+ static void System(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void ChangeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void UnsetEnvironment(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetUMask(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void MakeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void RemoveDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static MaybeLocal<Promise> HostImportModuleDynamically(
+ Local<Context> context, Local<ScriptOrModule> referrer,
+ Local<String> specifier);
+ static void HostInitializeImportMetaObject(Local<Context> context,
+ Local<Module> module,
+ Local<Object> meta);
+
+ // Data is of type DynamicImportData*. We use void* here to be able
+ // to conform with MicrotaskCallback interface and enqueue this
+ // function in the microtask queue.
+ static void DoHostImportModuleDynamically(void* data);
+ static void AddOSMethods(v8::Isolate* isolate,
+ Local<ObjectTemplate> os_template);
+
+ static const char* kPrompt;
+ static ShellOptions options;
+ static ArrayBuffer::Allocator* array_buffer_allocator;
+
+ static void SetWaitUntilDone(Isolate* isolate, bool value);
+
+ static char* ReadCharsFromTcpPort(const char* name, int* size_out);
+
+ static void set_script_executed() { script_executed_.store(true); }
+ static bool use_interactive_shell() {
+ return (options.interactive_shell || !script_executed_.load()) &&
+ !options.test_shell;
+ }
+
+ private:
+ static Global<Context> evaluation_context_;
+ static base::OnceType quit_once_;
+ static Global<Function> stringify_function_;
+ static const char* stringify_source_;
+ static CounterMap* counter_map_;
+ // We statically allocate a set of local counters to be used if we
+ // don't want to store the stats in a memory-mapped file
+ static CounterCollection local_counters_;
+ static CounterCollection* counters_;
+ static base::OS::MemoryMappedFile* counters_file_;
+ static base::LazyMutex context_mutex_;
+ static const base::TimeTicks kInitialTicks;
+
+ static base::LazyMutex workers_mutex_; // Guards the following members.
+ static bool allow_new_workers_;
+ static std::vector<Worker*> workers_;
+ static std::vector<ExternalizedContents> externalized_contents_;
+
+ // Multiple isolates may update this flag concurrently.
+ static std::atomic<bool> script_executed_;
+
+ static void WriteIgnitionDispatchCountersFile(v8::Isolate* isolate);
+ // Append LCOV coverage data to file.
+ static void WriteLcovData(v8::Isolate* isolate, const char* file);
+ static Counter* GetCounter(const char* name, bool is_histogram);
+ static Local<String> Stringify(Isolate* isolate, Local<Value> value);
+ static void Initialize(Isolate* isolate);
+ static void RunShell(Isolate* isolate);
+ static bool SetOptions(int argc, char* argv[]);
+ static Local<ObjectTemplate> CreateGlobalTemplate(Isolate* isolate);
+ static MaybeLocal<Context> CreateRealm(
+ const v8::FunctionCallbackInfo<v8::Value>& args, int index,
+ v8::MaybeLocal<Value> global_object);
+ static void DisposeRealm(const v8::FunctionCallbackInfo<v8::Value>& args,
+ int index);
+ static MaybeLocal<Module> FetchModuleTree(v8::Local<v8::Context> context,
+ const std::string& file_name);
+ static ScriptCompiler::CachedData* LookupCodeCache(Isolate* isolate,
+ Local<Value> name);
+ static void StoreInCodeCache(Isolate* isolate, Local<Value> name,
+ const ScriptCompiler::CachedData* data);
+ // We may have multiple isolates running concurrently, so the access to
+ // the isolate_status_ needs to be concurrency-safe.
+ static base::LazyMutex isolate_status_lock_;
+ static std::map<Isolate*, bool> isolate_status_;
+
+ static base::LazyMutex cached_code_mutex_;
+ static std::map<std::string, std::unique_ptr<ScriptCompiler::CachedData>>
+ cached_code_map_;
+};
+
+} // namespace v8
+
+#endif // V8_D8_D8_H_