From dee17ae6ef70ddf95a87a52104d258b10653b44e Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Wed, 8 Feb 2023 15:51:10 +0800 Subject: [PATCH] src: bootstrap Web [Exposed=*] APIs in the shadow realm This is the initial work to bootstrap Web interfaces that are defined with extended attributes `[Exposed=*]`. The ShadowRealm instances are garbage-collected once it is unreachable. However, V8 can not infer the reference cycles between the per-realm strong persistent function handles and the realm's context handle. To allow the context to be gc-ed once it is not reachable, the per-realm persistent handles are attached to the context's global object and the persistent handles are set as weak. --- src/api/environment.cc | 14 +- src/env-inl.h | 3 + src/env.cc | 26 +++- src/env.h | 6 + src/histogram.cc | 39 +++--- src/histogram.h | 5 +- src/node_binding.cc | 6 - src/node_binding.h | 12 +- src/node_buffer.cc | 7 +- src/node_i18n.cc | 42 +++--- src/node_perf.cc | 45 ++++--- src/node_realm-inl.h | 21 ++- src/node_realm.cc | 15 ++- src/node_realm.h | 23 +++- src/node_shadow_realm.cc | 57 ++++++++- src/node_shadow_realm.h | 15 +++ src/util.h | 11 ++ test/common/globals.js | 141 +++++++++++++++++++++ test/parallel/test-shadow-realm-gc.js | 12 ++ test/parallel/test-shadow-realm-globals.js | 27 ++++ 20 files changed, 446 insertions(+), 81 deletions(-) create mode 100644 test/common/globals.js create mode 100644 test/parallel/test-shadow-realm-gc.js create mode 100644 test/parallel/test-shadow-realm-globals.js diff --git a/src/api/environment.cc b/src/api/environment.cc index 8aa1385f548c4d..9bbac7d0c713e2 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -67,6 +67,16 @@ MaybeLocal PrepareStackTraceCallback(Local context, if (env == nullptr) { return exception->ToString(context).FromMaybe(Local()); } + // TODO(legendecas): Per-realm prepareStackTrace callback. + // If we are in a Realm that is not the principal Realm (e.g. ShadowRealm), + // skip the prepareStackTrace callback to avoid passing the JS objects ( + // the exception and trace) across the realm boundary with the + // `Error.prepareStackTrace` override. + Realm* current_realm = Realm::GetCurrent(context); + if (current_realm != nullptr && + current_realm->kind() != Realm::Kind::kPrincipal) { + return exception->ToString(context).FromMaybe(Local()); + } Local prepare = env->prepare_stack_trace_callback(); if (prepare.IsEmpty()) { return exception->ToString(context).FromMaybe(Local()); @@ -81,8 +91,8 @@ MaybeLocal PrepareStackTraceCallback(Local context, // is what ReThrow gives us). Just returning the empty MaybeLocal would leave // us with a pending exception. TryCatchScope try_catch(env); - MaybeLocal result = prepare->Call( - context, Undefined(env->isolate()), arraysize(args), args); + MaybeLocal result = + prepare->Call(context, Undefined(env->isolate()), arraysize(args), args); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } diff --git a/src/env-inl.h b/src/env-inl.h index 6b4d63616abe39..42bc45ea36271e 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -793,6 +793,7 @@ void Environment::set_process_exit_handler( #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ inline \ v8::Local IsolateData::PropertyName() const { \ @@ -801,7 +802,9 @@ void Environment::set_process_exit_handler( PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VS #undef VY #undef VP diff --git a/src/env.cc b/src/env.cc index c730401c7a7abc..f0a7a900694bd4 100644 --- a/src/env.cc +++ b/src/env.cc @@ -299,13 +299,16 @@ IsolateDataSerializeInfo IsolateData::Serialize(SnapshotCreator* creator) { #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) +#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ info.primitive_values.push_back( \ creator->AddData(PropertyName##_.Get(isolate))); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -338,6 +341,7 @@ void IsolateData::DeserializeProperties(const IsolateDataSerializeInfo* info) { #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) +#define VR(PropertyName, TypeName) V(Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ do { \ MaybeLocal maybe_field = \ @@ -352,7 +356,9 @@ void IsolateData::DeserializeProperties(const IsolateDataSerializeInfo* info) { PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -421,6 +427,19 @@ void IsolateData::CreateProperties() { .ToLocalChecked())); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) #undef V +#define V(PropertyName, TypeName) \ + per_realm_##PropertyName##_.Set( \ + isolate_, \ + Private::New( \ + isolate_, \ + String::NewFromOneByte( \ + isolate_, \ + reinterpret_cast("per_realm_" #PropertyName), \ + NewStringType::kInternalized, \ + sizeof("per_realm_" #PropertyName) - 1) \ + .ToLocalChecked())); + PER_REALM_STRONG_PERSISTENT_VALUES(V) +#undef V #define V(PropertyName, StringValue) \ PropertyName##_.Set( \ isolate_, \ @@ -785,8 +804,11 @@ Environment::Environment(IsolateData* isolate_data, void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { - principal_realm_ = std::make_unique( - this, context, MAYBE_FIELD_PTR(env_info, principal_realm)); + principal_realm_ = + std::make_unique(this, + context, + Realm::kPrincipal, + MAYBE_FIELD_PTR(env_info, principal_realm)); AssignToContext(context, principal_realm_.get(), ContextInfo("")); if (env_info != nullptr) { DeserializeProperties(env_info); diff --git a/src/env.h b/src/env.h index 629b99a9205069..3fe059779d55a6 100644 --- a/src/env.h +++ b/src/env.h @@ -147,12 +147,15 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define V(TypeName, PropertyName) \ inline v8::Local PropertyName() const; PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) #undef V +#undef VR #undef VY #undef VS #undef VP @@ -184,6 +187,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) +#define VR(PropertyName, TypeName) V(v8::Private, per_realm_##PropertyName) #define VM(PropertyName) V(v8::FunctionTemplate, PropertyName##_binding) #define VT(PropertyName, TypeName) V(TypeName, PropertyName) #define V(TypeName, PropertyName) \ @@ -192,9 +196,11 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) PER_ISOLATE_TEMPLATE_PROPERTIES(VT) + PER_REALM_STRONG_PERSISTENT_VALUES(VR) NODE_BINDINGS_WITH_PER_ISOLATE_INIT(VM) #undef V #undef VM +#undef VR #undef VT #undef VS #undef VY diff --git a/src/histogram.cc b/src/histogram.cc index 3a3228ddc9eb6b..112a8911cfb4af 100644 --- a/src/histogram.cc +++ b/src/histogram.cc @@ -16,6 +16,7 @@ using v8::Local; using v8::Map; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::String; using v8::Uint32; using v8::Value; @@ -213,7 +214,7 @@ void HistogramBase::Add(const FunctionCallbackInfo& args) { HistogramBase* histogram; ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); - CHECK(GetConstructorTemplate(env)->HasInstance(args[0])); + CHECK(GetConstructorTemplate(env->isolate_data())->HasInstance(args[0])); HistogramBase* other; ASSIGN_OR_RETURN_UNWRAP(&other, args[0]); @@ -225,9 +226,10 @@ BaseObjectPtr HistogramBase::Create( Environment* env, const Histogram::Options& options) { Local obj; - if (!GetConstructorTemplate(env) - ->InstanceTemplate() - ->NewInstance(env->context()).ToLocal(&obj)) { + if (!GetConstructorTemplate(env->isolate_data()) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj)) { return BaseObjectPtr(); } @@ -238,9 +240,10 @@ BaseObjectPtr HistogramBase::Create( Environment* env, std::shared_ptr histogram) { Local obj; - if (!GetConstructorTemplate(env) - ->InstanceTemplate() - ->NewInstance(env->context()).ToLocal(&obj)) { + if (!GetConstructorTemplate(env->isolate_data()) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, std::move(histogram)); @@ -278,15 +281,14 @@ void HistogramBase::New(const FunctionCallbackInfo& args) { } Local HistogramBase::GetConstructorTemplate( - Environment* env) { - Local tmpl = env->histogram_ctor_template(); + IsolateData* isolate_data) { + Local tmpl = isolate_data->histogram_ctor_template(); if (tmpl.IsEmpty()) { - Isolate* isolate = env->isolate(); + Isolate* isolate = isolate_data->isolate(); tmpl = NewFunctionTemplate(isolate, New); - Local classname = - FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + Local classname = FIXED_ONE_BYTE_STRING(isolate, "Histogram"); tmpl->SetClassName(classname); - tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); + tmpl->Inherit(BaseObject::GetConstructorTemplate(isolate_data)); tmpl->InstanceTemplate()->SetInternalFieldCount( HistogramBase::kInternalFieldCount); @@ -311,7 +313,7 @@ Local HistogramBase::GetConstructorTemplate( SetProtoMethod(isolate, tmpl, "record", Record); SetProtoMethod(isolate, tmpl, "recordDelta", RecordDelta); SetProtoMethod(isolate, tmpl, "add", Add); - env->set_histogram_ctor_template(tmpl); + isolate_data->set_histogram_ctor_template(tmpl); } return tmpl; } @@ -339,9 +341,12 @@ void HistogramBase::RegisterExternalReferences( registry->Register(Add); } -void HistogramBase::Initialize(Environment* env, Local target) { - SetConstructorFunction( - env->context(), target, "Histogram", GetConstructorTemplate(env)); +void HistogramBase::Initialize(IsolateData* isolate_data, + Local target) { + SetConstructorFunction(isolate_data->isolate(), + target, + "Histogram", + GetConstructorTemplate(isolate_data)); } BaseObjectPtr HistogramBase::HistogramTransferData::Deserialize( diff --git a/src/histogram.h b/src/histogram.h index d526bba8a1b797..b3df6c975d90b0 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -84,8 +84,9 @@ class HistogramImpl { class HistogramBase : public BaseObject, public HistogramImpl { public: static v8::Local GetConstructorTemplate( - Environment* env); - static void Initialize(Environment* env, v8::Local target); + IsolateData* isolate_data); + static void Initialize(IsolateData* isolate_data, + v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static BaseObjectPtr Create( diff --git a/src/node_binding.cc b/src/node_binding.cc index 3c97c73964d207..1ca18eff4137f9 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -14,12 +14,6 @@ #define NODE_BUILTIN_OPENSSL_BINDINGS(V) #endif -#if NODE_HAVE_I18N_SUPPORT -#define NODE_BUILTIN_ICU_BINDINGS(V) V(icu) -#else -#define NODE_BUILTIN_ICU_BINDINGS(V) -#endif - #if HAVE_INSPECTOR #define NODE_BUILTIN_PROFILER_BINDINGS(V) V(profiler) #else diff --git a/src/node_binding.h b/src/node_binding.h index c140297f5ca936..1b024774e120a9 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -24,9 +24,17 @@ static_assert(static_cast(NM_F_LINKED) == static_cast(node::ModuleFlags::kLinked), "NM_F_LINKED != node::ModuleFlags::kLinked"); +#if NODE_HAVE_I18N_SUPPORT +#define NODE_BUILTIN_ICU_BINDINGS(V) V(icu) +#else +#define NODE_BUILTIN_ICU_BINDINGS(V) +#endif + #define NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) \ V(builtins) \ - V(worker) + V(performance) \ + V(worker) \ + NODE_BUILTIN_ICU_BINDINGS(V) #define NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \ static node::node_module _module = { \ @@ -56,6 +64,8 @@ namespace node { NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL) // Define a per-isolate initialization function for a node internal binding. +// The modname should be registered in the NODE_BINDINGS_WITH_PER_ISOLATE_INIT +// list. #define NODE_BINDING_PER_ISOLATE_INIT(modname, per_isolate_func) \ void _register_isolate_##modname(node::IsolateData* isolate_data, \ v8::Local target) { \ diff --git a/src/node_buffer.cc b/src/node_buffer.cc index fec1c96634aaec..91f98272b73ab9 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1270,11 +1270,14 @@ static void IsAscii(const FunctionCallbackInfo& args) { } void SetBufferPrototype(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + + // TODO(legendecas): Remove this check once the binding supports sub-realms. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); CHECK(args[0]->IsObject()); Local proto = args[0].As(); - env->set_buffer_prototype_object(proto); + realm->set_buffer_prototype_object(proto); } void GetZeroFillToggle(const FunctionCallbackInfo& args) { diff --git a/src/node_i18n.cc b/src/node_i18n.cc index bb810632ee6617..cdb264d6202eb3 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -859,36 +859,41 @@ static void GetStringWidth(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(width); } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - SetMethod(context, target, "toUnicode", ToUnicode); - SetMethod(context, target, "toASCII", ToASCII); - SetMethod(context, target, "getStringWidth", GetStringWidth); +static void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); + Local proto = target->PrototypeTemplate(); + + SetMethod(isolate, proto, "toUnicode", ToUnicode); + SetMethod(isolate, proto, "toASCII", ToASCII); + SetMethod(isolate, proto, "getStringWidth", GetStringWidth); // One-shot converters - SetMethod(context, target, "icuErrName", ICUErrorName); - SetMethod(context, target, "transcode", Transcode); + SetMethod(isolate, proto, "icuErrName", ICUErrorName); + SetMethod(isolate, proto, "transcode", Transcode); // ConverterObject { - Local t = NewFunctionTemplate(env->isolate(), nullptr); - t->Inherit(BaseObject::GetConstructorTemplate(env)); + Local t = NewFunctionTemplate(isolate, nullptr); + t->Inherit(BaseObject::GetConstructorTemplate(isolate_data)); t->InstanceTemplate()->SetInternalFieldCount( ConverterObject::kInternalFieldCount); Local converter_string = - FIXED_ONE_BYTE_STRING(env->isolate(), "Converter"); + FIXED_ONE_BYTE_STRING(isolate, "Converter"); t->SetClassName(converter_string); - env->set_i18n_converter_template(t->InstanceTemplate()); + isolate_data->set_i18n_converter_template(t->InstanceTemplate()); } - SetMethod(context, target, "getConverter", ConverterObject::Create); - SetMethod(context, target, "decode", ConverterObject::Decode); - SetMethod(context, target, "hasConverter", ConverterObject::Has); + SetMethod(isolate, proto, "getConverter", ConverterObject::Create); + SetMethod(isolate, proto, "decode", ConverterObject::Decode); + SetMethod(isolate, proto, "hasConverter", ConverterObject::Has); } +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) {} + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(ToUnicode); registry->Register(ToASCII); @@ -903,7 +908,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace i18n } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(icu, node::i18n::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL(icu, node::i18n::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(icu, node::i18n::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(icu, node::i18n::RegisterExternalReferences) #endif // NODE_HAVE_I18N_SUPPORT diff --git a/src/node_perf.cc b/src/node_perf.cc index edc578d1b89ea7..1a723481015c5a 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -28,6 +28,7 @@ using v8::Local; using v8::MaybeLocal; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::PropertyAttribute; using v8::ReadOnly; using v8::String; @@ -96,7 +97,10 @@ void PerformanceState::Mark(PerformanceMilestone milestone, uint64_t ts) { // Allows specific Node.js lifecycle milestones to be set from JavaScript void MarkMilestone(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + // TODO(legendecas): Remove this check once the sub-realms are supported. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); + Environment* env = realm->env(); PerformanceMilestone milestone = static_cast(args[0].As()->Value()); if (milestone != NODE_PERFORMANCE_MILESTONE_INVALID) @@ -104,9 +108,11 @@ void MarkMilestone(const FunctionCallbackInfo& args) { } void SetupPerformanceObservers(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); + // TODO(legendecas): Remove this check once the sub-realms are supported. + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); CHECK(args[0]->IsFunction()); - env->set_performance_entry_callback(args[0].As()); + realm->set_performance_entry_callback(args[0].As()); } // Marks the start of a GC cycle @@ -281,15 +287,23 @@ void GetTimeOriginTimeStamp(const FunctionCallbackInfo& args) { } void MarkBootstrapComplete(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - env->performance_state()->Mark( + Realm* realm = Realm::GetCurrent(args); + CHECK_EQ(realm->kind(), Realm::Kind::kPrincipal); + realm->env()->performance_state()->Mark( performance::NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { +static void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Local proto = target->PrototypeTemplate(); + + HistogramBase::Initialize(isolate_data, proto); +} + +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); PerformanceState* state = env->performance_state(); @@ -362,12 +376,8 @@ void Initialize(Local target, PropertyAttribute attr = static_cast(ReadOnly | DontDelete); - target->DefineOwnProperty(context, - env->constants_string(), - constants, - attr).ToChecked(); - - HistogramBase::Initialize(env, target); + target->DefineOwnProperty(context, env->constants_string(), constants, attr) + .ToChecked(); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { @@ -387,6 +397,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace performance } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(performance, node::performance::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL( + performance, node::performance::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(performance, + node::performance::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(performance, node::performance::RegisterExternalReferences) diff --git a/src/node_realm-inl.h b/src/node_realm-inl.h index 2fc3eef97729d1..c082e448f0622d 100644 --- a/src/node_realm-inl.h +++ b/src/node_realm-inl.h @@ -42,6 +42,10 @@ inline v8::Isolate* Realm::isolate() const { return isolate_; } +inline Realm::Kind Realm::kind() const { + return kind_; +} + inline bool Realm::has_run_bootstrapping_code() const { return has_run_bootstrapping_code_; } @@ -115,18 +119,31 @@ int64_t Realm::base_object_count() const { return base_object_count_; } +// V8 can not infer reference cycles between global persistent handles, e.g. +// the Realm's Context handle and the per-realm function handles. +// Attach the per-realm strong persistent values' lifetime to the context's +// global object so that they are kept alive when the context is still +// reachable. #define V(PropertyName, TypeName) \ inline v8::Local Realm::PropertyName() const { \ - return PersistentToLocal::Strong(PropertyName##_); \ + return PersistentToLocal::StrongUnChecked(PropertyName##_); \ } \ inline void Realm::set_##PropertyName(v8::Local value) { \ PropertyName##_.Reset(isolate(), value); \ + if (!value.IsEmpty()) { \ + PropertyName##_.SetWeak(); \ + v8::Local ctx = context(); \ + ctx->Global() \ + ->SetPrivate(ctx, isolate_data()->per_realm_##PropertyName(), value) \ + .ToChecked(); \ + } \ } PER_REALM_STRONG_PERSISTENT_VALUES(V) #undef V +// Sub-realms' can set the context persistent handle as weak. v8::Local Realm::context() const { - return PersistentToLocal::Strong(context_); + return PersistentToLocal::Default(isolate_, context_); } void Realm::AddCleanupHook(CleanupQueue::Callback fn, void* arg) { diff --git a/src/node_realm.cc b/src/node_realm.cc index d7720bf4e1a1f7..6c2b1c366d393a 100644 --- a/src/node_realm.cc +++ b/src/node_realm.cc @@ -21,8 +21,9 @@ using v8::Value; Realm::Realm(Environment* env, v8::Local context, + Kind kind, const RealmSerializeInfo* realm_info) - : env_(env), isolate_(context->GetIsolate()) { + : env_(env), isolate_(context->GetIsolate()), kind_(kind) { context_.Reset(isolate_, context); // Create properties if not deserializing from snapshot. @@ -214,7 +215,7 @@ MaybeLocal Realm::BootstrapInternalLoaders() { return scope.Escape(loader_exports); } -MaybeLocal Realm::BootstrapNode() { +MaybeLocal Realm::BootstrapRealm() { EscapableHandleScope scope(isolate_); MaybeLocal result = ExecuteBootstrapper("internal/bootstrap/node"); @@ -278,7 +279,7 @@ MaybeLocal Realm::RunBootstrapping() { } Local result; - if (!BootstrapNode().ToLocal(&result)) { + if (!BootstrapRealm().ToLocal(&result)) { return MaybeLocal(); } @@ -295,8 +296,10 @@ void Realm::DoneBootstrapping() { // TODO(legendecas): track req_wrap and handle_wrap by realms instead of // environments. - CHECK(env_->req_wrap_queue()->IsEmpty()); - CHECK(env_->handle_wrap_queue()->IsEmpty()); + if (kind_ == kPrincipal) { + CHECK(env_->req_wrap_queue()->IsEmpty()); + CHECK(env_->handle_wrap_queue()->IsEmpty()); + } has_run_bootstrapping_code_ = true; @@ -323,7 +326,7 @@ void Realm::PrintInfoForSnapshot() { << "\n"; }); - fprintf(stderr, "\nnBuiltins without cache:\n"); + fprintf(stderr, "\nBuiltins without cache:\n"); for (const auto& s : builtins_without_cache) { fprintf(stderr, "%s\n", s.c_str()); } diff --git a/src/node_realm.h b/src/node_realm.h index 04129eec47d551..cc61c42da18185 100644 --- a/src/node_realm.h +++ b/src/node_realm.h @@ -43,6 +43,11 @@ using BindingDataStore = std::array, */ class Realm : public MemoryRetainer { public: + enum Kind { + kPrincipal, + kShadowRealm, + }; + static inline Realm* GetCurrent(v8::Isolate* isolate); static inline Realm* GetCurrent(v8::Local context); static inline Realm* GetCurrent( @@ -52,6 +57,7 @@ class Realm : public MemoryRetainer { Realm(Environment* env, v8::Local context, + Kind kind, const RealmSerializeInfo* realm_info); ~Realm(); @@ -69,8 +75,6 @@ class Realm : public MemoryRetainer { void DeserializeProperties(const RealmSerializeInfo* info); v8::MaybeLocal ExecuteBootstrapper(const char* id); - v8::MaybeLocal BootstrapInternalLoaders(); - v8::MaybeLocal BootstrapNode(); v8::MaybeLocal RunBootstrapping(); inline void AddCleanupHook(CleanupQueue::Callback cb, void* arg); @@ -87,6 +91,7 @@ class Realm : public MemoryRetainer { inline IsolateData* isolate_data() const; inline Environment* env() const; inline v8::Isolate* isolate() const; + inline Kind kind() const; inline v8::Local context() const; inline bool has_run_bootstrapping_code() const; @@ -127,15 +132,21 @@ class Realm : public MemoryRetainer { // it's only used for tests. std::vector builtins_in_snapshot; - private: - void InitializeContext(v8::Local context, - const RealmSerializeInfo* realm_info); - void DoneBootstrapping(); + protected: + v8::MaybeLocal BootstrapInternalLoaders(); + virtual v8::MaybeLocal BootstrapRealm(); Environment* env_; // Shorthand for isolate pointer. v8::Isolate* isolate_; v8::Global context_; + + private: + void InitializeContext(v8::Local context, + const RealmSerializeInfo* realm_info); + void DoneBootstrapping(); + + Kind kind_; bool has_run_bootstrapping_code_ = false; int64_t base_object_count_ = 0; diff --git a/src/node_shadow_realm.cc b/src/node_shadow_realm.cc index 2ccd62edc315d7..b24d8d5a4decb5 100644 --- a/src/node_shadow_realm.cc +++ b/src/node_shadow_realm.cc @@ -1,15 +1,70 @@ #include "node_shadow_realm.h" +#include "env-inl.h" namespace node { namespace shadow_realm { using v8::Context; +using v8::EscapableHandleScope; using v8::Local; using v8::MaybeLocal; +using v8::Value; + +// static +ShadowRealm* ShadowRealm::New(Environment* env) { + ShadowRealm* realm = new ShadowRealm(env); + env->AssignToContext(realm->context(), realm, ContextInfo("")); + + if (realm->RunBootstrapping().IsEmpty()) { + delete realm; + return nullptr; + } + return realm; +} // static MaybeLocal HostCreateShadowRealmContextCallback( Local initiator_context) { - return Context::New(initiator_context->GetIsolate()); + Environment* env = Environment::GetCurrent(initiator_context); + ShadowRealm* realm = ShadowRealm::New(env); + if (realm != nullptr) { + return realm->context(); + } + return MaybeLocal(); +} + +// static +void ShadowRealm::WeakCallback(const v8::WeakCallbackInfo& data) { + ShadowRealm* realm = data.GetParameter(); + delete realm; +} + +ShadowRealm::ShadowRealm(Environment* env) + : Realm(env, NewContext(env->isolate()), kShadowRealm, nullptr) { + context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); +} + +ShadowRealm::~ShadowRealm() { + while (HasCleanupHooks()) { + RunCleanup(); + } +} + +v8::MaybeLocal ShadowRealm::BootstrapRealm() { + EscapableHandleScope scope(isolate_); + MaybeLocal result; + + // Skip "internal/bootstrap/node" as it installs node globals and per-isolate + // callbacks like PrepareStackTraceCallback. + + if (!env_->no_browser_globals()) { + result = ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard"); + + if (result.IsEmpty()) { + return MaybeLocal(); + } + } + + return result; } } // namespace shadow_realm diff --git a/src/node_shadow_realm.h b/src/node_shadow_realm.h index a10fc425661f56..37bd9928914193 100644 --- a/src/node_shadow_realm.h +++ b/src/node_shadow_realm.h @@ -3,11 +3,26 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include "node_realm.h" #include "v8.h" namespace node { namespace shadow_realm { +class ShadowRealm : public Realm { + public: + static ShadowRealm* New(Environment* env); + + protected: + v8::MaybeLocal BootstrapRealm() override; + + private: + static void WeakCallback(const v8::WeakCallbackInfo& data); + + explicit ShadowRealm(Environment* env); + ~ShadowRealm(); +}; + v8::MaybeLocal HostCreateShadowRealmContextCallback( v8::Local initiator_context); diff --git a/src/util.h b/src/util.h index e30f298f8dd836..c262fce9e242e3 100644 --- a/src/util.h +++ b/src/util.h @@ -834,6 +834,17 @@ class PersistentToLocal { static inline v8::Local Strong( const v8::PersistentBase& persistent) { DCHECK(!persistent.IsWeak()); + return StrongUnChecked(persistent); + } + + // Unchecked conversion from a non-weak Persistent to Local, + // use with care! + // + // Do not call persistent.Reset() while the returned Local is still in + // scope, it will destroy the reference to the object. + template + static inline v8::Local StrongUnChecked( + const v8::PersistentBase& persistent) { return *reinterpret_cast*>( const_cast*>(&persistent)); } diff --git a/test/common/globals.js b/test/common/globals.js new file mode 100644 index 00000000000000..f18b358e657a55 --- /dev/null +++ b/test/common/globals.js @@ -0,0 +1,141 @@ +'use strict'; + +const intrinsics = new Set([ + 'Object', + 'Function', + 'Array', + 'Number', + 'parseFloat', + 'parseInt', + 'Infinity', + 'NaN', + 'undefined', + 'Boolean', + 'String', + 'Symbol', + 'Date', + 'Promise', + 'RegExp', + 'Error', + 'AggregateError', + 'EvalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', + 'globalThis', + 'JSON', + 'Math', + 'Intl', + 'ArrayBuffer', + 'Uint8Array', + 'Int8Array', + 'Uint16Array', + 'Int16Array', + 'Uint32Array', + 'Int32Array', + 'Float32Array', + 'Float64Array', + 'Uint8ClampedArray', + 'BigUint64Array', + 'BigInt64Array', + 'DataView', + 'Map', + 'BigInt', + 'Set', + 'WeakMap', + 'WeakSet', + 'Proxy', + 'Reflect', + 'ShadowRealm', + 'FinalizationRegistry', + 'WeakRef', + 'decodeURI', + 'decodeURIComponent', + 'encodeURI', + 'encodeURIComponent', + 'escape', + 'unescape', + 'eval', + 'isFinite', + 'isNaN', + 'SharedArrayBuffer', + 'Atomics', + 'WebAssembly', +]); + +if (global.gc) { + intrinsics.add('gc'); +} + +// v8 exposes console in the global scope. +intrinsics.add('console'); + +const webIdlExposedWildcard = new Set([ + 'DOMException', + 'TextEncoder', + 'TextDecoder', + 'AbortController', + 'AbortSignal', + 'EventTarget', + 'Event', + 'URL', + 'URLSearchParams', + 'ReadableStream', + 'ReadableStreamDefaultReader', + 'ReadableStreamBYOBReader', + 'ReadableStreamBYOBRequest', + 'ReadableByteStreamController', + 'ReadableStreamDefaultController', + 'TransformStream', + 'TransformStreamDefaultController', + 'WritableStream', + 'WritableStreamDefaultWriter', + 'WritableStreamDefaultController', + 'ByteLengthQueuingStrategy', + 'CountQueuingStrategy', + 'TextEncoderStream', + 'TextDecoderStream', + 'CompressionStream', + 'DecompressionStream', +]); + +const webIdlExposedWindow = new Set([ + 'console', + 'BroadcastChannel', + 'queueMicrotask', + 'structuredClone', + 'MessageChannel', + 'MessagePort', + 'MessageEvent', + 'clearInterval', + 'clearTimeout', + 'setInterval', + 'setTimeout', + 'atob', + 'btoa', + 'Blob', + 'Performance', + 'performance', + 'fetch', + 'FormData', + 'Headers', + 'Request', + 'Response', +]); + +const nodeGlobals = new Set([ + 'process', + 'global', + 'Buffer', + 'clearImmediate', + 'setImmediate', +]); + +module.exports = { + intrinsics, + webIdlExposedWildcard, + webIdlExposedWindow, + nodeGlobals, +}; diff --git a/test/parallel/test-shadow-realm-gc.js b/test/parallel/test-shadow-realm-gc.js new file mode 100644 index 00000000000000..c51810e9287c39 --- /dev/null +++ b/test/parallel/test-shadow-realm-gc.js @@ -0,0 +1,12 @@ +// Flags: --experimental-shadow-realm --max-old-space-size=10 +'use strict'; + +/** + * Verifying ShadowRealm instances can be correctly garbage collected. + */ + +require('../common'); + +for (let i = 0; i < 1000; i++) { + new ShadowRealm(); +} diff --git a/test/parallel/test-shadow-realm-globals.js b/test/parallel/test-shadow-realm-globals.js new file mode 100644 index 00000000000000..8e0107597985ad --- /dev/null +++ b/test/parallel/test-shadow-realm-globals.js @@ -0,0 +1,27 @@ +// Flags: --experimental-shadow-realm +'use strict'; + +require('../common'); +const { intrinsics, webIdlExposedWildcard } = require('../common/globals'); +const assert = require('assert'); + +// Validates APIs exposed on the ShadowRealm globalThis. +const shadowRealm = new ShadowRealm(); +const itemsStr = shadowRealm.evaluate(` +(() => { + return Object.getOwnPropertyNames(globalThis).join(','); +})(); +`); +const items = itemsStr.split(','); +const leaks = []; +for (const item of items) { + if (intrinsics.has(item)) { + continue; + } + if (webIdlExposedWildcard.has(item)) { + continue; + } + leaks.push(item); +} + +assert.deepStrictEqual(leaks, []);