Skip to content

Commit 21ab3c0

Browse files
legendecasUlisesGascon
authored andcommitted
module: bootstrap module loaders in shadow realm
This bootstraps ESM loaders in the ShadowRealm with `ShadowRealm.prototype.importValue` as its entry point and enables loading ESM and CJS modules in the ShadowRealm. The module is imported without a parent URL and resolved with the current process's working directory. PR-URL: #48655 Backport-PR-URL: #51239 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 8e886a2 commit 21ab3c0

24 files changed

+444
-122
lines changed

lib/internal/bootstrap/realm.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050

5151
const {
5252
ArrayFrom,
53+
ArrayPrototypeFilter,
54+
ArrayPrototypeIncludes,
5355
ArrayPrototypeMap,
5456
ArrayPrototypePush,
5557
ArrayPrototypeSlice,
@@ -215,8 +217,8 @@ const internalBuiltinIds = builtinIds
215217
.filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId);
216218

217219
// When --expose-internals is on we'll add the internal builtin ids to these.
218-
const canBeRequiredByUsersList = new SafeSet(publicBuiltinIds);
219-
const canBeRequiredByUsersWithoutSchemeList =
220+
let canBeRequiredByUsersList = new SafeSet(publicBuiltinIds);
221+
let canBeRequiredByUsersWithoutSchemeList =
220222
new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id)));
221223

222224
/**
@@ -269,6 +271,13 @@ class BuiltinModule {
269271
}
270272
}
271273

274+
static setRealmAllowRequireByUsers(ids) {
275+
canBeRequiredByUsersList =
276+
new SafeSet(ArrayPrototypeFilter(ids, (id) => ArrayPrototypeIncludes(publicBuiltinIds, id)));
277+
canBeRequiredByUsersWithoutSchemeList =
278+
new SafeSet(ArrayPrototypeFilter(ids, (id) => !schemelessBlockList.has(id)));
279+
}
280+
272281
// To be called during pre-execution when --expose-internals is on.
273282
// Enables the user-land module loader to access internal modules.
274283
static exposeInternals() {
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
// This script sets up the context for shadow realms.
4+
5+
const {
6+
prepareShadowRealmExecution,
7+
} = require('internal/process/pre_execution');
8+
const {
9+
BuiltinModule,
10+
} = require('internal/bootstrap/realm');
11+
12+
BuiltinModule.setRealmAllowRequireByUsers([
13+
/**
14+
* The built-in modules exposed in the ShadowRealm must each be providing
15+
* platform capabilities with no authority to cause side effects such as
16+
* I/O or mutation of values that are shared across different realms within
17+
* the same Node.js environment.
18+
*/
19+
]);
20+
21+
prepareShadowRealmExecution();

lib/internal/main/worker_thread.js

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ port.on('message', (message) => {
136136
const isLoaderWorker =
137137
doEval === 'internal' &&
138138
filename === require('internal/modules/esm/utils').loaderWorkerId;
139+
// Disable custom loaders in loader worker.
139140
setupUserModules(isLoaderWorker);
140141

141142
if (!hasStdin)

lib/internal/modules/esm/loader.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -528,9 +528,10 @@ let emittedLoaderFlagWarning = false;
528528
*/
529529
function createModuleLoader() {
530530
let customizations = null;
531-
// Don't spawn a new worker if we're already in a worker thread created by instantiating CustomizedModuleLoader;
532-
// doing so would cause an infinite loop.
533-
if (!require('internal/modules/esm/utils').isLoaderWorker()) {
531+
// Don't spawn a new worker if custom loaders are disabled. For instance, if
532+
// we're already in a worker thread created by instantiating
533+
// CustomizedModuleLoader; doing so would cause an infinite loop.
534+
if (!require('internal/modules/esm/utils').forceDefaultLoader()) {
534535
const userLoaderPaths = getOptionValue('--experimental-loader');
535536
if (userLoaderPaths.length > 0) {
536537
if (!emittedLoaderFlagWarning) {

lib/internal/modules/esm/utils.js

+33-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
ArrayIsArray,
55
SafeSet,
66
SafeWeakMap,
7+
Symbol,
78
ObjectFreeze,
89
} = primordials;
910

@@ -157,6 +158,26 @@ function registerModule(referrer, registry) {
157158
moduleRegistries.set(idSymbol, registry);
158159
}
159160

161+
/**
162+
* Registers the ModuleRegistry for dynamic import() calls with a realm
163+
* as the referrer. Similar to {@link registerModule}, but this function
164+
* generates a new id symbol instead of using the one from the referrer
165+
* object.
166+
* @param {globalThis} globalThis The globalThis object of the realm.
167+
* @param {ModuleRegistry} registry
168+
*/
169+
function registerRealm(globalThis, registry) {
170+
let idSymbol = globalThis[host_defined_option_symbol];
171+
// If the per-realm host-defined options is already registered, do nothing.
172+
if (idSymbol) {
173+
return;
174+
}
175+
// Otherwise, register the per-realm host-defined options.
176+
idSymbol = Symbol('Realm globalThis');
177+
globalThis[host_defined_option_symbol] = idSymbol;
178+
moduleRegistries.set(idSymbol, registry);
179+
}
180+
160181
/**
161182
* Defines the `import.meta` object for a given module.
162183
* @param {symbol} symbol - Reference to the module.
@@ -192,28 +213,29 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, attrib
192213
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
193214
}
194215

195-
let _isLoaderWorker = false;
216+
let _forceDefaultLoader = false;
196217
/**
197218
* Initializes handling of ES modules.
198219
* This is configured during pre-execution. Specifically it's set to true for
199220
* the loader worker in internal/main/worker_thread.js.
200-
* @param {boolean} [isLoaderWorker=false] - A boolean indicating whether the loader is a worker or not.
221+
* @param {boolean} [forceDefaultLoader=false] - A boolean indicating disabling custom loaders.
201222
*/
202-
function initializeESM(isLoaderWorker = false) {
203-
_isLoaderWorker = isLoaderWorker;
223+
function initializeESM(forceDefaultLoader = false) {
224+
_forceDefaultLoader = forceDefaultLoader;
204225
initializeDefaultConditions();
205-
// Setup per-isolate callbacks that locate data or callbacks that we keep
226+
// Setup per-realm callbacks that locate data or callbacks that we keep
206227
// track of for different ESM modules.
207228
setInitializeImportMetaObjectCallback(initializeImportMetaObject);
208229
setImportModuleDynamicallyCallback(importModuleDynamicallyCallback);
209230
}
210231

211232
/**
212-
* Determine whether the current process is a loader worker.
213-
* @returns {boolean} Whether the current process is a loader worker.
233+
* Determine whether custom loaders are disabled and it is forced to use the
234+
* default loader.
235+
* @returns {boolean}
214236
*/
215-
function isLoaderWorker() {
216-
return _isLoaderWorker;
237+
function forceDefaultLoader() {
238+
return _forceDefaultLoader;
217239
}
218240

219241
/**
@@ -253,10 +275,11 @@ async function initializeHooks() {
253275

254276
module.exports = {
255277
registerModule,
278+
registerRealm,
256279
initializeESM,
257280
initializeHooks,
258281
getDefaultConditions,
259282
getConditionsSet,
260283
loaderWorkerId: 'internal/modules/esm/worker',
261-
isLoaderWorker,
284+
forceDefaultLoader,
262285
};

lib/internal/process/pre_execution.js

+30-8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ function prepareWorkerThreadExecution() {
6767
});
6868
}
6969

70+
function prepareShadowRealmExecution() {
71+
const { registerRealm } = require('internal/modules/esm/utils');
72+
// Patch the process object with legacy properties and normalizations.
73+
// Do not expand argv1 as it is not available in ShadowRealm.
74+
patchProcessObject(false);
75+
setupDebugEnv();
76+
77+
// Disable custom loaders in ShadowRealm.
78+
setupUserModules(true);
79+
registerRealm(globalThis, {
80+
__proto__: null,
81+
importModuleDynamically: (specifier, _referrer, attributes) => {
82+
// The handler for `ShadowRealm.prototype.importValue`.
83+
const { esmLoader } = require('internal/process/esm_loader');
84+
// `parentURL` is not set in the case of a ShadowRealm top-level import.
85+
return esmLoader.import(specifier, undefined, attributes);
86+
},
87+
});
88+
}
89+
7090
function prepareExecution(options) {
7191
const { expandArgv1, initializeModules, isMainThread } = options;
7292

@@ -160,16 +180,17 @@ function setupSymbolDisposePolyfill() {
160180
}
161181
}
162182

163-
function setupUserModules(isLoaderWorker = false) {
183+
function setupUserModules(forceDefaultLoader = false) {
164184
initializeCJSLoader();
165-
initializeESMLoader(isLoaderWorker);
185+
initializeESMLoader(forceDefaultLoader);
166186
const CJSLoader = require('internal/modules/cjs/loader');
167187
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
168-
// Loader workers are responsible for doing this themselves.
169-
if (isLoaderWorker) {
170-
return;
188+
// Do not enable preload modules if custom loaders are disabled.
189+
// For example, loader workers are responsible for doing this themselves.
190+
// And preload modules are not supported in ShadowRealm as well.
191+
if (!forceDefaultLoader) {
192+
loadPreloadModules();
171193
}
172-
loadPreloadModules();
173194
// Need to be done after --require setup.
174195
initializeFrozenIntrinsics();
175196
}
@@ -687,9 +708,9 @@ function initializeCJSLoader() {
687708
initializeCJS();
688709
}
689710

690-
function initializeESMLoader(isLoaderWorker) {
711+
function initializeESMLoader(forceDefaultLoader) {
691712
const { initializeESM } = require('internal/modules/esm/utils');
692-
initializeESM(isLoaderWorker);
713+
initializeESM(forceDefaultLoader);
693714

694715
// Patch the vm module when --experimental-vm-modules is on.
695716
// Please update the comments in vm.js when this block changes.
@@ -765,6 +786,7 @@ module.exports = {
765786
setupUserModules,
766787
prepareMainThreadExecution,
767788
prepareWorkerThreadExecution,
789+
prepareShadowRealmExecution,
768790
markBootstrapComplete,
769791
loadPreloadModules,
770792
initializeFrozenIntrinsics,

src/async_wrap.cc

+13-9
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,9 @@ void AsyncWrap::CreatePerContextProperties(Local<Object> target,
372372
Local<Value> unused,
373373
Local<Context> context,
374374
void* priv) {
375-
Environment* env = Environment::GetCurrent(context);
376-
Isolate* isolate = env->isolate();
375+
Realm* realm = Realm::GetCurrent(context);
376+
Environment* env = realm->env();
377+
Isolate* isolate = realm->isolate();
377378
HandleScope scope(isolate);
378379

379380
PropertyAttribute ReadOnlyDontDelete =
@@ -446,13 +447,16 @@ void AsyncWrap::CreatePerContextProperties(Local<Object> target,
446447

447448
#undef FORCE_SET_TARGET_FIELD
448449

449-
env->set_async_hooks_init_function(Local<Function>());
450-
env->set_async_hooks_before_function(Local<Function>());
451-
env->set_async_hooks_after_function(Local<Function>());
452-
env->set_async_hooks_destroy_function(Local<Function>());
453-
env->set_async_hooks_promise_resolve_function(Local<Function>());
454-
env->set_async_hooks_callback_trampoline(Local<Function>());
455-
env->set_async_hooks_binding(target);
450+
// TODO(legendecas): async hook functions are not realm-aware yet.
451+
// This simply avoid overriding principal realm's functions when a
452+
// ShadowRealm initializes the binding.
453+
realm->set_async_hooks_init_function(Local<Function>());
454+
realm->set_async_hooks_before_function(Local<Function>());
455+
realm->set_async_hooks_after_function(Local<Function>());
456+
realm->set_async_hooks_destroy_function(Local<Function>());
457+
realm->set_async_hooks_promise_resolve_function(Local<Function>());
458+
realm->set_async_hooks_callback_trampoline(Local<Function>());
459+
realm->set_async_hooks_binding(target);
456460
}
457461

458462
void AsyncWrap::RegisterExternalReferences(

src/env.cc

+7-4
Original file line numberDiff line numberDiff line change
@@ -1654,10 +1654,13 @@ void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const {
16541654
void AsyncHooks::grow_async_ids_stack() {
16551655
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
16561656

1657-
env()->async_hooks_binding()->Set(
1658-
env()->context(),
1659-
env()->async_ids_stack_string(),
1660-
async_ids_stack_.GetJSArray()).Check();
1657+
env()
1658+
->principal_realm()
1659+
->async_hooks_binding()
1660+
->Set(env()->context(),
1661+
env()->async_ids_stack_string(),
1662+
async_ids_stack_.GetJSArray())
1663+
.Check();
16611664
}
16621665

16631666
void AsyncHooks::FailWithCorruptedAsyncStack(double expected_async_id) {

0 commit comments

Comments
 (0)