diff options
Diffstat (limited to 'deps/v8/src/d8')
-rw-r--r-- | deps/v8/src/d8/OWNERS | 5 | ||||
-rw-r--r-- | deps/v8/src/d8/async-hooks-wrapper.cc | 294 | ||||
-rw-r--r-- | deps/v8/src/d8/async-hooks-wrapper.h | 96 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-console.cc | 142 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-console.h | 47 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-js.cc | 101 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-platforms.cc | 309 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-platforms.h | 29 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-posix.cc | 854 | ||||
-rw-r--r-- | deps/v8/src/d8/d8-windows.cc | 16 | ||||
-rw-r--r-- | deps/v8/src/d8/d8.cc | 3572 | ||||
-rw-r--r-- | deps/v8/src/d8/d8.h | 550 |
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_ |