From 23a9cb40b20d55a70e47c756fed1b77a1e624613 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Mon, 27 Mar 2023 15:28:18 +0800 Subject: [PATCH] module: bootstrap module loaders in shadow realm --- lib/internal/bootstrap/realm.js | 10 +- lib/internal/bootstrap/shadow_realm.js | 47 ++++ lib/internal/main/worker_thread.js | 5 +- lib/internal/modules/esm/utils.js | 9 + lib/internal/process/pre_execution.js | 22 +- lib/internal/vm/module.js | 1 + src/async_wrap.cc | 19 +- src/env.cc | 12 +- src/module_wrap.cc | 246 ++++++++++-------- src/module_wrap.h | 14 +- src/node_binding.h | 7 +- src/node_dir.cc | 28 +- src/node_env_var.cc | 3 +- src/node_errors.h | 4 + src/node_messaging.cc | 68 ++--- src/node_messaging.h | 2 +- src/node_process.h | 12 +- src/node_process_methods.cc | 17 +- src/node_realm.cc | 5 +- src/node_shadow_realm.cc | 31 +++ .../builtin-modules.mjs | 9 + .../es-module-shadow-realm/state-counter.mjs | 4 + ...st-shadow-realm-allowed-builtin-modules.js | 37 +++ .../test-shadow-realm-import-value-resolve.js | 26 ++ test/parallel/test-shadow-realm-module.js | 29 +++ 25 files changed, 477 insertions(+), 190 deletions(-) create mode 100644 lib/internal/bootstrap/shadow_realm.js create mode 100644 test/fixtures/es-module-shadow-realm/builtin-modules.mjs create mode 100644 test/fixtures/es-module-shadow-realm/state-counter.mjs create mode 100644 test/parallel/test-shadow-realm-allowed-builtin-modules.js create mode 100644 test/parallel/test-shadow-realm-import-value-resolve.js create mode 100644 test/parallel/test-shadow-realm-module.js diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js index 608e3072850d45..1acc4d8234b749 100644 --- a/lib/internal/bootstrap/realm.js +++ b/lib/internal/bootstrap/realm.js @@ -215,8 +215,8 @@ const internalBuiltinIds = builtinIds .filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId); // When --expose-internals is on we'll add the internal builtin ids to these. -const canBeRequiredByUsersList = new SafeSet(publicBuiltinIds); -const canBeRequiredByUsersWithoutSchemeList = +let canBeRequiredByUsersList = new SafeSet(publicBuiltinIds); +let canBeRequiredByUsersWithoutSchemeList = new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id))); /** @@ -269,6 +269,12 @@ class BuiltinModule { } } + static setRealmAllowRequireByUsers(ids) { + canBeRequiredByUsersList = new SafeSet(ids.filter((id) => publicBuiltinIds.includes(id))); + canBeRequiredByUsersWithoutSchemeList = + new SafeSet(ids.filter((id) => !schemelessBlockList.has(id))); + } + // To be called during pre-execution when --expose-internals is on. // Enables the user-land module loader to access internal modules. static exposeInternals() { diff --git a/lib/internal/bootstrap/shadow_realm.js b/lib/internal/bootstrap/shadow_realm.js new file mode 100644 index 00000000000000..8bb10d6086f98d --- /dev/null +++ b/lib/internal/bootstrap/shadow_realm.js @@ -0,0 +1,47 @@ +'use strict'; + +// In ShadowRealm. + +const { + prepareShadowRealmExecution, +} = require('internal/process/pre_execution'); +const { + BuiltinModule, +} = require('internal/bootstrap/realm'); +const { setDefaultImportModuleDynamically } = require('internal/modules/esm/utils'); + +BuiltinModule.setRealmAllowRequireByUsers([ + 'assert', + 'assert/strict', + 'buffer', + 'console', + 'constants', + 'cypto', + 'diagnostics_channel', + 'events', + 'module', + 'path', + 'path/posix', + 'path/win32', + 'readline', + 'readline/promises', + 'repl', + 'stream', + 'stream/consumers', + 'stream/promises', + 'stream/web', + 'string_decoder', + 'url', + 'util', + 'util/types', + 'zlib', +]); + +prepareShadowRealmExecution(); + +// The handler for `ShadowRealm.prototype.importValue`. +setDefaultImportModuleDynamically((specifier, assertions) => { + const { esmLoader } = require('internal/process/esm_loader'); + // `parentUrl` is not set in the case of a ShadowRealm top-level import. + return esmLoader.import(specifier, undefined, assertions); +}); diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 12ae4a9b23212d..f65cd30ad317f6 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -136,7 +136,10 @@ port.on('message', (message) => { const isLoaderWorker = doEval === 'internal' && filename === require('internal/modules/esm/utils').loaderWorkerId; - setupUserModules(isLoaderWorker); + setupUserModules({ + __proto__: null, + isLoaderWorker, + }); if (!hasStdin) process.stdin.push(null); diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 5014c99b2a9eb3..3f2d1b5984cbff 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -31,6 +31,11 @@ function setCallbackForWrap(wrap, data) { callbackMap.set(wrap, data); } +let defaultImportModuleDynamically; +function setDefaultImportModuleDynamically(importModuleDynamically) { + defaultImportModuleDynamically = importModuleDynamically; +} + let defaultConditions; function getDefaultConditions() { assert(defaultConditions !== undefined); @@ -90,6 +95,9 @@ async function importModuleDynamicallyCallback(wrap, specifier, assertions) { specifier, getModuleFromWrap(wrap) || wrap, assertions); } } + if (defaultImportModuleDynamically) { + return defaultImportModuleDynamically(specifier, assertions); + } throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); } @@ -152,6 +160,7 @@ async function initializeHooks() { module.exports = { setCallbackForWrap, + setDefaultImportModuleDynamically, initializeESM, initializeHooks, getDefaultConditions, diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index d79aa41c53e7b6..7f45c95cd0bec4 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -26,6 +26,7 @@ const { exposeLazyInterfaces, defineReplaceableLazyAttribute, setupCoverageHooks, + kEmptyObject, } = require('internal/util'); const { @@ -59,6 +60,18 @@ function prepareWorkerThreadExecution() { }); } +function prepareShadowRealmExecution() { + // Patch the process object with legacy properties and normalizations. + // Do not expand argv1 as it is not available in ShadowRealm. + patchProcessObject(false); + setupDebugEnv(); + + setupUserModules({ + __proto__: null, + noPreloadModules: true, + }); +} + function prepareExecution(options) { const { expandArgv1, initializeModules, isMainThread } = options; @@ -150,16 +163,16 @@ function setupSymbolDisposePolyfill() { } } -function setupUserModules(isLoaderWorker = false) { +function setupUserModules(options = kEmptyObject) { + const { isLoaderWorker = false, noPreloadModules = false } = options; initializeCJSLoader(); initializeESMLoader(isLoaderWorker); const CJSLoader = require('internal/modules/cjs/loader'); assert(!CJSLoader.hasLoadedAnyUserCJSModule); // Loader workers are responsible for doing this themselves. - if (isLoaderWorker) { - return; + if (!isLoaderWorker || noPreloadModules) { + loadPreloadModules(); } - loadPreloadModules(); // Need to be done after --require setup. initializeFrozenIntrinsics(); } @@ -687,6 +700,7 @@ module.exports = { setupUserModules, prepareMainThreadExecution, prepareWorkerThreadExecution, + prepareShadowRealmExecution, markBootstrapComplete, loadPreloadModules, initializeFrozenIntrinsics, diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 3d2d25064b62cd..d7651f3e03c8f2 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -126,6 +126,7 @@ class Module { options.cachedData); const { setCallbackForWrap } = require('internal/modules/esm/utils'); setCallbackForWrap(this[kWrap], { + __proto__: null, initializeImportMeta: options.initializeImportMeta, importModuleDynamically: options.importModuleDynamically ? importModuleDynamicallyWrap(options.importModuleDynamically) : diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 42cddc52aed285..7d7272babde014 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -372,8 +372,9 @@ void AsyncWrap::CreatePerContextProperties(Local target, Local unused, Local context, void* priv) { - Environment* env = Environment::GetCurrent(context); - Isolate* isolate = env->isolate(); + Realm* realm = Realm::GetCurrent(context); + Environment* env = realm->env(); + Isolate* isolate = realm->isolate(); HandleScope scope(isolate); PropertyAttribute ReadOnlyDontDelete = @@ -446,13 +447,13 @@ void AsyncWrap::CreatePerContextProperties(Local target, #undef FORCE_SET_TARGET_FIELD - env->set_async_hooks_init_function(Local()); - env->set_async_hooks_before_function(Local()); - env->set_async_hooks_after_function(Local()); - env->set_async_hooks_destroy_function(Local()); - env->set_async_hooks_promise_resolve_function(Local()); - env->set_async_hooks_callback_trampoline(Local()); - env->set_async_hooks_binding(target); + realm->set_async_hooks_init_function(Local()); + realm->set_async_hooks_before_function(Local()); + realm->set_async_hooks_after_function(Local()); + realm->set_async_hooks_destroy_function(Local()); + realm->set_async_hooks_promise_resolve_function(Local()); + realm->set_async_hooks_callback_trampoline(Local()); + realm->set_async_hooks_binding(target); } void AsyncWrap::RegisterExternalReferences( diff --git a/src/env.cc b/src/env.cc index 6b0aba44a36eba..03e9057e5341e3 100644 --- a/src/env.cc +++ b/src/env.cc @@ -500,6 +500,7 @@ void IsolateData::CreateProperties() { binding::CreateInternalBindingTemplates(this); contextify::ContextifyContext::InitializeGlobalTemplates(this); + CreateEnvProxyTemplate(this); } constexpr uint16_t kDefaultCppGCEmebdderID = 0x90de; @@ -1650,10 +1651,13 @@ void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const { void AsyncHooks::grow_async_ids_stack() { async_ids_stack_.reserve(async_ids_stack_.Length() * 3); - env()->async_hooks_binding()->Set( - env()->context(), - env()->async_ids_stack_string(), - async_ids_stack_.GetJSArray()).Check(); + env() + ->principal_realm() + ->async_hooks_binding() + ->Set(env()->context(), + env()->async_ids_stack_string(), + async_ids_stack_.GetJSArray()) + .Check(); } void AsyncHooks::FailWithCorruptedAsyncStack(double expected_async_id) { diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 2dca349bd97089..8457f0d95b2321 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -40,6 +40,7 @@ using v8::Module; using v8::ModuleRequest; using v8::Number; using v8::Object; +using v8::ObjectTemplate; using v8::PrimitiveArray; using v8::Promise; using v8::ScriptCompiler; @@ -49,26 +50,26 @@ using v8::UnboundModuleScript; using v8::Undefined; using v8::Value; -ModuleWrap::ModuleWrap(Environment* env, +ModuleWrap::ModuleWrap(Realm* realm, Local object, Local module, Local url) - : BaseObject(env, object), - module_(env->isolate(), module), - id_(env->get_next_module_id()) { - env->id_to_module_map.emplace(id_, this); - - Local undefined = Undefined(env->isolate()); + : BaseObject(realm, object), + module_(realm->isolate(), module), + id_(realm->env()->get_next_module_id()), + module_hash_(module->GetIdentityHash()) { + realm->env()->id_to_module_map.emplace(id_, this); + realm->env()->hash_to_module_map.emplace(module_hash_, this); + + Local undefined = Undefined(realm->isolate()); object->SetInternalField(kURLSlot, url); object->SetInternalField(kSyntheticEvaluationStepsSlot, undefined); object->SetInternalField(kContextObjectSlot, undefined); } ModuleWrap::~ModuleWrap() { - HandleScope scope(env()->isolate()); - Local module = module_.Get(env()->isolate()); env()->id_to_module_map.erase(id_); - auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash()); + auto range = env()->hash_to_module_map.equal_range(module_hash_); for (auto it = range.first; it != range.second; ++it) { if (it->second == this) { env()->hash_to_module_map.erase(it); @@ -108,8 +109,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); CHECK_GE(args.Length(), 3); - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); + Realm* realm = Realm::GetCurrent(args); + Isolate* isolate = realm->isolate(); Local that = args.This(); @@ -123,7 +124,7 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { } else { CHECK(args[1]->IsObject()); contextify_context = ContextifyContext::ContextFromContextifiedSandbox( - env, args[1].As()); + realm->env(), args[1].As()); CHECK_NOT_NULL(contextify_context); context = contextify_context->context(); } @@ -149,8 +150,8 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { host_defined_options->Set(isolate, HostDefinedOptions::kType, Number::New(isolate, ScriptType::kModule)); - ShouldNotAbortOnUncaughtScope no_abort_scope(env); - TryCatchScope try_catch(env); + ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); + TryCatchScope try_catch(realm->env()); Local module; @@ -207,7 +208,9 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); - AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(), + AppendExceptionLine(realm->env(), + try_catch.Exception(), + try_catch.Message(), ErrorHandlingMode::MODULE_ERROR); try_catch.ReThrow(); } @@ -216,18 +219,19 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { if (options == ScriptCompiler::kConsumeCodeCache && source.GetCachedData()->rejected) { THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( - env, "cachedData buffer was rejected"); + realm, "cachedData buffer was rejected"); try_catch.ReThrow(); return; } } } - if (!that->Set(context, env->url_string(), url).FromMaybe(false)) { + if (!that->Set(context, realm->isolate_data()->url_string(), url) + .FromMaybe(false)) { return; } - ModuleWrap* obj = new ModuleWrap(env, that, module, url); + ModuleWrap* obj = new ModuleWrap(realm, that, module, url); if (synthetic) { obj->synthetic_ = true; @@ -241,8 +245,6 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { context->GetExtrasBindingObject()); obj->contextify_context_ = contextify_context; - env->hash_to_module_map.emplace(module->GetIdentityHash(), obj); - host_defined_options->Set(isolate, HostDefinedOptions::kID, Number::New(isolate, obj->id())); @@ -250,23 +252,24 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(that); } -static Local createImportAssertionContainer(Environment* env, - Isolate* isolate, Local raw_assertions) { +static Local createImportAssertionContainer( + Realm* realm, Isolate* isolate, Local raw_assertions) { + Local context = realm->context(); Local assertions = - Object::New(isolate, v8::Null(env->isolate()), nullptr, nullptr, 0); + Object::New(isolate, v8::Null(realm->isolate()), nullptr, nullptr, 0); for (int i = 0; i < raw_assertions->Length(); i += 3) { - assertions - ->Set(env->context(), - raw_assertions->Get(env->context(), i).As(), - raw_assertions->Get(env->context(), i + 1).As()) - .ToChecked(); + assertions + ->Set(context, + raw_assertions->Get(context, i).As(), + raw_assertions->Get(context, i + 1).As()) + .ToChecked(); } return assertions; } void ModuleWrap::Link(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); CHECK_EQ(args.Length(), 1); @@ -293,14 +296,14 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { // call the dependency resolve callbacks for (int i = 0; i < module_requests_length; i++) { Local module_request = - module_requests->Get(env->context(), i).As(); + module_requests->Get(realm->context(), i).As(); Local specifier = module_request->GetSpecifier(); - Utf8Value specifier_utf8(env->isolate(), specifier); + Utf8Value specifier_utf8(realm->isolate(), specifier); std::string specifier_std(*specifier_utf8, specifier_utf8.length()); Local raw_assertions = module_request->GetImportAssertions(); Local assertions = - createImportAssertionContainer(env, isolate, raw_assertions); + createImportAssertionContainer(realm, isolate, raw_assertions); Local argv[] = { specifier, @@ -316,11 +319,11 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { maybe_resolve_return_value.ToLocalChecked(); if (!resolve_return_value->IsPromise()) { THROW_ERR_VM_MODULE_LINK_FAILURE( - env, "request for '%s' did not return promise", specifier_std); + realm, "request for '%s' did not return promise", specifier_std); return; } Local resolve_promise = resolve_return_value.As(); - obj->resolve_cache_[specifier_std].Reset(env->isolate(), resolve_promise); + obj->resolve_cache_[specifier_std].Reset(isolate, resolve_promise); promises[i] = resolve_promise; } @@ -330,13 +333,13 @@ void ModuleWrap::Link(const FunctionCallbackInfo& args) { } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); Local module = obj->module_.Get(isolate); - TryCatchScope try_catch(env); + TryCatchScope try_catch(realm->env()); USE(module->InstantiateModule(context, ResolveModuleCallback)); // clear resolve cache on instantiate @@ -345,7 +348,9 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); - AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(), + AppendExceptionLine(realm->env(), + try_catch.Exception(), + try_catch.Message(), ErrorHandlingMode::MODULE_ERROR); try_catch.ReThrow(); return; @@ -353,8 +358,8 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { } void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); + Realm* realm = Realm::GetCurrent(args); + Isolate* isolate = realm->isolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); @@ -369,14 +374,14 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsNumber()); - int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); + int64_t timeout = args[0]->IntegerValue(realm->context()).FromJust(); CHECK(args[1]->IsBoolean()); bool break_on_sigint = args[1]->IsTrue(); - ShouldNotAbortOnUncaughtScope no_abort_scope(env); - TryCatchScope try_catch(env); - Isolate::SafeForTerminationScope safe_for_termination(env->isolate()); + ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); + TryCatchScope try_catch(realm->env()); + Isolate::SafeForTerminationScope safe_for_termination(isolate); bool timed_out = false; bool received_signal = false; @@ -407,16 +412,15 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { // Convert the termination exception into a regular exception. if (timed_out || received_signal) { - if (!env->is_main_thread() && env->is_stopping()) - return; - env->isolate()->CancelTerminateExecution(); + if (!realm->env()->is_main_thread() && realm->env()->is_stopping()) return; + isolate->CancelTerminateExecution(); // It is possible that execution was terminated by another timeout in // which this timeout is nested, so check whether one of the watchdogs // from this invocation is responsible for termination. if (timed_out) { - THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, timeout); + THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(realm->env(), timeout); } else if (received_signal) { - THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(env); + THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(realm->env()); } } @@ -430,7 +434,7 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { } void ModuleWrap::GetNamespace(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); @@ -440,7 +444,7 @@ void ModuleWrap::GetNamespace(const FunctionCallbackInfo& args) { switch (module->GetStatus()) { case v8::Module::Status::kUninstantiated: case v8::Module::Status::kInstantiating: - return env->ThrowError( + return realm->env()->ThrowError( "cannot get namespace, module has not been instantiated"); case v8::Module::Status::kInstantiated: case v8::Module::Status::kEvaluating: @@ -467,11 +471,11 @@ void ModuleWrap::GetStatus(const FunctionCallbackInfo& args) { void ModuleWrap::GetStaticDependencySpecifiers( const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); - Local module = obj->module_.Get(env->isolate()); + Local module = obj->module_.Get(realm->isolate()); Local module_requests = module->GetModuleRequests(); int count = module_requests->Length(); @@ -480,12 +484,12 @@ void ModuleWrap::GetStaticDependencySpecifiers( for (int i = 0; i < count; i++) { Local module_request = - module_requests->Get(env->context(), i).As(); + module_requests->Get(realm->context(), i).As(); specifiers[i] = module_request->GetSpecifier(); } args.GetReturnValue().Set( - Array::New(env->isolate(), specifiers.out(), count)); + Array::New(realm->isolate(), specifiers.out(), count)); } void ModuleWrap::GetError(const FunctionCallbackInfo& args) { @@ -502,15 +506,13 @@ MaybeLocal ModuleWrap::ResolveModuleCallback( Local specifier, Local import_assertions, Local referrer) { + Isolate* isolate = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); if (env == nullptr) { - Isolate* isolate = context->GetIsolate(); THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); return MaybeLocal(); } - Isolate* isolate = env->isolate(); - Utf8Value specifier_utf8(isolate, specifier); std::string specifier_std(*specifier_utf8, specifier_utf8.length()); @@ -560,14 +562,23 @@ static MaybeLocal ImportModuleDynamically( THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); return MaybeLocal(); } + Realm* realm = Realm::GetCurrent(context); + if (realm == nullptr) { + // Fallback to the principal realm if it's in a vm context. + realm = env->principal_realm(); + } EscapableHandleScope handle_scope(isolate); Local import_callback = - env->host_import_module_dynamically_callback(); + realm->host_import_module_dynamically_callback(); Local options = host_defined_options.As(); - if (options->Length() != HostDefinedOptions::kLength) { + // Check if a referrer script is available. + // ShadowRealm.prototype.importValue allows dynamic import without a + // referrer. Skip the check if the request is initiated by a shadow realm. + if (options->Length() != HostDefinedOptions::kLength && + realm->kind() != Realm::kShadowRealm) { Local resolver; if (!Promise::Resolver::New(context).ToLocal(&resolver)) return {}; resolver @@ -579,31 +590,36 @@ static MaybeLocal ImportModuleDynamically( } Local object; - - int type = options->Get(context, HostDefinedOptions::kType) - .As() - ->Int32Value(context) - .ToChecked(); - uint32_t id = options->Get(context, HostDefinedOptions::kID) - .As() - ->Uint32Value(context) - .ToChecked(); - if (type == ScriptType::kScript) { - contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second; - object = wrap->object(); - } else if (type == ScriptType::kModule) { - ModuleWrap* wrap = ModuleWrap::GetFromID(env, id); - object = wrap->object(); - } else if (type == ScriptType::kFunction) { - auto it = env->id_to_function_map.find(id); - CHECK_NE(it, env->id_to_function_map.end()); - object = it->second->object(); + if (options->Length() == HostDefinedOptions::kLength) { + int type = options->Get(context, HostDefinedOptions::kType) + .As() + ->Int32Value(context) + .ToChecked(); + uint32_t id = options->Get(context, HostDefinedOptions::kID) + .As() + ->Uint32Value(context) + .ToChecked(); + if (type == ScriptType::kScript) { + contextify::ContextifyScript* wrap = + env->id_to_script_map.find(id)->second; + object = wrap->object(); + } else if (type == ScriptType::kModule) { + ModuleWrap* wrap = ModuleWrap::GetFromID(env, id); + object = wrap->object(); + } else if (type == ScriptType::kFunction) { + auto it = env->id_to_function_map.find(id); + CHECK_NE(it, env->id_to_function_map.end()); + object = it->second->object(); + } else { + UNREACHABLE(); + } } else { - UNREACHABLE(); + // If the request is initiated by a shadow realm, the referrer is null. + object = Null(isolate); } Local assertions = - createImportAssertionContainer(env, isolate, import_assertions); + createImportAssertionContainer(realm, isolate, import_assertions); Local import_args[] = { object, @@ -627,13 +643,13 @@ static MaybeLocal ImportModuleDynamically( void ModuleWrap::SetImportModuleDynamicallyCallback( const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); - Environment* env = Environment::GetCurrent(args); + Realm* realm = Realm::GetCurrent(args); HandleScope handle_scope(isolate); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsFunction()); Local import_callback = args[0].As(); - env->set_host_import_module_dynamically_callback(import_callback); + realm->set_host_import_module_dynamically_callback(import_callback); isolate->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); } @@ -648,14 +664,19 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback( if (module_wrap == nullptr) { return; } + Realm* realm = Realm::GetCurrent(context); + if (realm == nullptr) { + // Fallback to the principal realm if it's in a vm context. + realm = env->principal_realm(); + } Local wrap = module_wrap->object(); Local callback = - env->host_initialize_import_meta_object_callback(); + realm->host_initialize_import_meta_object_callback(); Local args[] = { wrap, meta }; TryCatchScope try_catch(env); USE(callback->Call( - context, Undefined(env->isolate()), arraysize(args), args)); + context, Undefined(realm->isolate()), arraysize(args), args)); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } @@ -663,13 +684,13 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback( void ModuleWrap::SetInitializeImportMetaObjectCallback( const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); + Realm* realm = Realm::GetCurrent(args); + Isolate* isolate = realm->isolate(); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsFunction()); Local import_meta_callback = args[0].As(); - env->set_host_initialize_import_meta_object_callback(import_meta_callback); + realm->set_host_initialize_import_meta_object_callback(import_meta_callback); isolate->SetHostInitializeImportMetaObjectCallback( HostInitializeImportMetaObjectCallback); @@ -760,12 +781,9 @@ void ModuleWrap::CreateCachedData(const FunctionCallbackInfo& args) { } } -void ModuleWrap::Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Isolate* isolate = env->isolate(); +void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); Local tpl = NewFunctionTemplate(isolate, New); tpl->InstanceTemplate()->SetInternalFieldCount( @@ -785,28 +803,36 @@ void ModuleWrap::Initialize(Local target, "getStaticDependencySpecifiers", GetStaticDependencySpecifiers); - SetConstructorFunction(context, target, "ModuleWrap", tpl); + SetConstructorFunction(isolate, target, "ModuleWrap", tpl); - SetMethod(context, + SetMethod(isolate, target, "setImportModuleDynamicallyCallback", SetImportModuleDynamicallyCallback); - SetMethod(context, + SetMethod(isolate, target, "setInitializeImportMetaObjectCallback", SetInitializeImportMetaObjectCallback); +} +void ModuleWrap::CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { + Realm* realm = Realm::GetCurrent(context); + Isolate* isolate = realm->isolate(); #define V(name) \ - target->Set(context, \ - FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ - Integer::New(env->isolate(), Module::Status::name)) \ - .FromJust() - V(kUninstantiated); - V(kInstantiating); - V(kInstantiated); - V(kEvaluating); - V(kEvaluated); - V(kErrored); + target \ + ->Set(context, \ + FIXED_ONE_BYTE_STRING(isolate, #name), \ + Integer::New(isolate, Module::Status::name)) \ + .FromJust() + V(kUninstantiated); + V(kInstantiating); + V(kInstantiated); + V(kEvaluating); + V(kEvaluated); + V(kErrored); #undef V } @@ -830,7 +856,9 @@ void ModuleWrap::RegisterExternalReferences( } // namespace loader } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(module_wrap, - node::loader::ModuleWrap::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL( + module_wrap, node::loader::ModuleWrap::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT( + module_wrap, node::loader::ModuleWrap::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE( module_wrap, node::loader::ModuleWrap::RegisterExternalReferences) diff --git a/src/module_wrap.h b/src/module_wrap.h index c609ba5509dcd0..51da8e590c5291 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -10,6 +10,7 @@ namespace node { +class IsolateData; class Environment; class ExternalReferenceRegistry; @@ -41,10 +42,12 @@ class ModuleWrap : public BaseObject { kInternalFieldCount }; - static void Initialize(v8::Local target, - v8::Local unused, - v8::Local context, - void* priv); + static void CreatePerIsolateProperties(IsolateData* isolate_data, + v8::Local target); + static void CreatePerContextProperties(v8::Local target, + v8::Local unused, + v8::Local context, + void* priv); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static void HostInitializeImportMetaObjectCallback( v8::Local context, @@ -69,7 +72,7 @@ class ModuleWrap : public BaseObject { } private: - ModuleWrap(Environment* env, + ModuleWrap(Realm* realm, v8::Local object, v8::Local module, v8::Local url); @@ -108,6 +111,7 @@ class ModuleWrap : public BaseObject { bool synthetic_ = false; bool linked_ = false; uint32_t id_; + int module_hash_; }; } // namespace loader diff --git a/src/node_binding.h b/src/node_binding.h index 9f0692ca4e190b..7256bf2bbcf732 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -37,10 +37,13 @@ static_assert(static_cast(NM_F_LINKED) == V(contextify) \ V(encoding_binding) \ V(fs) \ + V(fs_dir) \ + V(messaging) \ V(mksnapshot) \ - V(timers) \ - V(process_methods) \ + V(module_wrap) \ V(performance) \ + V(process_methods) \ + V(timers) \ V(url) \ V(worker) \ NODE_BUILTIN_ICU_BINDINGS(V) diff --git a/src/node_dir.cc b/src/node_dir.cc index 0bef2b8927639b..7720303ea7aeae 100644 --- a/src/node_dir.cc +++ b/src/node_dir.cc @@ -397,26 +397,28 @@ static void OpenDir(const FunctionCallbackInfo& args) { } } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Isolate* isolate = env->isolate(); - - SetMethod(context, target, "opendir", OpenDir); +void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); // Create FunctionTemplate for DirHandle Local dir = NewFunctionTemplate(isolate, DirHandle::New); - dir->Inherit(AsyncWrap::GetConstructorTemplate(env)); + dir->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); SetProtoMethod(isolate, dir, "read", DirHandle::Read); SetProtoMethod(isolate, dir, "close", DirHandle::Close); Local dirt = dir->InstanceTemplate(); dirt->SetInternalFieldCount(DirHandle::kInternalFieldCount); - SetConstructorFunction(context, target, "DirHandle", dir); - env->set_dir_instance_template(dirt); + SetConstructorFunction(isolate, target, "DirHandle", dir); + isolate_data->set_dir_instance_template(dirt); + + SetMethod(isolate, target, "opendir", OpenDir); } +void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) {} + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(OpenDir); registry->Register(DirHandle::New); @@ -428,6 +430,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // end namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize) +NODE_BINDING_CONTEXT_AWARE_INTERNAL(fs_dir, + node::fs_dir::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(fs_dir, node::fs_dir::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(fs_dir, node::fs_dir::RegisterExternalReferences) diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 94936ea1c2bd22..bce7ae07214ddf 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -456,7 +456,8 @@ static void EnvDefiner(Local property, } } -void CreateEnvProxyTemplate(Isolate* isolate, IsolateData* isolate_data) { +void CreateEnvProxyTemplate(IsolateData* isolate_data) { + Isolate* isolate = isolate_data->isolate(); HandleScope scope(isolate); if (!isolate_data->env_proxy_template().IsEmpty()) return; Local env_proxy_ctor_template = diff --git a/src/node_errors.h b/src/node_errors.h index 569dafe82df83d..8591faab8eaf6f 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -125,6 +125,10 @@ void AppendExceptionLine(Environment* env, inline void THROW_##code( \ Environment* env, const char* format, Args&&... args) { \ THROW_##code(env->isolate(), format, std::forward(args)...); \ + } \ + template \ + inline void THROW_##code(Realm* realm, const char* format, Args&&... args) { \ + THROW_##code(realm->isolate(), format, std::forward(args)...); \ } ERRORS_WITH_CODE(V) #undef V diff --git a/src/node_messaging.cc b/src/node_messaging.cc index d3d2070d623c80..9992b16bd91c2d 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -30,6 +30,7 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Object; +using v8::ObjectTemplate; using v8::SharedArrayBuffer; using v8::SharedValueConveyor; using v8::String; @@ -717,7 +718,8 @@ MessagePort* MessagePort::New( std::unique_ptr data, std::shared_ptr sibling_group) { Context::Scope context_scope(context); - Local ctor_templ = GetMessagePortConstructorTemplate(env); + Local ctor_templ = + GetMessagePortConstructorTemplate(env->isolate_data()); // Construct a new instance, then assign the listener instance and possibly // the MessagePortData to it. @@ -1088,7 +1090,8 @@ void MessagePort::Stop(const FunctionCallbackInfo& args) { void MessagePort::CheckType(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); args.GetReturnValue().Set( - GetMessagePortConstructorTemplate(env)->HasInstance(args[0])); + GetMessagePortConstructorTemplate(env->isolate_data()) + ->HasInstance(args[0])); } void MessagePort::Drain(const FunctionCallbackInfo& args) { @@ -1165,28 +1168,30 @@ void MessagePort::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("emit_message_fn", emit_message_fn_); } -Local GetMessagePortConstructorTemplate(Environment* env) { +Local GetMessagePortConstructorTemplate( + IsolateData* isolate_data) { // Factor generating the MessagePort JS constructor into its own piece // of code, because it is needed early on in the child environment setup. - Local templ = env->message_port_constructor_template(); + Local templ = + isolate_data->message_port_constructor_template(); if (!templ.IsEmpty()) return templ; { - Isolate* isolate = env->isolate(); + Isolate* isolate = isolate_data->isolate(); Local m = NewFunctionTemplate(isolate, MessagePort::New); - m->SetClassName(env->message_port_constructor_string()); + m->SetClassName(isolate_data->message_port_constructor_string()); m->InstanceTemplate()->SetInternalFieldCount( MessagePort::kInternalFieldCount); - m->Inherit(HandleWrap::GetConstructorTemplate(env)); + m->Inherit(HandleWrap::GetConstructorTemplate(isolate_data)); SetProtoMethod(isolate, m, "postMessage", MessagePort::PostMessage); SetProtoMethod(isolate, m, "start", MessagePort::Start); - env->set_message_port_constructor_template(m); + isolate_data->set_message_port_constructor_template(m); } - return GetMessagePortConstructorTemplate(env); + return GetMessagePortConstructorTemplate(isolate_data); } // static @@ -1558,15 +1563,12 @@ static void BroadcastChannel(const FunctionCallbackInfo& args) { } } -static void InitMessaging(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Isolate* isolate = env->isolate(); +static void CreatePerIsolateProperties(IsolateData* isolate_data, + Local target) { + Isolate* isolate = isolate_data->isolate(); { - SetConstructorFunction(context, + SetConstructorFunction(isolate, target, "MessageChannel", NewFunctionTemplate(isolate, MessageChannel)); @@ -1577,30 +1579,35 @@ static void InitMessaging(Local target, t->InstanceTemplate()->SetInternalFieldCount( JSTransferable::kInternalFieldCount); t->SetClassName(OneByteString(isolate, "JSTransferable")); - env->isolate_data()->set_js_transferable_constructor_template(t); + isolate_data->set_js_transferable_constructor_template(t); } - SetConstructorFunction(context, + SetConstructorFunction(isolate, target, - env->message_port_constructor_string(), - GetMessagePortConstructorTemplate(env), - SetConstructorFunctionFlag::NONE); + isolate_data->message_port_constructor_string(), + GetMessagePortConstructorTemplate(isolate_data)); // These are not methods on the MessagePort prototype, because // the browser equivalents do not provide them. - SetMethod(context, target, "stopMessagePort", MessagePort::Stop); - SetMethod(context, target, "checkMessagePort", MessagePort::CheckType); - SetMethod(context, target, "drainMessagePort", MessagePort::Drain); + SetMethod(isolate, target, "stopMessagePort", MessagePort::Stop); + SetMethod(isolate, target, "checkMessagePort", MessagePort::CheckType); + SetMethod(isolate, target, "drainMessagePort", MessagePort::Drain); SetMethod( - context, target, "receiveMessageOnPort", MessagePort::ReceiveMessage); + isolate, target, "receiveMessageOnPort", MessagePort::ReceiveMessage); SetMethod( - context, target, "moveMessagePortToContext", MessagePort::MoveToContext); - SetMethod(context, + isolate, target, "moveMessagePortToContext", MessagePort::MoveToContext); + SetMethod(isolate, target, "setDeserializerCreateObjectFunction", SetDeserializerCreateObjectFunction); - SetMethod(context, target, "broadcastChannel", BroadcastChannel); + SetMethod(isolate, target, "broadcastChannel", BroadcastChannel); +} +static void CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); { Local domexception = GetDOMException(context).ToLocalChecked(); target @@ -1630,6 +1637,9 @@ static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { } // namespace worker } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(messaging, node::worker::InitMessaging) +NODE_BINDING_CONTEXT_AWARE_INTERNAL(messaging, + node::worker::CreatePerContextProperties) +NODE_BINDING_PER_ISOLATE_INIT(messaging, + node::worker::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(messaging, node::worker::RegisterExternalReferences) diff --git a/src/node_messaging.h b/src/node_messaging.h index d1c16bc1c03cb7..9a8f387fc2e88c 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -377,7 +377,7 @@ class JSTransferable : public BaseObject { }; v8::Local GetMessagePortConstructorTemplate( - Environment* env); + IsolateData* isolate_data); } // namespace worker } // namespace node diff --git a/src/node_process.h b/src/node_process.h index cb8c7962825f46..4bd94736c4547f 100644 --- a/src/node_process.h +++ b/src/node_process.h @@ -15,7 +15,7 @@ class MemoryTracker; class ExternalReferenceRegistry; class Realm; -void CreateEnvProxyTemplate(v8::Isolate* isolate, IsolateData* isolate_data); +void CreateEnvProxyTemplate(IsolateData* isolate_data); // Most of the time, it's best to use `console.error` to write // to the process.stderr stream. However, in some cases, such as @@ -48,6 +48,12 @@ void PatchProcessObject(const v8::FunctionCallbackInfo& args); namespace process { class BindingData : public SnapshotableObject { public: + enum InternalFields { + kBindingDataBaseField = BaseObject::kInternalFieldCount, + kHrTimeBuffer, + kInternalFieldCount + }; + static void AddMethods(v8::Isolate* isolate, v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); @@ -59,7 +65,7 @@ class BindingData : public SnapshotableObject { BindingData(Realm* realm, v8::Local object); - void MemoryInfo(MemoryTracker* tracker) const override; + SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(BindingData) SET_SELF_SIZE(BindingData) @@ -83,7 +89,7 @@ class BindingData : public SnapshotableObject { private: static constexpr size_t kBufferSize = std::max(sizeof(uint64_t), sizeof(uint32_t) * 3); - v8::Global array_buffer_; + bool HrTimeBufferValid(); std::shared_ptr backing_store_; // These need to be static so that we have their addresses available to diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 1b68207f3e3ba6..d421a355d46b9e 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -470,7 +470,7 @@ BindingData::BindingData(Realm* realm, v8::Local object) Isolate* isolate = realm->isolate(); Local context = realm->context(); Local ab = ArrayBuffer::New(isolate, kBufferSize); - array_buffer_.Reset(isolate, ab); + object->SetInternalField(kHrTimeBuffer, ab); object->Set(context, FIXED_ONE_BYTE_STRING(isolate, "hrtimeBuffer"), ab) .ToChecked(); backing_store_ = ab->GetBackingStore(); @@ -502,8 +502,11 @@ BindingData* BindingData::FromV8Value(Local value) { v8_object->GetAlignedPointerFromInternalField(BaseObject::kSlot)); } -void BindingData::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("array_buffer", array_buffer_); +bool BindingData::HrTimeBufferValid() { + v8::HandleScope scope(realm()->isolate()); + // Make sure the buffer is not wiped for snapshot. + Local ab = object()->GetInternalField(kHrTimeBuffer); + return ab->IsArrayBuffer(); } // This is the legacy version of hrtime before BigInt was introduced in @@ -517,7 +520,7 @@ void BindingData::MemoryInfo(MemoryTracker* tracker) const { // The third entry contains the remaining nanosecond part of the value. void BindingData::NumberImpl(BindingData* receiver) { // Make sure we don't accidentally access buffers wiped for snapshot. - CHECK(!receiver->array_buffer_.IsEmpty()); + CHECK(receiver->HrTimeBufferValid()); uint64_t t = uv_hrtime(); uint32_t* fields = static_cast(receiver->backing_store_->Data()); fields[0] = (t / NANOS_PER_SEC) >> 32; @@ -527,7 +530,7 @@ void BindingData::NumberImpl(BindingData* receiver) { void BindingData::BigIntImpl(BindingData* receiver) { // Make sure we don't accidentally access buffers wiped for snapshot. - CHECK(!receiver->array_buffer_.IsEmpty()); + CHECK(receiver->HrTimeBufferValid()); uint64_t t = uv_hrtime(); uint64_t* fields = static_cast(receiver->backing_store_->Data()); fields[0] = t; @@ -545,7 +548,8 @@ bool BindingData::PrepareForSerialization(Local context, v8::SnapshotCreator* creator) { // It's not worth keeping. // Release it, we will recreate it when the instance is dehydrated. - array_buffer_.Reset(); + object()->SetInternalField(kHrTimeBuffer, + v8::Undefined(context->GetIsolate())); // Return true because we need to maintain the reference to the binding from // JS land. return true; @@ -573,6 +577,7 @@ void BindingData::Deserialize(Local context, static void CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); + target->SetInternalFieldCount(BindingData::kInternalFieldCount); BindingData::AddMethods(isolate, target); // define various internal methods diff --git a/src/node_realm.cc b/src/node_realm.cc index 7101b23e347c7c..23fb6bd55213ee 100644 --- a/src/node_realm.cc +++ b/src/node_realm.cc @@ -344,10 +344,11 @@ MaybeLocal PrincipalRealm::BootstrapRealm() { return MaybeLocal(); } + // Setup process.env proxy. Local env_string = FIXED_ONE_BYTE_STRING(isolate_, "env"); Local env_proxy; - CreateEnvProxyTemplate(isolate_, env_->isolate_data()); - if (!env_->env_proxy_template()->NewInstance(context()).ToLocal(&env_proxy) || + if (!isolate_data()->env_proxy_template()->NewInstance(context()).ToLocal( + &env_proxy) || process_object()->Set(context(), env_string, env_proxy).IsNothing()) { return MaybeLocal(); } diff --git a/src/node_shadow_realm.cc b/src/node_shadow_realm.cc index 1cf0a57617b8b2..1f0689f18a1b9c 100644 --- a/src/node_shadow_realm.cc +++ b/src/node_shadow_realm.cc @@ -1,6 +1,7 @@ #include "node_shadow_realm.h" #include "env-inl.h" #include "node_errors.h" +#include "node_process.h" namespace node { namespace shadow_realm { @@ -9,6 +10,8 @@ using v8::EscapableHandleScope; using v8::HandleScope; using v8::Local; using v8::MaybeLocal; +using v8::Object; +using v8::String; using v8::Value; using TryCatchScope = node::errors::TryCatchScope; @@ -16,6 +19,11 @@ using TryCatchScope = node::errors::TryCatchScope; // static ShadowRealm* ShadowRealm::New(Environment* env) { ShadowRealm* realm = new ShadowRealm(env); + // TODO(legendecas): required by node::PromiseRejectCallback. + // Remove this once promise rejection doesn't need to be handled across + // realms. + realm->context()->SetSecurityToken( + env->principal_realm()->context()->GetSecurityToken()); // We do not expect the realm bootstrapping to throw any // exceptions. If it does, exit the current Node.js instance. @@ -32,6 +40,10 @@ MaybeLocal HostCreateShadowRealmContextCallback( Local initiator_context) { Environment* env = Environment::GetCurrent(initiator_context); EscapableHandleScope scope(env->isolate()); + + // We do not expect the realm bootstrapping to throw any + // exceptions. If it does, exit the current Node.js instance. + TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal); ShadowRealm* realm = ShadowRealm::New(env); if (realm != nullptr) { return scope.Escape(realm->context()); @@ -137,6 +149,25 @@ v8::MaybeLocal ShadowRealm::BootstrapRealm() { } } + if (ExecuteBootstrapper( + "internal/bootstrap/switches/does_not_own_process_state") + .IsEmpty()) { + return MaybeLocal(); + } + + // Setup process.env proxy. + Local env_string = FIXED_ONE_BYTE_STRING(isolate_, "env"); + Local env_proxy; + if (!isolate_data()->env_proxy_template()->NewInstance(context()).ToLocal( + &env_proxy) || + process_object()->Set(context(), env_string, env_proxy).IsNothing()) { + return MaybeLocal(); + } + + if (ExecuteBootstrapper("internal/bootstrap/shadow_realm").IsEmpty()) { + return MaybeLocal(); + } + return v8::True(isolate_); } diff --git a/test/fixtures/es-module-shadow-realm/builtin-modules.mjs b/test/fixtures/es-module-shadow-realm/builtin-modules.mjs new file mode 100644 index 00000000000000..993c5a39ccd34b --- /dev/null +++ b/test/fixtures/es-module-shadow-realm/builtin-modules.mjs @@ -0,0 +1,9 @@ +import { builtinModules, createRequire } from 'node:module'; + +export const builtinModulesStr = builtinModules.join(','); + +const require = createRequire(import.meta.url); +export function requireValue(specifier, name) { + const mod = require(specifier); + return mod.name; +} diff --git a/test/fixtures/es-module-shadow-realm/state-counter.mjs b/test/fixtures/es-module-shadow-realm/state-counter.mjs new file mode 100644 index 00000000000000..c547658bf455ba --- /dev/null +++ b/test/fixtures/es-module-shadow-realm/state-counter.mjs @@ -0,0 +1,4 @@ +let counter = 0; +export const getCounter = () => { + return counter++; +}; diff --git a/test/parallel/test-shadow-realm-allowed-builtin-modules.js b/test/parallel/test-shadow-realm-allowed-builtin-modules.js new file mode 100644 index 00000000000000..97bd227990bf0f --- /dev/null +++ b/test/parallel/test-shadow-realm-allowed-builtin-modules.js @@ -0,0 +1,37 @@ +// Flags: --experimental-shadow-realm +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const { builtinModules } = require('module'); + +async function main() { + const realm = new ShadowRealm(); + const mod = fixtures.path('es-module-shadow-realm', 'builtin-modules.mjs'); + const builtinModulesStr = await realm.importValue(mod, 'builtinModulesStr'); + const shadowRealmBuiltinModules = builtinModulesStr.split(','); + const diff = []; + for (const mod of builtinModules) { + if (!shadowRealmBuiltinModules.includes(mod)) { + diff.push(mod); + } + } + // Check that not all builtin modules are available in the ShadowRealm. + assert.ok(diff.length > 0); + + // Verify that dynamic import can not import disallowed builtin modules. + assert.ok(!shadowRealmBuiltinModules.includes('fs')); + await assert.rejects(realm.importValue('fs', 'readFileSync'), { + message: /Cannot find package 'fs'/, + }); + + // Verify that CJS require can not import disallowed builtin modules. + const innerRequireValue = await realm.importValue(mod, 'requireValue'); + assert.throws(() => { + innerRequireValue('fs', 'readFileSync'); + }, { + message: /Cannot find module 'fs'/, + }); +} + +main().then(common.mustCall()); diff --git a/test/parallel/test-shadow-realm-import-value-resolve.js b/test/parallel/test-shadow-realm-import-value-resolve.js new file mode 100644 index 00000000000000..bbafca8ef948d9 --- /dev/null +++ b/test/parallel/test-shadow-realm-import-value-resolve.js @@ -0,0 +1,26 @@ +// Flags: --experimental-shadow-realm +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const path = require('path'); + +async function main() { + const realm = new ShadowRealm(); + const mod = fixtures.path('es-module-shadow-realm', 'state-counter.mjs'); + + const dirname = __dirname; + // Set process cwd to the parent directory of __dirname. + const cwd = path.dirname(dirname); + process.chdir(cwd); + const relativePath = `./${path.relative(cwd, mod)}`; + + // Make sure that the module can not be resolved relative to __filename. + assert.throws(() => require.resolve(relativePath), { code: 'MODULE_NOT_FOUND' }); + + // Resolve relative to the current working directory. + const getCounter = await realm.importValue(relativePath, 'getCounter'); + assert.strictEqual(typeof getCounter, 'function'); +} + +main().then(common.mustCall()); diff --git a/test/parallel/test-shadow-realm-module.js b/test/parallel/test-shadow-realm-module.js new file mode 100644 index 00000000000000..7916ebb9049914 --- /dev/null +++ b/test/parallel/test-shadow-realm-module.js @@ -0,0 +1,29 @@ +// Flags: --experimental-shadow-realm +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); + +async function main() { + const realm = new ShadowRealm(); + const mod = fixtures.path('es-module-shadow-realm', 'state-counter.mjs'); + const getCounter = await realm.importValue(mod, 'getCounter'); + assert.strictEqual(getCounter(), 0); + const getCounter1 = await realm.importValue(mod, 'getCounter'); + // Returned value is a newly wrapped function. + assert.notStrictEqual(getCounter, getCounter1); + // Verify that the module state is shared between two `importValue` calls. + assert.strictEqual(getCounter1(), 1); + assert.strictEqual(getCounter(), 2); + + const { getCounter: getCounterThisRealm } = await import(mod); + assert.notStrictEqual(getCounterThisRealm, getCounter); + // Verify that the module state is not shared between two realms. + assert.strictEqual(getCounterThisRealm(), 0); + assert.strictEqual(getCounter(), 3); + + // Verify that shadow realm rejects to import a non-existing module. + await assert.rejects(realm.importValue('non-exists', 'exports'), TypeError); +} + +main().then(common.mustCall());