diff --git a/benchmark/vm/context-global-proxy.js b/benchmark/vm/context-global-proxy.js new file mode 100644 index 00000000000000..3f461671b195e1 --- /dev/null +++ b/benchmark/vm/context-global-proxy.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [100000], +}); + +const vm = require('vm'); +const script = new vm.Script(` + globalThis.foo++; +`); +const context = vm.createContext({ foo: 1 }); + +function main({ n }) { + bench.start(); + for (let i = 0; i < n; i++) { + script.runInContext(context); + } + bench.end(n); +} diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 52f3c4e16b8382..d0bea8df8ba1d9 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -73,14 +73,24 @@ Realm* BaseObject::realm() const { return realm_; } +void BaseObject::TagNodeObject(v8::Local object) { + DCHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount); + object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, + &kNodeEmbedderId); +} + +void BaseObject::SetInternalFields(v8::Local object, void* slot) { + TagNodeObject(object); + object->SetAlignedPointerInInternalField(BaseObject::kSlot, slot); +} + BaseObject* BaseObject::FromJSObject(v8::Local value) { v8::Local obj = value.As(); - DCHECK_GE(obj->InternalFieldCount(), BaseObject::kSlot); + DCHECK_GE(obj->InternalFieldCount(), BaseObject::kInternalFieldCount); return static_cast( obj->GetAlignedPointerFromInternalField(BaseObject::kSlot)); } - template T* BaseObject::FromJSObject(v8::Local object) { return static_cast(FromJSObject(object)); diff --git a/src/base_object.cc b/src/base_object.cc index cdf1285bef06fa..d0444573c85ac7 100644 --- a/src/base_object.cc +++ b/src/base_object.cc @@ -17,10 +17,7 @@ BaseObject::BaseObject(Realm* realm, Local object) : persistent_handle_(realm->isolate(), object), realm_(realm) { CHECK_EQ(false, object.IsEmpty()); CHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount); - object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, - &kNodeEmbedderId); - object->SetAlignedPointerInInternalField(BaseObject::kSlot, - static_cast(this)); + SetInternalFields(object, static_cast(this)); realm->AddCleanupHook(DeleteMe, static_cast(this)); realm->modify_base_object_count(1); } @@ -80,16 +77,19 @@ void BaseObject::LazilyInitializedJSTemplateConstructor( const FunctionCallbackInfo& args) { DCHECK(args.IsConstructCall()); CHECK_GE(args.This()->InternalFieldCount(), BaseObject::kInternalFieldCount); - args.This()->SetAlignedPointerInInternalField(BaseObject::kEmbedderType, - &kNodeEmbedderId); - args.This()->SetAlignedPointerInInternalField(BaseObject::kSlot, nullptr); + SetInternalFields(args.This(), nullptr); } Local BaseObject::MakeLazilyInitializedJSTemplate( Environment* env) { + return MakeLazilyInitializedJSTemplate(env->isolate_data()); +} + +Local BaseObject::MakeLazilyInitializedJSTemplate( + IsolateData* isolate_data) { Local t = NewFunctionTemplate( - env->isolate(), LazilyInitializedJSTemplateConstructor); - t->Inherit(BaseObject::GetConstructorTemplate(env)); + isolate_data->isolate(), LazilyInitializedJSTemplateConstructor); + t->Inherit(BaseObject::GetConstructorTemplate(isolate_data)); t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount); return t; } diff --git a/src/base_object.h b/src/base_object.h index 4c07ef765dc006..2f0d8cec45fd10 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -73,6 +73,9 @@ class BaseObject : public MemoryRetainer { // was also passed to the `BaseObject()` constructor initially. // This may return `nullptr` if the C++ object has not been constructed yet, // e.g. when the JS object used `MakeLazilyInitializedJSTemplate`. + static inline void SetInternalFields(v8::Local object, + void* slot); + static inline void TagNodeObject(v8::Local object); static void LazilyInitializedJSTemplateConstructor( const v8::FunctionCallbackInfo& args); static inline BaseObject* FromJSObject(v8::Local object); @@ -96,7 +99,8 @@ class BaseObject : public MemoryRetainer { // Utility to create a FunctionTemplate with one internal field (used for // the `BaseObject*` pointer) and a constructor that initializes that field // to `nullptr`. - // TODO(legendecas): Disentangle template with env. + static v8::Local MakeLazilyInitializedJSTemplate( + IsolateData* isolate); static v8::Local MakeLazilyInitializedJSTemplate( Environment* env); diff --git a/src/env.cc b/src/env.cc index 20e817b50d66f0..88106c80f3a9a5 100644 --- a/src/env.cc +++ b/src/env.cc @@ -451,8 +451,7 @@ void IsolateData::CreateProperties() { templ->Inherit(BaseObject::GetConstructorTemplate(this)); set_binding_data_ctor_template(templ); - set_contextify_global_template( - contextify::ContextifyContext::CreateGlobalTemplate(isolate_)); + contextify::ContextifyContext::InitializeGlobalTemplates(this); } IsolateData::IsolateData(Isolate* isolate, diff --git a/src/env_properties.h b/src/env_properties.h index e650e4926fb3df..76c52f1ea57385 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -332,6 +332,7 @@ V(blob_constructor_template, v8::FunctionTemplate) \ V(blocklist_constructor_template, v8::FunctionTemplate) \ V(contextify_global_template, v8::ObjectTemplate) \ + V(contextify_wrapper_template, v8::ObjectTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ V(fd_constructor_template, v8::ObjectTemplate) \ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index c2cfe5071d0094..efc09a7a0faa53 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -42,7 +42,6 @@ using v8::ArrayBufferView; using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; -using v8::External; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -108,62 +107,77 @@ Local Uint32ToName(Local context, uint32_t index) { } // anonymous namespace -ContextifyContext::ContextifyContext( +BaseObjectPtr ContextifyContext::New( Environment* env, Local sandbox_obj, - const ContextOptions& options) - : env_(env), - microtask_queue_wrap_(options.microtask_queue_wrap) { + const ContextOptions& options) { + HandleScope scope(env->isolate()); + InitializeGlobalTemplates(env->isolate_data()); Local object_template = env->contextify_global_template(); - if (object_template.IsEmpty()) { - object_template = CreateGlobalTemplate(env->isolate()); - env->set_contextify_global_template(object_template); - } + DCHECK(!object_template.IsEmpty()); bool use_node_snapshot = per_process::cli_options->node_snapshot; const SnapshotData* snapshot_data = use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr; MicrotaskQueue* queue = - microtask_queue() - ? microtask_queue().get() + options.microtask_queue_wrap + ? options.microtask_queue_wrap->microtask_queue().get() : env->isolate()->GetCurrentContext()->GetMicrotaskQueue(); Local v8_context; if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue) - .ToLocal(&v8_context)) || - !InitializeContext(v8_context, env, sandbox_obj, options)) { + .ToLocal(&v8_context))) { // Allocation failure, maximum call stack size reached, termination, etc. - return; + return BaseObjectPtr(); } + return New(v8_context, env, sandbox_obj, options); +} - context_.Reset(env->isolate(), v8_context); - context_.SetWeak(this, WeakCallback, WeakCallbackType::kParameter); - env->AddCleanupHook(CleanupHook, this); +void ContextifyContext::MemoryInfo(MemoryTracker* tracker) const { + if (microtask_queue_wrap_) { + tracker->TrackField("microtask_queue_wrap", + microtask_queue_wrap_->object()); + } } +ContextifyContext::ContextifyContext(Environment* env, + Local wrapper, + Local v8_context, + const ContextOptions& options) + : BaseObject(env, wrapper), + microtask_queue_wrap_(options.microtask_queue_wrap) { + context_.Reset(env->isolate(), v8_context); + // This should only be done after the initial initializations of the context + // global object is finished. + DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData( + ContextEmbedderIndex::kContextifyContext)); + v8_context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextifyContext, this); + // It's okay to make this reference weak - V8 would create an internal + // reference to this context via the constructor of the wrapper. + // As long as the wrapper is alive, it's constructor is alive, and so + // is the context. + context_.SetWeak(); +} ContextifyContext::~ContextifyContext() { - env()->RemoveCleanupHook(CleanupHook, this); Isolate* isolate = env()->isolate(); HandleScope scope(isolate); env()->async_hooks() ->RemoveContext(PersistentToLocal::Weak(isolate, context_)); + context_.Reset(); } - -void ContextifyContext::CleanupHook(void* arg) { - ContextifyContext* self = static_cast(arg); - self->context_.Reset(); - delete self; -} - -Local ContextifyContext::CreateGlobalTemplate( - Isolate* isolate) { - Local function_template = FunctionTemplate::New(isolate); - - Local object_template = - function_template->InstanceTemplate(); +void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { + if (!isolate_data->contextify_global_template().IsEmpty()) { + return; + } + DCHECK(isolate_data->contextify_wrapper_template().IsEmpty()); + Local global_func_template = + FunctionTemplate::New(isolate_data->isolate()); + Local global_object_template = + global_func_template->InstanceTemplate(); NamedPropertyHandlerConfiguration config( PropertyGetterCallback, @@ -185,10 +199,15 @@ Local ContextifyContext::CreateGlobalTemplate( {}, PropertyHandlerFlags::kHasNoSideEffect); - object_template->SetHandler(config); - object_template->SetHandler(indexed_config); + global_object_template->SetHandler(config); + global_object_template->SetHandler(indexed_config); + isolate_data->set_contextify_global_template(global_object_template); - return object_template; + Local wrapper_func_template = + BaseObject::MakeLazilyInitializedJSTemplate(isolate_data); + Local wrapper_object_template = + wrapper_func_template->InstanceTemplate(); + isolate_data->set_contextify_wrapper_template(wrapper_object_template); } MaybeLocal ContextifyContext::CreateV8Context( @@ -218,43 +237,45 @@ MaybeLocal ContextifyContext::CreateV8Context( .ToLocal(&ctx)) { return MaybeLocal(); } + return scope.Escape(ctx); } -bool ContextifyContext::InitializeContext(Local ctx, - Environment* env, - Local sandbox_obj, - const ContextOptions& options) { +BaseObjectPtr ContextifyContext::New( + Local v8_context, + 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; + if (InitializeContextRuntime(v8_context).IsNothing()) { + return BaseObjectPtr(); } Local main_context = env->context(); - ctx->SetSecurityToken(main_context->GetSecurityToken()); + Local new_context_global = v8_context->Global(); + v8_context->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 // 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(ContextEmbedderIndex::kSandboxObject, sandbox_obj); - sandbox_obj->SetPrivate( - main_context, env->contextify_global_private_symbol(), ctx->Global()); + // embedder data field. The sandbox uses a private symbol to hold a reference + // to the ContextifyContext wrapper which in turn internally references + // the context from its constructor. + v8_context->SetEmbedderData(ContextEmbedderIndex::kSandboxObject, + sandbox_obj); // Delegate the code generation validation to // node::ModifyCodeGenerationFromStrings. - ctx->AllowCodeGenerationFromStrings(false); - ctx->SetEmbedderData(ContextEmbedderIndex::kAllowCodeGenerationFromStrings, - options.allow_code_gen_strings); - ctx->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, - options.allow_code_gen_wasm); + v8_context->AllowCodeGenerationFromStrings(false); + v8_context->SetEmbedderData( + ContextEmbedderIndex::kAllowCodeGenerationFromStrings, + options.allow_code_gen_strings); + v8_context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, + options.allow_code_gen_wasm); Utf8Value name_val(env->isolate(), options.name); ContextInfo info(*name_val); @@ -263,28 +284,43 @@ bool ContextifyContext::InitializeContext(Local ctx, info.origin = *origin_val; } + BaseObjectPtr result; + Local wrapper; { - Context::Scope context_scope(ctx); + Context::Scope context_scope(v8_context); Local ctor_name = sandbox_obj->GetConstructorName(); - if (!ctor_name->Equals(ctx, env->object_string()).FromMaybe(false) && - ctx->Global() + if (!ctor_name->Equals(v8_context, env->object_string()).FromMaybe(false) && + new_context_global ->DefineOwnProperty( - ctx, + v8_context, v8::Symbol::GetToStringTag(env->isolate()), ctor_name, static_cast(v8::DontEnum)) .IsNothing()) { - return false; + return BaseObjectPtr(); } + env->AssignToContext(v8_context, nullptr, info); + + if (!env->contextify_wrapper_template() + ->NewInstance(v8_context) + .ToLocal(&wrapper)) { + return BaseObjectPtr(); + } + + result = + MakeBaseObject(env, wrapper, v8_context, options); + // The only strong reference to the wrapper will come from the sandbox. + result->MakeWeak(); } - env->AssignToContext(ctx, nullptr, info); + if (sandbox_obj + ->SetPrivate( + v8_context, env->contextify_context_private_symbol(), wrapper) + .IsNothing()) { + return BaseObjectPtr(); + } - // This should only be done after the initial initializations of the context - // global object is finished. - ctx->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kContextifyContext, - this); - return true; + return result; } void ContextifyContext::Init(Environment* env, Local target) { @@ -350,22 +386,14 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo& args) { } TryCatchScope try_catch(env); - std::unique_ptr context_ptr = - std::make_unique(env, sandbox, options); + BaseObjectPtr context_ptr = + ContextifyContext::New(env, sandbox, options); if (try_catch.HasCaught()) { if (!try_catch.HasTerminated()) try_catch.ReThrow(); return; } - - Local new_context = context_ptr->context(); - if (new_context.IsEmpty()) return; - - sandbox->SetPrivate( - env->context(), - env->contextify_context_private_symbol(), - External::New(env->isolate(), context_ptr.release())); } @@ -392,23 +420,24 @@ void ContextifyContext::WeakCallback( ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox( Environment* env, const Local& sandbox) { - MaybeLocal maybe_value = - sandbox->GetPrivate(env->context(), - env->contextify_context_private_symbol()); - Local context_external_v; - if (maybe_value.ToLocal(&context_external_v) && - context_external_v->IsExternal()) { - Local context_external = context_external_v.As(); - return static_cast(context_external->Value()); + Local context_global; + if (sandbox + ->GetPrivate(env->context(), env->contextify_context_private_symbol()) + .ToLocal(&context_global) && + context_global->IsObject()) { + return Unwrap(context_global.As()); } return nullptr; } -// static template ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo& args) { + return Get(args.This()); +} + +ContextifyContext* ContextifyContext::Get(Local object) { Local context; - if (!args.This()->GetCreationContext().ToLocal(&context)) { + if (!object->GetCreationContext().ToLocal(&context)) { return nullptr; } if (!ContextEmbedderTag::IsNodeContext(context)) { diff --git a/src/node_contextify.h b/src/node_contextify.h index a424c56d7e8f26..87440169503fbd 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -41,23 +41,23 @@ struct ContextOptions { BaseObjectPtr microtask_queue_wrap; }; -class ContextifyContext { +class ContextifyContext : public BaseObject { public: ContextifyContext(Environment* env, - v8::Local sandbox_obj, + v8::Local wrapper, + v8::Local v8_context, const ContextOptions& options); ~ContextifyContext(); - static void CleanupHook(void* arg); + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(ContextifyContext) + SET_SELF_SIZE(ContextifyContext) static v8::MaybeLocal CreateV8Context( v8::Isolate* isolate, v8::Local object_template, const SnapshotData* snapshot_data, 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); @@ -65,10 +65,6 @@ class ContextifyContext { Environment* env, const v8::Local& sandbox); - inline Environment* env() const { - return env_; - } - inline v8::Local context() const { return PersistentToLocal::Default(env()->isolate(), context_); } @@ -89,11 +85,20 @@ class ContextifyContext { template static ContextifyContext* Get(const v8::PropertyCallbackInfo& args); + static ContextifyContext* Get(v8::Local object); - static v8::Local CreateGlobalTemplate( - v8::Isolate* isolate); + static void InitializeGlobalTemplates(IsolateData* isolate_data); private: + static BaseObjectPtr New(Environment* env, + v8::Local sandbox_obj, + const ContextOptions& options); + // Initialize a context created from CreateV8Context() + static BaseObjectPtr New(v8::Local ctx, + Environment* env, + v8::Local sandbox_obj, + const ContextOptions& options); + static bool IsStillInitializing(const ContextifyContext* ctx); static void MakeContext(const v8::FunctionCallbackInfo& args); static void IsContext(const v8::FunctionCallbackInfo& args); @@ -137,7 +142,7 @@ class ContextifyContext { static void IndexedPropertyDeleterCallback( uint32_t index, const v8::PropertyCallbackInfo& args); - Environment* const env_; + v8::Global context_; BaseObjectPtr microtask_queue_wrap_; };