From cfd25e0c7486f73068defe4b85160306a74d6417 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 16 Aug 2022 23:22:32 +0800 Subject: [PATCH] vm: make ContextifyContext template context-independent Instead of creating an object template for every ContextifyContext, we now create one object template that can be reused by all contexts. The native pointer can be obtained through an embdder pointer field in the creation context of the receiver in the interceptors, because the interceptors are only meant to be invoked on the global object of the contextified contexts. This makes the ContextifyContext template context-independent and therefore snapshotable. PR-URL: https://github.com/nodejs/node/pull/44252 Refs: https://github.com/nodejs/node/issues/44014 Refs: https://github.com/nodejs/node/issues/37476 Reviewed-By: Chengzhong Wu --- src/env.cc | 11 +- src/env.h | 3 +- src/node_context_data.h | 7 +- src/node_contextify.cc | 226 +++++++++++++++++++--------------- src/node_contextify.h | 21 ++-- src/node_external_reference.h | 7 +- 6 files changed, 162 insertions(+), 113 deletions(-) diff --git a/src/env.cc b/src/env.cc index c0c9f17cdc71aa..9bc9346e653fe1 100644 --- a/src/env.cc +++ b/src/env.cc @@ -6,6 +6,7 @@ #include "memory_tracker-inl.h" #include "node_buffer.h" #include "node_context_data.h" +#include "node_contextify.h" #include "node_errors.h" #include "node_internals.h" #include "node_options-inl.h" @@ -444,6 +445,8 @@ void IsolateData::CreateProperties() { #undef V // TODO(legendecas): eagerly create per isolate templates. + set_contextify_global_template( + contextify::ContextifyContext::CreateGlobalTemplate(isolate_)); } IsolateData::IsolateData(Isolate* isolate, @@ -512,13 +515,19 @@ void TrackingTraceStateObserver::UpdateTraceCategoryState() { void Environment::AssignToContext(Local context, const ContextInfo& info) { - ContextEmbedderTag::TagNodeContext(context); context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kEnvironment, this); // Used to retrieve bindings context->SetAlignedPointerInEmbedderData( ContextEmbedderIndex::kBindingListIndex, &(this->bindings_)); + // ContextifyContexts will update this to a pointer to the native object. + context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextifyContext, nullptr); + + // This must not be done before other context fields are initialized. + ContextEmbedderTag::TagNodeContext(context); + #if HAVE_INSPECTOR inspector_agent()->ContextCreated(context, info); #endif // HAVE_INSPECTOR diff --git a/src/env.h b/src/env.h index 42c5ef888b2f49..ef5850ee3b3aef 100644 --- a/src/env.h +++ b/src/env.h @@ -349,6 +349,7 @@ class NoArrayBufferZeroFillScope { V(nistcurve_string, "nistCurve") \ V(node_string, "node") \ V(nsname_string, "nsname") \ + V(object_string, "Object") \ V(ocsp_request_string, "OCSPRequest") \ V(oncertcb_string, "oncertcb") \ V(onchange_string, "onchange") \ @@ -477,6 +478,7 @@ class NoArrayBufferZeroFillScope { V(binding_data_ctor_template, v8::FunctionTemplate) \ V(blob_constructor_template, v8::FunctionTemplate) \ V(blocklist_constructor_template, v8::FunctionTemplate) \ + V(contextify_global_template, v8::ObjectTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ V(fd_constructor_template, v8::ObjectTemplate) \ @@ -560,7 +562,6 @@ class NoArrayBufferZeroFillScope { V(primordials_safe_weak_set_prototype_object, v8::Object) \ V(promise_hook_handler, v8::Function) \ V(promise_reject_callback, v8::Function) \ - V(script_data_constructor_function, v8::Function) \ V(snapshot_serialize_callback, v8::Function) \ V(snapshot_deserialize_callback, v8::Function) \ V(snapshot_deserialize_main, v8::Function) \ diff --git a/src/node_context_data.h b/src/node_context_data.h index 632b648a0a1ef7..3fe425bdf4cff6 100644 --- a/src/node_context_data.h +++ b/src/node_context_data.h @@ -32,11 +32,15 @@ namespace node { #define NODE_CONTEXT_ALLOW_CODE_GENERATION_FROM_STRINGS_INDEX 36 #endif +#ifndef NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX +#define NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX 37 +#endif + // NODE_CONTEXT_TAG must be greater than any embedder indexes so that a single // check on the number of embedder data fields can assure the presence of all // embedder indexes. #ifndef NODE_CONTEXT_TAG -#define NODE_CONTEXT_TAG 37 +#define NODE_CONTEXT_TAG 38 #endif enum ContextEmbedderIndex { @@ -46,6 +50,7 @@ enum ContextEmbedderIndex { kBindingListIndex = NODE_BINDING_LIST_INDEX, kAllowCodeGenerationFromStrings = NODE_CONTEXT_ALLOW_CODE_GENERATION_FROM_STRINGS_INDEX, + kContextifyContext = NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX, kContextTag = NODE_CONTEXT_TAG, }; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e1fc5e5513d6ab..9f166f20e3c11c 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -113,12 +113,26 @@ ContextifyContext::ContextifyContext( const ContextOptions& options) : env_(env), microtask_queue_wrap_(options.microtask_queue_wrap) { - MaybeLocal v8_context = CreateV8Context(env, sandbox_obj, options); + Local object_template = env->contextify_global_template(); + if (object_template.IsEmpty()) { + object_template = CreateGlobalTemplate(env->isolate()); + env->set_contextify_global_template(object_template); + } + + MicrotaskQueue* queue = + microtask_queue() + ? microtask_queue().get() + : env->isolate()->GetCurrentContext()->GetMicrotaskQueue(); - // Allocation failure, maximum call stack size reached, termination, etc. - if (v8_context.IsEmpty()) return; + Local v8_context; + if (!(CreateV8Context(env->isolate(), object_template, queue) + .ToLocal(&v8_context)) || + !InitializeContext(v8_context, env, sandbox_obj, options)) { + // Allocation failure, maximum call stack size reached, termination, etc. + return; + } - context_.Reset(env->isolate(), v8_context.ToLocalChecked()); + context_.Reset(env->isolate(), v8_context); context_.SetWeak(this, WeakCallback, WeakCallbackType::kParameter); env->AddCleanupHook(CleanupHook, this); } @@ -140,41 +154,13 @@ void ContextifyContext::CleanupHook(void* arg) { delete self; } - -// 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. -MaybeLocal ContextifyContext::CreateDataWrapper(Environment* env) { - Local wrapper; - if (!env->script_data_constructor_function() - ->NewInstance(env->context()) - .ToLocal(&wrapper)) { - return MaybeLocal(); - } - - wrapper->SetAlignedPointerInInternalField(ContextifyContext::kSlot, this); - return wrapper; -} - -MaybeLocal ContextifyContext::CreateV8Context( - Environment* env, - Local sandbox_obj, - const ContextOptions& options) { - EscapableHandleScope scope(env->isolate()); - Local function_template = - FunctionTemplate::New(env->isolate()); - - function_template->SetClassName(sandbox_obj->GetConstructorName()); +Local ContextifyContext::CreateGlobalTemplate( + Isolate* isolate) { + Local function_template = FunctionTemplate::New(isolate); Local object_template = function_template->InstanceTemplate(); - Local data_wrapper; - if (!CreateDataWrapper(env).ToLocal(&data_wrapper)) - return MaybeLocal(); - NamedPropertyHandlerConfiguration config( PropertyGetterCallback, PropertySetterCallback, @@ -182,7 +168,7 @@ MaybeLocal ContextifyContext::CreateV8Context( PropertyDeleterCallback, PropertyEnumeratorCallback, PropertyDefinerCallback, - data_wrapper, + {}, PropertyHandlerFlags::kHasNoSideEffect); IndexedPropertyHandlerConfiguration indexed_config( @@ -192,33 +178,48 @@ MaybeLocal ContextifyContext::CreateV8Context( IndexedPropertyDeleterCallback, PropertyEnumeratorCallback, IndexedPropertyDefinerCallback, - data_wrapper, + {}, PropertyHandlerFlags::kHasNoSideEffect); object_template->SetHandler(config); object_template->SetHandler(indexed_config); - Local ctx = Context::New( - env->isolate(), - nullptr, // extensions - object_template, - {}, // global object - {}, // deserialization callback - microtask_queue() ? - microtask_queue().get() : - env->isolate()->GetCurrentContext()->GetMicrotaskQueue()); + + return object_template; +} + +MaybeLocal ContextifyContext::CreateV8Context( + Isolate* isolate, + Local object_template, + MicrotaskQueue* queue) { + EscapableHandleScope scope(isolate); + + Local ctx = Context::New(isolate, + nullptr, // extensions + object_template, + {}, // global object + {}, // deserialization callback + queue); if (ctx.IsEmpty()) return MaybeLocal(); - // Only partially initialize the context - the primordials are left out - // and only initialized when necessary. - if (InitializeContextRuntime(ctx).IsNothing()) { - return MaybeLocal(); - } - if (ctx.IsEmpty()) { - return MaybeLocal(); + return scope.Escape(ctx); +} + +bool ContextifyContext::InitializeContext(Local ctx, + Environment* env, + Local sandbox_obj, + const ContextOptions& options) { + HandleScope scope(env->isolate()); + + // This only initializes part of the context. The primordials are + // only initilaized when needed because even deserializing them slows + // things down significantly and they are only needed in rare occasions + // in the vm contexts. + if (InitializeContextRuntime(ctx).IsNothing()) { + return false; } - Local context = env->context(); - ctx->SetSecurityToken(context->GetSecurityToken()); + Local main_context = env->context(); + ctx->SetSecurityToken(main_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 @@ -227,11 +228,9 @@ MaybeLocal ContextifyContext::CreateV8Context( // 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(ContextEmbedderIndex::kSandboxObject, sandbox_obj); - sandbox_obj->SetPrivate(context, - env->contextify_global_private_symbol(), - ctx->Global()); + sandbox_obj->SetPrivate( + main_context, env->contextify_global_private_symbol(), ctx->Global()); - Utf8Value name_val(env->isolate(), options.name); // Delegate the code generation validation to // node::ModifyCodeGenerationFromStrings. ctx->AllowCodeGenerationFromStrings(false); @@ -240,30 +239,39 @@ MaybeLocal ContextifyContext::CreateV8Context( ctx->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, options.allow_code_gen_wasm); + Utf8Value name_val(env->isolate(), options.name); ContextInfo info(*name_val); - if (!options.origin.IsEmpty()) { Utf8Value origin_val(env->isolate(), options.origin); info.origin = *origin_val; } + { + Context::Scope context_scope(ctx); + Local ctor_name = sandbox_obj->GetConstructorName(); + if (!ctor_name->Equals(ctx, env->object_string()).FromMaybe(false) && + ctx->Global() + ->DefineOwnProperty( + ctx, + v8::Symbol::GetToStringTag(env->isolate()), + ctor_name, + static_cast(v8::DontEnum)) + .IsNothing()) { + return false; + } + } + env->AssignToContext(ctx, info); - return scope.Escape(ctx); + // This should only be done after the initial initializations of the context + // global object is finished. + ctx->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kContextifyContext, + this); + return true; } - void ContextifyContext::Init(Environment* env, Local target) { - Isolate* isolate = env->isolate(); Local context = env->context(); - - Local function_template = - NewFunctionTemplate(isolate, nullptr); - function_template->InstanceTemplate()->SetInternalFieldCount( - ContextifyContext::kInternalFieldCount); - env->set_script_data_constructor_function( - function_template->GetFunction(env->context()).ToLocalChecked()); - SetMethod(context, target, "makeContext", MakeContext); SetMethod(context, target, "isContext", IsContext); SetMethod(context, target, "compileFunction", CompileFunction); @@ -274,6 +282,17 @@ void ContextifyContext::RegisterExternalReferences( registry->Register(MakeContext); registry->Register(IsContext); registry->Register(CompileFunction); + registry->Register(PropertyGetterCallback); + registry->Register(PropertySetterCallback); + registry->Register(PropertyDescriptorCallback); + registry->Register(PropertyDeleterCallback); + registry->Register(PropertyEnumeratorCallback); + registry->Register(PropertyDefinerCallback); + registry->Register(IndexedPropertyGetterCallback); + registry->Register(IndexedPropertySetterCallback); + registry->Register(IndexedPropertyDescriptorCallback); + registry->Register(IndexedPropertyDeleterCallback); + registry->Register(IndexedPropertyDefinerCallback); } // makeContext(sandbox, name, origin, strings, wasm); @@ -323,8 +342,8 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo& args) { return; } - if (context_ptr->context().IsEmpty()) - return; + Local new_context = context_ptr->context(); + if (new_context.IsEmpty()) return; sandbox->SetPrivate( env->context(), @@ -371,10 +390,20 @@ ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox( // static template ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo& args) { - Local data = args.Data(); + Local context; + if (!args.This()->GetCreationContext().ToLocal(&context)) { + return nullptr; + } + if (!ContextEmbedderTag::IsNodeContext(context)) { + return nullptr; + } return static_cast( - data.As()->GetAlignedPointerFromInternalField( - ContextifyContext::kSlot)); + context->GetAlignedPointerFromEmbedderData( + ContextEmbedderIndex::kContextifyContext)); +} + +bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) { + return ctx == nullptr || ctx->context_.IsEmpty(); } // static @@ -384,8 +413,7 @@ void ContextifyContext::PropertyGetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); Local sandbox = ctx->sandbox(); @@ -413,8 +441,7 @@ void ContextifyContext::PropertySetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); PropertyAttribute attributes = PropertyAttribute::None; @@ -466,8 +493,7 @@ void ContextifyContext::PropertyDescriptorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); @@ -489,8 +515,7 @@ void ContextifyContext::PropertyDefinerCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); Isolate* isolate = context->GetIsolate(); @@ -550,8 +575,7 @@ void ContextifyContext::PropertyDeleterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Maybe success = ctx->sandbox()->Delete(ctx->context(), property); @@ -569,8 +593,7 @@ void ContextifyContext::PropertyEnumeratorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local properties; @@ -587,8 +610,7 @@ void ContextifyContext::IndexedPropertyGetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyGetterCallback( Uint32ToName(ctx->context(), index), args); @@ -602,8 +624,7 @@ void ContextifyContext::IndexedPropertySetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertySetterCallback( Uint32ToName(ctx->context(), index), value, args); @@ -616,8 +637,7 @@ void ContextifyContext::IndexedPropertyDescriptorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyDescriptorCallback( Uint32ToName(ctx->context(), index), args); @@ -631,8 +651,7 @@ void ContextifyContext::IndexedPropertyDefinerCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyDefinerCallback( Uint32ToName(ctx->context(), index), desc, args); @@ -645,8 +664,7 @@ void ContextifyContext::IndexedPropertyDeleterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Maybe success = ctx->sandbox()->Delete(ctx->context(), index); @@ -891,8 +909,8 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { bool break_on_first_line = args[4]->IsTrue(); // Do the eval within the context - Context::Scope context_scope(context); - EvalMachine(env, + EvalMachine(context, + env, timeout, display_errors, break_on_sigint, @@ -901,13 +919,16 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { args); } -bool ContextifyScript::EvalMachine(Environment* env, +bool ContextifyScript::EvalMachine(Local context, + Environment* env, const int64_t timeout, const bool display_errors, const bool break_on_sigint, const bool break_on_first_line, std::shared_ptr mtask_queue, const FunctionCallbackInfo& args) { + Context::Scope context_scope(context); + if (!env->can_call_into_js()) return false; if (!ContextifyScript::InstanceOf(env, args.Holder())) { @@ -916,6 +937,7 @@ bool ContextifyScript::EvalMachine(Environment* env, "Script methods can only be called on script instances."); return false; } + TryCatchScope try_catch(env); Isolate::SafeForTerminationScope safe_for_termination(env->isolate()); ContextifyScript* wrapped_script; @@ -934,7 +956,7 @@ bool ContextifyScript::EvalMachine(Environment* env, bool timed_out = false; bool received_signal = false; auto run = [&]() { - MaybeLocal result = script->Run(env->context()); + MaybeLocal result = script->Run(context); if (!result.IsEmpty() && mtask_queue) mtask_queue->PerformCheckpoint(env->isolate()); return result; diff --git a/src/node_contextify.h b/src/node_contextify.h index c9b5fc0f62dcfc..088b5c5a56192c 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -43,17 +43,20 @@ struct ContextOptions { class ContextifyContext { public: - enum InternalFields { kSlot, kInternalFieldCount }; ContextifyContext(Environment* env, v8::Local sandbox_obj, const ContextOptions& options); ~ContextifyContext(); static void CleanupHook(void* arg); - v8::MaybeLocal CreateDataWrapper(Environment* env); - v8::MaybeLocal CreateV8Context(Environment* env, - v8::Local sandbox_obj, - const ContextOptions& options); + static v8::MaybeLocal CreateV8Context( + v8::Isolate* isolate, + v8::Local object_template, + v8::MicrotaskQueue* queue); + bool InitializeContext(v8::Local ctx, + Environment* env, + v8::Local sandbox_obj, + const ContextOptions& options); static void Init(Environment* env, v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); @@ -83,11 +86,14 @@ class ContextifyContext { return microtask_queue_wrap_->microtask_queue(); } - template static ContextifyContext* Get(const v8::PropertyCallbackInfo& args); + static v8::Local CreateGlobalTemplate( + v8::Isolate* isolate); + private: + static bool IsStillInitializing(const ContextifyContext* ctx); static void MakeContext(const v8::FunctionCallbackInfo& args); static void IsContext(const v8::FunctionCallbackInfo& args); static void CompileFunction( @@ -150,7 +156,8 @@ class ContextifyScript : public BaseObject { static bool InstanceOf(Environment* env, const v8::Local& args); static void CreateCachedData(const v8::FunctionCallbackInfo& args); static void RunInContext(const v8::FunctionCallbackInfo& args); - static bool EvalMachine(Environment* env, + static bool EvalMachine(v8::Local context, + Environment* env, const int64_t timeout, const bool display_errors, const bool break_on_sigint, diff --git a/src/node_external_reference.h b/src/node_external_reference.h index b7d7a63a8a88a6..a77c1ab14ee0de 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -30,7 +30,12 @@ class ExternalReferenceRegistry { V(v8::GenericNamedPropertyDeleterCallback) \ V(v8::GenericNamedPropertyEnumeratorCallback) \ V(v8::GenericNamedPropertyQueryCallback) \ - V(v8::GenericNamedPropertySetterCallback) + V(v8::GenericNamedPropertySetterCallback) \ + V(v8::IndexedPropertySetterCallback) \ + V(v8::IndexedPropertyDefinerCallback) \ + V(v8::IndexedPropertyDeleterCallback) \ + V(v8::IndexedPropertyQueryCallback) \ + V(v8::IndexedPropertyDescriptorCallback) #define V(ExternalReferenceType) \ void Register(ExternalReferenceType addr) { RegisterT(addr); }