GeezerVison is showing github.com — module: bootstrap module loaders in shadow realm · nodejs/node@c7a7493
File treeExpand file treeCollapse file tree Open diff view settings fixtures/es-module-shadow-realm
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/bootstrap/realm.js +11-2Lines changed: 11 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 50 | 50 |
| 51 | 51 | const { | 52 | 52 | ArrayFrom, | | 53 | + ArrayPrototypeFilter, | | 54 | + ArrayPrototypeIncludes, | 53 | 55 | ArrayPrototypeMap, | 54 | 56 | ArrayPrototypePush, | 55 | 57 | ArrayPrototypeSlice, | @@ -215,8 +217,8 @@ const internalBuiltinIds = builtinIds | 215 | 217 | .filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId); | 216 | 218 |
| 217 | 219 | // 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 = | 220 | 222 | new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id))); | 221 | 223 |
| 222 | 224 | /** | @@ -269,6 +271,13 @@ class BuiltinModule { | 269 | 271 | } | 270 | 272 | } | 271 | 273 |
| | 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 | + | 272 | 281 | // To be called during pre-execution when --expose-internals is on. | 273 | 282 | // Enables the user-land module loader to access internal modules. | 274 | 283 | static exposeInternals() { | |
Collapse file Copy file name to clipboard +21Lines changed: 21 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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(); |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/worker_thread.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -136,6 +136,7 @@ port.on('message', (message) => { | 136 | 136 | const isLoaderWorker = | 137 | 137 | doEval === 'internal' && | 138 | 138 | filename === require('internal/modules/esm/utils').loaderWorkerId; | | 139 | + // Disable custom loaders in loader worker. | 139 | 140 | setupUserModules(isLoaderWorker); | 140 | 141 |
| 141 | 142 | if (!hasStdin) | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/loader.js +4-3Lines changed: 4 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -528,9 +528,10 @@ let emittedLoaderFlagWarning = false; | 528 | 528 | */ | 529 | 529 | function createModuleLoader() { | 530 | 530 | 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()) { | 534 | 535 | const userLoaderPaths = getOptionValue('--experimental-loader'); | 535 | 536 | if (userLoaderPaths.length > 0) { | 536 | 537 | if (!emittedLoaderFlagWarning) { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/utils.js +33-10Lines changed: 33 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 4 | 4 | ArrayIsArray, | 5 | 5 | SafeSet, | 6 | 6 | SafeWeakMap, | | 7 | + Symbol, | 7 | 8 | ObjectFreeze, | 8 | 9 | } = primordials; | 9 | 10 |
| @@ -157,6 +158,26 @@ function registerModule(referrer, registry) { | 157 | 158 | moduleRegistries.set(idSymbol, registry); | 158 | 159 | } | 159 | 160 |
| | 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 | + | 160 | 181 | /** | 161 | 182 | * Defines the `import.meta` object for a given module. | 162 | 183 | * @param {symbol} symbol - Reference to the module. | @@ -192,28 +213,29 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, attrib | 192 | 213 | throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); | 193 | 214 | } | 194 | 215 |
| 195 | | -let _isLoaderWorker = false; | | 216 | +let _forceDefaultLoader = false; | 196 | 217 | /** | 197 | 218 | * Initializes handling of ES modules. | 198 | 219 | * This is configured during pre-execution. Specifically it's set to true for | 199 | 220 | * 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. | 201 | 222 | */ | 202 | | -function initializeESM(isLoaderWorker = false) { | 203 | | - _isLoaderWorker = isLoaderWorker; | | 223 | +function initializeESM(forceDefaultLoader = false) { | | 224 | + _forceDefaultLoader = forceDefaultLoader; | 204 | 225 | 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 | 206 | 227 | // track of for different ESM modules. | 207 | 228 | setInitializeImportMetaObjectCallback(initializeImportMetaObject); | 208 | 229 | setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); | 209 | 230 | } | 210 | 231 |
| 211 | 232 | /** | 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} | 214 | 236 | */ | 215 | | -function isLoaderWorker() { | 216 | | - return _isLoaderWorker; | | 237 | +function forceDefaultLoader() { | | 238 | + return _forceDefaultLoader; | 217 | 239 | } | 218 | 240 |
| 219 | 241 | /** | @@ -251,10 +273,11 @@ async function initializeHooks() { | 251 | 273 |
| 252 | 274 | module.exports = { | 253 | 275 | registerModule, | | 276 | + registerRealm, | 254 | 277 | initializeESM, | 255 | 278 | initializeHooks, | 256 | 279 | getDefaultConditions, | 257 | 280 | getConditionsSet, | 258 | 281 | loaderWorkerId: 'internal/modules/esm/worker', | 259 | | - isLoaderWorker, | | 282 | + forceDefaultLoader, | 260 | 283 | }; |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js +30-8Lines changed: 30 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -67,6 +67,26 @@ function prepareWorkerThreadExecution() { | 67 | 67 | }); | 68 | 68 | } | 69 | 69 |
| | 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 | + | 70 | 90 | function prepareExecution(options) { | 71 | 91 | const { expandArgv1, initializeModules, isMainThread } = options; | 72 | 92 |
| @@ -161,16 +181,17 @@ function setupSymbolDisposePolyfill() { | 161 | 181 | } | 162 | 182 | } | 163 | 183 |
| 164 | | -function setupUserModules(isLoaderWorker = false) { | | 184 | +function setupUserModules(forceDefaultLoader = false) { | 165 | 185 | initializeCJSLoader(); | 166 | | - initializeESMLoader(isLoaderWorker); | | 186 | + initializeESMLoader(forceDefaultLoader); | 167 | 187 | const CJSLoader = require('internal/modules/cjs/loader'); | 168 | 188 | assert(!CJSLoader.hasLoadedAnyUserCJSModule); | 169 | | - // Loader workers are responsible for doing this themselves. | 170 | | - if (isLoaderWorker) { | 171 | | - return; | | 189 | + // Do not enable preload modules if custom loaders are disabled. | | 190 | + // For example, loader workers are responsible for doing this themselves. | | 191 | + // And preload modules are not supported in ShadowRealm as well. | | 192 | + if (!forceDefaultLoader) { | | 193 | + loadPreloadModules(); | 172 | 194 | } | 173 | | - loadPreloadModules(); | 174 | 195 | // Need to be done after --require setup. | 175 | 196 | initializeFrozenIntrinsics(); | 176 | 197 | } | @@ -701,9 +722,9 @@ function initializeCJSLoader() { | 701 | 722 | initializeCJS(); | 702 | 723 | } | 703 | 724 |
| 704 | | -function initializeESMLoader(isLoaderWorker) { | | 725 | +function initializeESMLoader(forceDefaultLoader) { | 705 | 726 | const { initializeESM } = require('internal/modules/esm/utils'); | 706 | | - initializeESM(isLoaderWorker); | | 727 | + initializeESM(forceDefaultLoader); | 707 | 728 |
| 708 | 729 | // Patch the vm module when --experimental-vm-modules is on. | 709 | 730 | // Please update the comments in vm.js when this block changes. | @@ -779,6 +800,7 @@ module.exports = { | 779 | 800 | setupUserModules, | 780 | 801 | prepareMainThreadExecution, | 781 | 802 | prepareWorkerThreadExecution, | | 803 | + prepareShadowRealmExecution, | 782 | 804 | markBootstrapComplete, | 783 | 805 | loadPreloadModules, | 784 | 806 | initializeFrozenIntrinsics, | |
Collapse file Copy file name to clipboardExpand all lines: src/async_wrap.cc +13-9Lines changed: 13 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -372,8 +372,9 @@ void AsyncWrap::CreatePerContextProperties(Local<Object> target, | 372 | 372 | Local<Value> unused, | 373 | 373 | Local<Context> context, | 374 | 374 | 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(); | 377 | 378 | HandleScope scope(isolate); | 378 | 379 |
| 379 | 380 | PropertyAttribute ReadOnlyDontDelete = | @@ -446,13 +447,16 @@ void AsyncWrap::CreatePerContextProperties(Local<Object> target, | 446 | 447 |
| 447 | 448 | #undef FORCE_SET_TARGET_FIELD | 448 | 449 |
| 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); | 456 | 460 | } | 457 | 461 |
| 458 | 462 | void AsyncWrap::RegisterExternalReferences( | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +7-4Lines changed: 7 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1651,10 +1651,13 @@ void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const { | 1651 | 1651 | void AsyncHooks::grow_async_ids_stack() { | 1652 | 1652 | async_ids_stack_.reserve(async_ids_stack_.Length() * 3); | 1653 | 1653 |
| 1654 | | - env()->async_hooks_binding()->Set( | 1655 | | - env()->context(), | 1656 | | - env()->async_ids_stack_string(), | 1657 | | - async_ids_stack_.GetJSArray()).Check(); | | 1654 | + env() | | 1655 | + ->principal_realm() | | 1656 | + ->async_hooks_binding() | | 1657 | + ->Set(env()->context(), | | 1658 | + env()->async_ids_stack_string(), | | 1659 | + async_ids_stack_.GetJSArray()) | | 1660 | + .Check(); | 1658 | 1661 | } | 1659 | 1662 |
| 1660 | 1663 | void AsyncHooks::FailWithCorruptedAsyncStack(double expected_async_id) { | |
|