From ff88cf4e9b7fc19def1a784788b1862211330953 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sat, 15 Jul 2017 00:05:54 +0800 Subject: [PATCH 01/12] src: refactor contextify --- node.gyp | 1 + src/node_contextify.cc | 730 ++++++++++++++++++++--------------------- src/node_contextify.h | 111 +++++++ 3 files changed, 468 insertions(+), 374 deletions(-) create mode 100644 src/node_contextify.h diff --git a/node.gyp b/node.gyp index b0e4676a96e477..7ecf48e3bf1888 100644 --- a/node.gyp +++ b/node.gyp @@ -175,6 +175,7 @@ 'src/node_config.cc', 'src/node_constants.cc', 'src/node_contextify.cc', + 'src/node_contextify.h', 'src/node_debug_options.cc', 'src/node_file.cc', 'src/node_http_parser.cc', diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 792b1c4f8009f9..0b8f9c27f6c2fa 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -19,6 +19,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "node_contextify.h" #include "node.h" #include "node_internals.h" #include "node_watchdog.h" @@ -67,451 +68,432 @@ using v8::UnboundScript; using v8::Value; using v8::WeakCallbackInfo; -namespace { +namespace contextify { -class ContextifyContext { - protected: - // V8 reserves the first field in context objects for the debugger. We use the - // second field to hold a reference to the sandbox object. - enum { kSandboxObjectIndex = 1 }; +ContextifyContext::ContextifyContext(Environment* env, Local sandbox_obj) : env_(env) { + Local v8_context = CreateV8Context(env, sandbox_obj); + context_.Reset(env->isolate(), v8_context); - Environment* const env_; - Persistent context_; - - public: - ContextifyContext(Environment* env, Local sandbox_obj) : env_(env) { - Local v8_context = CreateV8Context(env, sandbox_obj); - context_.Reset(env->isolate(), v8_context); + // Allocation failure or maximum call stack size reached + if (context_.IsEmpty()) + return; + context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); + context_.MarkIndependent(); +} - // Allocation failure or maximum call stack size reached - if (context_.IsEmpty()) - return; - context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); - context_.MarkIndependent(); - } +ContextifyContext::~ContextifyContext() { + context_.Reset(); +} - ~ContextifyContext() { - context_.Reset(); - } +// XXX(isaacs): This function only exists because of a shortcoming of +// the V8 SetNamedPropertyHandler function. +// +// It does not provide a way to intercept Object.defineProperty(..) +// calls. As a result, these properties are not copied onto the +// contextified sandbox when a new global property is added via either +// a function declaration or a Object.defineProperty(global, ...) call. +// +// Note that any function declarations or Object.defineProperty() +// globals that are created asynchronously (in a setTimeout, callback, +// etc.) will happen AFTER the call to copy properties, and thus not be +// caught. +// +// The way to properly fix this is to add some sort of a +// Object::SetNamedDefinePropertyHandler() function that takes a callback, +// which receives the property name and property descriptor as arguments. +// +// Luckily, such situations are rare, and asynchronously-added globals +// weren't supported by Node's VM module until 0.12 anyway. But, this +// should be fixed properly in V8, and this copy function should be +// removed once there is a better way. +void ContextifyContext::CopyProperties() { + HandleScope scope(env()->isolate()); + + Local context = PersistentToLocal(env()->isolate(), context_); + Local global = + context->Global()->GetPrototype()->ToObject(env()->isolate()); + Local sandbox_obj = sandbox(); + + Local clone_property_method; + + Local names = global->GetOwnPropertyNames(); + int length = names->Length(); + for (int i = 0; i < length; i++) { + Local key = names->Get(i)->ToString(env()->isolate()); + Maybe has = sandbox_obj->HasOwnProperty(context, key); + + // Check for pending exceptions + if (has.IsNothing()) + return; - inline Environment* env() const { - return env_; + if (!has.FromJust()) { + Local desc_vm_context = + global->GetOwnPropertyDescriptor(context, key) + .ToLocalChecked().As(); + + bool is_accessor = + desc_vm_context->Has(context, env()->get_string()).FromJust() || + desc_vm_context->Has(context, env()->set_string()).FromJust(); + + auto define_property_on_sandbox = [&] (PropertyDescriptor* desc) { + desc->set_configurable(desc_vm_context + ->Get(context, env()->configurable_string()).ToLocalChecked() + ->BooleanValue(context).FromJust()); + desc->set_enumerable(desc_vm_context + ->Get(context, env()->enumerable_string()).ToLocalChecked() + ->BooleanValue(context).FromJust()); + CHECK(sandbox_obj->DefineProperty(context, key, *desc).FromJust()); + }; + + if (is_accessor) { + Local get = + desc_vm_context->Get(context, env()->get_string()) + .ToLocalChecked().As(); + Local set = + desc_vm_context->Get(context, env()->set_string()) + .ToLocalChecked().As(); + + PropertyDescriptor desc(get, set); + define_property_on_sandbox(&desc); + } else { + Local value = + desc_vm_context->Get(context, env()->value_string()) + .ToLocalChecked(); + + bool writable = + desc_vm_context->Get(context, env()->writable_string()) + .ToLocalChecked()->BooleanValue(context).FromJust(); + + PropertyDescriptor desc(value, writable); + define_property_on_sandbox(&desc); + } + } } +} - inline Local context() const { - return PersistentToLocal(env()->isolate(), context_); - } +// This is an object that just keeps an internal pointer to this +// ContextifyContext. It's passed to the NamedPropertyHandler. If we +// pass the main JavaScript context object we're embedded in, then the +// NamedPropertyHandler will store a reference to it forever and keep it +// from getting gc'd. +Local ContextifyContext::CreateDataWrapper(Environment* env) { + EscapableHandleScope scope(env->isolate()); + Local wrapper = + env->script_data_constructor_function() + ->NewInstance(env->context()).FromMaybe(Local()); + if (wrapper.IsEmpty()) + return scope.Escape(Local::New(env->isolate(), Local())); + + Wrap(wrapper, this); + return scope.Escape(wrapper); +} - inline Local global_proxy() const { - return context()->Global(); - } +Local ContextifyContext::CreateV8Context(Environment* env, Local sandbox_obj) { + EscapableHandleScope scope(env->isolate()); + Local function_template = + FunctionTemplate::New(env->isolate()); + function_template->SetHiddenPrototype(true); + function_template->SetClassName(sandbox_obj->GetConstructorName()); - inline Local sandbox() const { - return Local::Cast(context()->GetEmbedderData(kSandboxObjectIndex)); - } - - // XXX(isaacs): This function only exists because of a shortcoming of - // the V8 SetNamedPropertyHandler function. - // - // It does not provide a way to intercept Object.defineProperty(..) - // calls. As a result, these properties are not copied onto the - // contextified sandbox when a new global property is added via either - // a function declaration or a Object.defineProperty(global, ...) call. - // - // Note that any function declarations or Object.defineProperty() - // globals that are created asynchronously (in a setTimeout, callback, - // etc.) will happen AFTER the call to copy properties, and thus not be - // caught. - // - // The way to properly fix this is to add some sort of a - // Object::SetNamedDefinePropertyHandler() function that takes a callback, - // which receives the property name and property descriptor as arguments. - // - // Luckily, such situations are rare, and asynchronously-added globals - // weren't supported by Node's VM module until 0.12 anyway. But, this - // should be fixed properly in V8, and this copy function should be - // removed once there is a better way. - void CopyProperties() { - HandleScope scope(env()->isolate()); - - Local context = PersistentToLocal(env()->isolate(), context_); - Local global = - context->Global()->GetPrototype()->ToObject(env()->isolate()); - Local sandbox_obj = sandbox(); - - Local clone_property_method; - - Local names = global->GetOwnPropertyNames(); - int length = names->Length(); - for (int i = 0; i < length; i++) { - Local key = names->Get(i)->ToString(env()->isolate()); - Maybe has = sandbox_obj->HasOwnProperty(context, key); - - // Check for pending exceptions - if (has.IsNothing()) - return; + Local object_template = + function_template->InstanceTemplate(); - if (!has.FromJust()) { - Local desc_vm_context = - global->GetOwnPropertyDescriptor(context, key) - .ToLocalChecked().As(); - - bool is_accessor = - desc_vm_context->Has(context, env()->get_string()).FromJust() || - desc_vm_context->Has(context, env()->set_string()).FromJust(); - - auto define_property_on_sandbox = [&] (PropertyDescriptor* desc) { - desc->set_configurable(desc_vm_context - ->Get(context, env()->configurable_string()).ToLocalChecked() - ->BooleanValue(context).FromJust()); - desc->set_enumerable(desc_vm_context - ->Get(context, env()->enumerable_string()).ToLocalChecked() - ->BooleanValue(context).FromJust()); - CHECK(sandbox_obj->DefineProperty(context, key, *desc).FromJust()); - }; - - if (is_accessor) { - Local get = - desc_vm_context->Get(context, env()->get_string()) - .ToLocalChecked().As(); - Local set = - desc_vm_context->Get(context, env()->set_string()) - .ToLocalChecked().As(); - - PropertyDescriptor desc(get, set); - define_property_on_sandbox(&desc); - } else { - Local value = - desc_vm_context->Get(context, env()->value_string()) - .ToLocalChecked(); - - bool writable = - desc_vm_context->Get(context, env()->writable_string()) - .ToLocalChecked()->BooleanValue(context).FromJust(); - - PropertyDescriptor desc(value, writable); - define_property_on_sandbox(&desc); - } - } - } -} + NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback, + GlobalPropertySetterCallback, + GlobalPropertyQueryCallback, + GlobalPropertyDeleterCallback, + GlobalPropertyEnumeratorCallback, + CreateDataWrapper(env)); + object_template->SetHandler(config); + Local ctx = Context::New(env->isolate(), nullptr, object_template); - // This is an object that just keeps an internal pointer to this - // ContextifyContext. It's passed to the NamedPropertyHandler. If we - // pass the main JavaScript context object we're embedded in, then the - // NamedPropertyHandler will store a reference to it forever and keep it - // from getting gc'd. - Local CreateDataWrapper(Environment* env) { - EscapableHandleScope scope(env->isolate()); - Local wrapper = - env->script_data_constructor_function() - ->NewInstance(env->context()).FromMaybe(Local()); - if (wrapper.IsEmpty()) - return scope.Escape(Local::New(env->isolate(), Local())); - - Wrap(wrapper, this); - return scope.Escape(wrapper); + if (ctx.IsEmpty()) { + env->ThrowError("Could not instantiate context"); + return Local(); } + ctx->SetSecurityToken(env->context()->GetSecurityToken()); - Local CreateV8Context(Environment* env, Local sandbox_obj) { - EscapableHandleScope scope(env->isolate()); - Local function_template = - FunctionTemplate::New(env->isolate()); - function_template->SetHiddenPrototype(true); - - function_template->SetClassName(sandbox_obj->GetConstructorName()); + // We need to tie the lifetime of the sandbox object with the lifetime of + // newly created context. We do this by making them hold references to each + // other. The context can directly hold a reference to the sandbox as an + // embedder data field. However, we cannot hold a reference to a v8::Context + // directly in an Object, we instead hold onto the new context's global + // object instead (which then has a reference to the context). + ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj); + sandbox_obj->SetPrivate(env->context(), + env->contextify_global_private_symbol(), + ctx->Global()); - Local object_template = - function_template->InstanceTemplate(); + env->AssignToContext(ctx); - NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback, - GlobalPropertySetterCallback, - GlobalPropertyQueryCallback, - GlobalPropertyDeleterCallback, - GlobalPropertyEnumeratorCallback, - CreateDataWrapper(env)); - object_template->SetHandler(config); + return scope.Escape(ctx); +} - Local ctx = Context::New(env->isolate(), nullptr, object_template); - if (ctx.IsEmpty()) { - env->ThrowError("Could not instantiate context"); - return Local(); - } +// static +void ContextifyContext::Init(Environment* env, Local target) { + Local function_template = + FunctionTemplate::New(env->isolate()); + function_template->InstanceTemplate()->SetInternalFieldCount(1); + env->set_script_data_constructor_function(function_template->GetFunction()); - ctx->SetSecurityToken(env->context()->GetSecurityToken()); + env->SetMethod(target, "runInDebugContext", RunInDebugContext); + env->SetMethod(target, "makeContext", MakeContext); + env->SetMethod(target, "isContext", IsContext); +} - // We need to tie the lifetime of the sandbox object with the lifetime of - // newly created context. We do this by making them hold references to each - // other. The context can directly hold a reference to the sandbox as an - // embedder data field. However, we cannot hold a reference to a v8::Context - // directly in an Object, we instead hold onto the new context's global - // object instead (which then has a reference to the context). - ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj); - sandbox_obj->SetPrivate(env->context(), - env->contextify_global_private_symbol(), - ctx->Global()); - env->AssignToContext(ctx); +// static +void ContextifyContext::RunInDebugContext(const FunctionCallbackInfo& args) { + Local script_source(args[0]->ToString(args.GetIsolate())); + if (script_source.IsEmpty()) + return; // Exception pending. + Local debug_context = Debug::GetDebugContext(args.GetIsolate()); + Environment* env = Environment::GetCurrent(args); + if (debug_context.IsEmpty()) { + // Force-load the debug context. + auto dummy_event_listener = [] (const Debug::EventDetails&) {}; + Debug::SetDebugEventListener(args.GetIsolate(), dummy_event_listener); + debug_context = Debug::GetDebugContext(args.GetIsolate()); + CHECK(!debug_context.IsEmpty()); + // Ensure that the debug context has an Environment assigned in case + // a fatal error is raised. The fatal exception handler in node.cc + // is not equipped to deal with contexts that don't have one and + // can't easily be taught that due to a deficiency in the V8 API: + // there is no way for the embedder to tell if the data index is + // in use. + const int index = Environment::kContextEmbedderDataIndex; + debug_context->SetAlignedPointerInEmbedderData(index, env); + } + + Context::Scope context_scope(debug_context); + MaybeLocal