diff --git a/node.gyp b/node.gyp index 5a727eb0c92d34..15eb66a4094905 100644 --- a/node.gyp +++ b/node.gyp @@ -344,6 +344,7 @@ 'src/node.h', 'src/node_buffer.h', 'src/node_constants.h', + 'src/node_contextify.h', 'src/node_debug_options.h', 'src/node_http2.h', 'src/node_http2_state.h', diff --git a/src/node_contextify.cc b/src/node_contextify.cc index d50c6023dc8349..6fe9276d82ecab 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -23,8 +23,10 @@ #include "node_watchdog.h" #include "base_object-inl.h" #include "v8-debug.h" +#include "node_contextify.h" namespace node { +namespace contextify { using v8::Array; using v8::ArrayBuffer; @@ -92,550 +94,531 @@ Local Uint32ToName(Local context, uint32_t index) { .ToLocalChecked(); } -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 }; - - Environment* const env_; - Persistent context_; - - public: - ContextifyContext(Environment* env, - Local sandbox_obj, - Local options_obj) - : env_(env) { - Local v8_context = CreateV8Context(env, sandbox_obj, options_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(); - } - - - ~ContextifyContext() { - context_.Reset(); - } - +} // anonymous namespace - inline Environment* env() const { - return env_; - } +ContextifyContext::ContextifyContext( + Environment* env, + Local sandbox_obj, Local options_obj) : env_(env) { + Local v8_context = CreateV8Context(env, sandbox_obj, options_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(); +} - inline Local context() const { - return PersistentToLocal(env()->isolate(), context_); - } +ContextifyContext::~ContextifyContext() { + context_.Reset(); +} - inline Local global_proxy() const { - return context()->Global(); - } +// 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 sandbox() const { - return Local::Cast(context()->GetEmbedderData(kSandboxObjectIndex)); +Local ContextifyContext::CreateV8Context( + Environment* env, + Local sandbox_obj, + Local options_obj) { + EscapableHandleScope scope(env->isolate()); + Local function_template = + FunctionTemplate::New(env->isolate()); + + function_template->SetClassName(sandbox_obj->GetConstructorName()); + + Local object_template = + function_template->InstanceTemplate(); + + NamedPropertyHandlerConfiguration config(PropertyGetterCallback, + PropertySetterCallback, + PropertyDescriptorCallback, + PropertyDeleterCallback, + PropertyEnumeratorCallback, + PropertyDefinerCallback, + CreateDataWrapper(env)); + + IndexedPropertyHandlerConfiguration indexed_config( + IndexedPropertyGetterCallback, + IndexedPropertySetterCallback, + IndexedPropertyDescriptorCallback, + IndexedPropertyDeleterCallback, + PropertyEnumeratorCallback, + IndexedPropertyDefinerCallback, + CreateDataWrapper(env)); + + object_template->SetHandler(config); + object_template->SetHandler(indexed_config); + + Local ctx = NewContext(env->isolate(), object_template); + + if (ctx.IsEmpty()) { + env->ThrowError("Could not instantiate context"); + return Local(); } - // 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); + ctx->SetSecurityToken(env->context()->GetSecurityToken()); + + // 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 name = + options_obj->Get(env->context(), env->name_string()) + .ToLocalChecked(); + CHECK(name->IsString()); + Utf8Value name_val(env->isolate(), name); + + ContextInfo info(*name_val); + + Local origin = + options_obj->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "origin")) + .ToLocalChecked(); + if (!origin->IsUndefined()) { + CHECK(origin->IsString()); + Utf8Value origin_val(env->isolate(), origin); + info.origin = *origin_val; } + env->AssignToContext(ctx, info); - Local CreateV8Context(Environment* env, - Local sandbox_obj, - Local options_obj) { - EscapableHandleScope scope(env->isolate()); - Local function_template = - FunctionTemplate::New(env->isolate()); - - function_template->SetClassName(sandbox_obj->GetConstructorName()); - - Local object_template = - function_template->InstanceTemplate(); - - NamedPropertyHandlerConfiguration config(PropertyGetterCallback, - PropertySetterCallback, - PropertyDescriptorCallback, - PropertyDeleterCallback, - PropertyEnumeratorCallback, - PropertyDefinerCallback, - CreateDataWrapper(env)); - - IndexedPropertyHandlerConfiguration indexed_config( - IndexedPropertyGetterCallback, - IndexedPropertySetterCallback, - IndexedPropertyDescriptorCallback, - IndexedPropertyDeleterCallback, - PropertyEnumeratorCallback, - IndexedPropertyDefinerCallback, - CreateDataWrapper(env)); - - object_template->SetHandler(config); - object_template->SetHandler(indexed_config); + return scope.Escape(ctx); +} - Local ctx = NewContext(env->isolate(), object_template); - if (ctx.IsEmpty()) { - env->ThrowError("Could not instantiate context"); - return Local(); - } +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()); - - // 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 name = - options_obj->Get(env->context(), env->name_string()) - .ToLocalChecked(); - CHECK(name->IsString()); - Utf8Value name_val(env->isolate(), name); - - ContextInfo info(*name_val); - - Local origin = - options_obj->Get(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "origin")) - .ToLocalChecked(); - if (!origin->IsUndefined()) { - CHECK(origin->IsString()); - Utf8Value origin_val(env->isolate(), origin); - info.origin = *origin_val; - } + env->SetMethod(target, "runInDebugContext", RunInDebugContext); + env->SetMethod(target, "makeContext", MakeContext); + env->SetMethod(target, "isContext", IsContext); +} - env->AssignToContext(ctx, info); - return scope.Escape(ctx); +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); } - - static void 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()); - - env->SetMethod(target, "runInDebugContext", RunInDebugContext); - env->SetMethod(target, "makeContext", MakeContext); - env->SetMethod(target, "isContext", IsContext); - } + Context::Scope context_scope(debug_context); + MaybeLocal