From fcf2b2764a036cb0ed80fccd6394281946c9c7e6 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Thu, 21 May 2026 04:09:52 +0800 Subject: [PATCH 1/4] lib: add `internal/primordials_staging` module Signed-off-by: LiviaMedeiros --- doc/contributing/primordials.md | 53 +++++++++ lib/eslint.config_partial.mjs | 16 +-- lib/internal/freeze_intrinsics.js | 3 +- lib/internal/fs/utils.js | 6 +- lib/internal/modules/esm/translators.js | 4 +- lib/internal/primordials_staging.js | 88 +++++++++++++++ lib/internal/process/pre_execution.js | 2 + lib/internal/util.js | 4 +- lib/internal/util/comparisons.js | 4 +- lib/v8.js | 6 +- test/parallel/test-bootstrap-modules.js | 1 + .../test-internal-primordials-staging.js | 102 ++++++++++++++++++ 12 files changed, 268 insertions(+), 21 deletions(-) create mode 100644 lib/internal/primordials_staging.js create mode 100644 test/parallel/test-internal-primordials-staging.js diff --git a/doc/contributing/primordials.md b/doc/contributing/primordials.md index 4b143c485ff9d9..d0870dfcf5eb34 100644 --- a/doc/contributing/primordials.md +++ b/doc/contributing/primordials.md @@ -105,6 +105,59 @@ There are some built-in functions that accept a variable number of arguments the list of arguments as an array. You can use primordial function with the suffix `Apply` (e.g.: `MathMaxApply`, `ArrayPrototypePushApply`) to do that. +## Staging primordials + +Conditinally present built-ins can not be primordials. This usually applies +to every experimental feature that either still exists only behind runtime flag, +or is enabled by default but still can be disabled by `--no-` runtime flag. + +Instead, these should be stored in `internal/primordials_staging` module. +This module is populated once at pre-execution stage and can not be changed +afterwards. + +Whenever a new conditional feature is used within Node.js core, it should be +added to this module instead of getting it directly from globals, to make +it unaffected by userland. + +Whenever a conditional feature graduates, it should be added to regular primordials +and removed from staging primordials. + +### Lazy-loaded staging primordials + +Some internal modules are used in early bootstrap, and staging primordials module +might not be initialized yet. If that's the case, do not destructure the module +synchronously, and instead get the built-ins lazily in runtime. + +For example, instead of this on top-level of the module: + +```js +// For modules that are loaded _after_ pre-exec, this is still safe and preferred +const { Float16Array } = require('internal/primordials_staging'); +``` + +Use either: + +```js +// Safe to import synchronously, even though the values are not defined yet +const primordialsStaging = require('internal/primordials_staging'); + +let SafeFloat16Array; +function numberToFloat16(n) { + SafeFloat16Array ??= primordialsStaging.Float16Array; + return new SafeFloat16Array([ n ])[0]; +} +``` + +Or: + +```js +let SafeFloat16Array; +function numberToFloat16(n) { + SafeFloat16Array ??= require('internal/primordials_staging').Float16Array; + return new SafeFloat16Array([ n ])[0]; +} +``` + ## Primordials with known performance issues One of the reasons why the current Node.js API is not completely tamper-proof is diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index d4c3fd688314c2..f55f353475d237 100644 --- a/lib/eslint.config_partial.mjs +++ b/lib/eslint.config_partial.mjs @@ -162,11 +162,11 @@ export default [ // disabled with --without-intl build flag. { name: 'Intl', - message: 'Use `const { Intl } = globalThis;` instead of the global.', + message: "Use `const { Intl } = require('internal/primordials_staging');` instead of the global.", }, { name: 'Iterator', - message: 'Use `const { Iterator } = globalThis;` instead of the global.', + message: "Use `const { Iterator } = require('internal/primordials_staging');` instead of the global.", }, { name: 'MessageChannel', @@ -248,7 +248,7 @@ export default [ // disabled with --no-harmony-shadow-realm CLI flag. { name: 'ShadowRealm', - message: 'Use `const { ShadowRealm } = globalThis;` instead of the global.', + message: "Use `const { ShadowRealm } = require('internal/primordials_staging');` instead of the global.", }, // SharedArrayBuffer is not available in primordials because it can be // disabled with --enable-sharedarraybuffer-per-context CLI flag. @@ -260,7 +260,7 @@ export default [ // disabled with --no-harmony-temporal CLI flag. { name: 'Temporal', - message: 'Use `const { Temporal } = globalThis;` instead of the global.', + message: "Use `const { Temporal } = require('internal/primordials_staging');` instead of the global.", }, { name: 'TextDecoder', @@ -298,7 +298,7 @@ export default [ // disabled with --jitless CLI flag. { name: 'WebAssembly', - message: 'Use `const { WebAssembly } = globalThis;` instead of the global.', + message: "Use `const { WebAssembly } = require('internal/primordials_staging');` instead of the global.", }, { name: 'WritableStream', @@ -396,17 +396,17 @@ export default [ // disabled with --no-js-float16array CLI flag. { name: 'Float16Array', - message: 'Use `const { Float16Array } = globalThis;` instead of the global.', + message: "Use `const { Float16Array } = require('internal/primordials_staging');` instead of the global.", }, // DisposableStack and AsyncDisposableStack are not available in primordials because they can be // disabled with --no-js-explicit-resource-management CLI flag. { name: 'DisposableStack', - message: 'Use `const { DisposableStack } = globalThis;` instead of the global.', + message: "Use `const { DisposableStack } = require('internal/primordials_staging');` instead of the global.", }, { name: 'AsyncDisposableStack', - message: 'Use `const { AsyncDisposableStack } = globalThis;` instead of the global.', + message: "Use `const { AsyncDisposableStack } = require('internal/primordials_staging');` instead of the global.", }, ], 'no-restricted-modules': [ diff --git a/lib/internal/freeze_intrinsics.js b/lib/internal/freeze_intrinsics.js index 4f083dc34f44d2..115fe59c6707e4 100644 --- a/lib/internal/freeze_intrinsics.js +++ b/lib/internal/freeze_intrinsics.js @@ -128,13 +128,12 @@ const { globalThis, unescape, } = primordials; - const { Intl, SharedArrayBuffer, Temporal, WebAssembly, -} = globalThis; +} = require('internal/primordials_staging'); module.exports = function() { const { Console } = require('internal/console/constructor'); diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 811c52aeffb8b9..ed346a44c834bf 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -23,8 +23,8 @@ const { Symbol, TypedArrayPrototypeAt, TypedArrayPrototypeIncludes, - globalThis, } = primordials; +const primordialsStaging = require('internal/primordials_staging'); const { Buffer } = require('buffer'); const { @@ -441,7 +441,7 @@ function nsFromTimeSpecBigInt(sec, nsec) { let TemporalInstant; function instantFromNs(nsec) { - TemporalInstant ??= globalThis.Temporal?.Instant; + TemporalInstant ??= primordialsStaging.Temporal?.Instant; if (TemporalInstant === undefined) { throw new ERR_NO_TEMPORAL(); } @@ -449,7 +449,7 @@ function instantFromNs(nsec) { } function instantFromTimeSpecMs(msec, nsec) { - TemporalInstant ??= globalThis.Temporal?.Instant; + TemporalInstant ??= primordialsStaging.Temporal?.Instant; if (TemporalInstant === undefined) { throw new ERR_NO_TEMPORAL(); } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index c453e2b54f5957..f7a0215fccf672 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -13,8 +13,8 @@ const { StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeStartsWith, - globalThis, } = primordials; +const primordialsStaging = require('internal/primordials_staging'); const { compileFunctionForCJSLoader, @@ -554,7 +554,7 @@ const wasmInstances = new SafeWeakMap(); translators.set('wasm', function(url, translateContext) { const { source } = translateContext; // WebAssembly global is not available during snapshot building, so we need to get it lazily. - const { WebAssembly } = globalThis; + const { WebAssembly } = primordialsStaging; assertBufferSource(source, false, 'load'); debug(`Translating WASMModule ${url}`, translateContext); diff --git a/lib/internal/primordials_staging.js b/lib/internal/primordials_staging.js new file mode 100644 index 00000000000000..885ab94ed41bcd --- /dev/null +++ b/lib/internal/primordials_staging.js @@ -0,0 +1,88 @@ +'use strict'; + +// This file stores JS builtins that can not become primordials yet because +// they can be disabled by runtime flags, or are not enabled by default yet. + +// Without this module, the only choice we would have is getting them from +// the global proxy which is mutable from userland. This is especially important +// for lazy-loaded builtins required in modules participating in early bootstrap. +// In such modules, we are usually unable to get these builtins synchronously, +// this applies to synchronous destructuring of this module. +// Importing the module itself is fine at any point, including top level of file. + +let _AsyncDisposableStack; +let _DisposableStack; +let _Float16Array; +let _Intl; +let _Iterator; +let _ShadowRealm; +let _SharedArrayBuffer; +let _Temporal; +let _Uint8ArrayFromHex; +let _Uint8ArrayFromBase64; +let _WebAssembly; + +module.exports = { + get AsyncDisposableStack() { + return _AsyncDisposableStack; + }, + get DisposableStack() { + return _DisposableStack; + }, + get Float16Array() { + return _Float16Array; + }, + get Intl() { + return _Intl; + }, + get Iterator() { + return _Iterator; + }, + get ShadowRealm() { + return _ShadowRealm; + }, + get SharedArrayBuffer() { + return _SharedArrayBuffer; + }, + get Temporal() { + return _Temporal; + }, + get Uint8ArrayFromHex() { + return _Uint8ArrayFromHex; + }, + get Uint8ArrayFromBase64() { + return _Uint8ArrayFromBase64; + }, + get WebAssembly() { + return _WebAssembly; + }, + _init({ + AsyncDisposableStack, + DisposableStack, + Float16Array, + Intl, + Iterator, + ShadowRealm, + SharedArrayBuffer, + Temporal, + Uint8Array: { + fromBase64, + fromHex, + }, + WebAssembly, + }) { + _AsyncDisposableStack = AsyncDisposableStack; + _DisposableStack = DisposableStack; + _Float16Array = Float16Array; + _Intl = Intl; + _Iterator = Iterator; + _ShadowRealm = ShadowRealm; + _SharedArrayBuffer = SharedArrayBuffer; + _Temporal = Temporal; + _Uint8ArrayFromBase64 = fromBase64; + _Uint8ArrayFromHex = fromHex; + _WebAssembly = WebAssembly; + + delete this._init; + }, +}; diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 16a80c2d4f410f..39caf2188cf407 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -107,6 +107,8 @@ function prepareExecution(options) { refreshRuntimeOptions(); + require('internal/primordials_staging')._init?.(globalThis); + // Patch the process object and get the resolved main entry point. const mainEntry = patchProcessObject(expandArgv1); setupTraceCategoryState(); diff --git a/lib/internal/util.js b/lib/internal/util.js index 34af9ca6f61a6f..54297e7f952049 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -46,8 +46,8 @@ const { SymbolPrototypeGetDescription, SymbolReplace, SymbolSplit, - globalThis, } = primordials; +const stagingPrimordials = require('internal/primordials_staging'); const { codes: { @@ -246,7 +246,7 @@ function assertCrypto() { function assertTypeScript() { if (noTypeScript) throw new ERR_NO_TYPESCRIPT(); - if (globalThis.WebAssembly === undefined) + if (stagingPrimordials.WebAssembly === undefined) throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('TypeScript'); } diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 80d39756cf155a..1591c932bfdbfc 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -50,8 +50,10 @@ const { Uint8ClampedArray, WeakMap, WeakSet, - globalThis: { Float16Array }, } = primordials; +const { + Float16Array, +} = require('internal/primordials_staging'); const { compare } = internalBinding('buffer'); const assert = require('internal/assert'); diff --git a/lib/v8.js b/lib/v8.js index bb174f8d524305..4741bf2194f446 100644 --- a/lib/v8.js +++ b/lib/v8.js @@ -32,10 +32,10 @@ const { Uint32Array, Uint8Array, Uint8ClampedArray, - globalThis: { - Float16Array, - }, } = primordials; +const { + Float16Array, +} = require('internal/primordials_staging'); const { Buffer } = require('buffer'); const { diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 92bf3be1f612ff..a47ee00a1dddc1 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -117,6 +117,7 @@ expected.beforePreExec = new Set([ 'NativeModule internal/net', 'NativeModule internal/dns/utils', 'NativeModule internal/modules/esm/get_format', + 'NativeModule internal/primordials_staging', ]); expected.atRunTime = new Set([ diff --git a/test/parallel/test-internal-primordials-staging.js b/test/parallel/test-internal-primordials-staging.js new file mode 100644 index 00000000000000..d7d4c007e6c55b --- /dev/null +++ b/test/parallel/test-internal-primordials-staging.js @@ -0,0 +1,102 @@ +// Flags: --expose-internals --harmony-shadow-realm +'use strict'; +require('../common'); +const assert = require('assert'); + +// Test internal/primordials_staging module +// Mutating globals must not affect builtins stored there. + +const throwify = (name) => new Proxy({}, { + get(_, prop) { + assert.fail(`Tried to get ${prop} on mutated ${name}`); + }, + set(_, prop) { + assert.fail(`Tried to set ${prop} on mutated ${name}`); + }, + apply() { + assert.fail(`Tried to apply mutated ${name}`); + }, + construct() { + assert.fail(`Tried to construct mutated ${name}`); + }, +}); + +const { primordials } = require('internal/test/binding'); +const primordialsStaging = require('internal/primordials_staging'); +const originalGlobals = {}; +const notGlobals = new Set([ + 'Uint8ArrayFromBase64', + 'Uint8ArrayFromHex', +]); + +for (const [ name, primordialStaging ] of Object.entries(primordialsStaging)) { + // Sometimes conditional primordial might exist depending on environment + // If they always exist, internals should get them from primordials, and they should be removed from staging + if (name in primordials) { + console.log(`${name} is already in primordials. Consider removing it from 'internal/primordials_staging'`); + } + + // Do not treat missing builtins as error, because they might be disabled in this build + if (primordialStaging === undefined) { + console.log(`${name} is expected to exist. If it's new and not intentinally disabled, the test requires runtime flag.`); + continue; + } + + if (!notGlobals.has(name)) { + originalGlobals[name] = globalThis[name]; + globalThis[name] = throwify(name); + assert.strictEqual(originalGlobals[name], primordialStaging); + assert.notStrictEqual(originalGlobals[name], primordials.globalThis[name]); + } +} + +if (primordialsStaging.Temporal !== undefined || globalThis.Temporal !== undefined) { + // Safe Temporal must work + { + const { Temporal: { Instant, Now } } = primordialsStaging; + assert.ok(Now.instant() instanceof Instant); + assert.strictEqual(new Instant(123456789n).epochMilliseconds, 123); + } + + // Global Temporal must break + { + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + const { Temporal: { Instant, Now } } = globalThis; + }, { + code: 'ERR_ASSERTION', + }); + assert.throws(() => { + // eslint-disable-next-line no-unused-expressions + Temporal.Now.instant() instanceof Temporal.Instant; + }, { + code: 'ERR_ASSERTION', + }); + assert.throws(() => { + // eslint-disable-next-line no-unused-expressions + new Temporal.Instant(123456789n).epochMilliseconds; + }, { + code: 'ERR_ASSERTION', + }); + } +} + +if (primordialsStaging.Float16Array !== undefined || globalThis.Float16Array !== undefined) { + // Safe Float16Array must work + { + const { Float16Array } = primordialsStaging; + const buffer = new Float16Array([ 1, 2, 3 ]); + assert.ok(buffer instanceof Float16Array); + assert.strictEqual(buffer.byteLength, 6); + } + + // Global Float16Array must not work + { + const { Float16Array } = globalThis; + assert.throws(() => { + new Float16Array([ 1, 2, 3 ]); + }, { + name: 'TypeError', + }); + } +} From 731947029d301e4c6b47eea3743f17a4d6ce6f01 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Thu, 21 May 2026 19:37:59 +0800 Subject: [PATCH 2/4] squash: add `TemporalInstant` --- doc/contributing/primordials.md | 6 ++++++ lib/internal/fs/utils.js | 4 ++-- lib/internal/primordials_staging.js | 5 +++++ test/parallel/test-internal-primordials-staging.js | 12 +++++------- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/doc/contributing/primordials.md b/doc/contributing/primordials.md index d0870dfcf5eb34..e176e24dc9ed24 100644 --- a/doc/contributing/primordials.md +++ b/doc/contributing/primordials.md @@ -122,6 +122,12 @@ it unaffected by userland. Whenever a conditional feature graduates, it should be added to regular primordials and removed from staging primordials. +Staging primordials do not automatically adopt every new global object, and do not +replicate nested objects recursively. For example, no internal code requires +`TemporalPlainMonthDay*`, so there's no need to create primordials for it. If +you're adding experimental feature that requires new staging primordial, add it +to the internal module. + ### Lazy-loaded staging primordials Some internal modules are used in early bootstrap, and staging primordials module diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index ed346a44c834bf..b1e1e1d575ad65 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -441,7 +441,7 @@ function nsFromTimeSpecBigInt(sec, nsec) { let TemporalInstant; function instantFromNs(nsec) { - TemporalInstant ??= primordialsStaging.Temporal?.Instant; + TemporalInstant ??= primordialsStaging.TemporalInstant; if (TemporalInstant === undefined) { throw new ERR_NO_TEMPORAL(); } @@ -449,7 +449,7 @@ function instantFromNs(nsec) { } function instantFromTimeSpecMs(msec, nsec) { - TemporalInstant ??= primordialsStaging.Temporal?.Instant; + TemporalInstant ??= primordialsStaging.TemporalInstant; if (TemporalInstant === undefined) { throw new ERR_NO_TEMPORAL(); } diff --git a/lib/internal/primordials_staging.js b/lib/internal/primordials_staging.js index 885ab94ed41bcd..cf9003d945bc42 100644 --- a/lib/internal/primordials_staging.js +++ b/lib/internal/primordials_staging.js @@ -18,6 +18,7 @@ let _Iterator; let _ShadowRealm; let _SharedArrayBuffer; let _Temporal; +let _TemporalInstant; let _Uint8ArrayFromHex; let _Uint8ArrayFromBase64; let _WebAssembly; @@ -47,6 +48,9 @@ module.exports = { get Temporal() { return _Temporal; }, + get TemporalInstant() { + return _TemporalInstant; + }, get Uint8ArrayFromHex() { return _Uint8ArrayFromHex; }, @@ -79,6 +83,7 @@ module.exports = { _ShadowRealm = ShadowRealm; _SharedArrayBuffer = SharedArrayBuffer; _Temporal = Temporal; + _TemporalInstant = Temporal?.Instant; _Uint8ArrayFromBase64 = fromBase64; _Uint8ArrayFromHex = fromHex; _WebAssembly = WebAssembly; diff --git a/test/parallel/test-internal-primordials-staging.js b/test/parallel/test-internal-primordials-staging.js index d7d4c007e6c55b..f747ad07eee116 100644 --- a/test/parallel/test-internal-primordials-staging.js +++ b/test/parallel/test-internal-primordials-staging.js @@ -24,10 +24,6 @@ const throwify = (name) => new Proxy({}, { const { primordials } = require('internal/test/binding'); const primordialsStaging = require('internal/primordials_staging'); const originalGlobals = {}; -const notGlobals = new Set([ - 'Uint8ArrayFromBase64', - 'Uint8ArrayFromHex', -]); for (const [ name, primordialStaging ] of Object.entries(primordialsStaging)) { // Sometimes conditional primordial might exist depending on environment @@ -42,7 +38,8 @@ for (const [ name, primordialStaging ] of Object.entries(primordialsStaging)) { continue; } - if (!notGlobals.has(name)) { + // Test that top-level global objects mutation doesn't affect us + if (globalThis[name] !== undefined) { originalGlobals[name] = globalThis[name]; globalThis[name] = throwify(name); assert.strictEqual(originalGlobals[name], primordialStaging); @@ -53,7 +50,8 @@ for (const [ name, primordialStaging ] of Object.entries(primordialsStaging)) { if (primordialsStaging.Temporal !== undefined || globalThis.Temporal !== undefined) { // Safe Temporal must work { - const { Temporal: { Instant, Now } } = primordialsStaging; + const { Temporal: { Instant, Now }, TemporalInstant } = primordialsStaging; + assert.strictEqual(TemporalInstant, Instant); assert.ok(Now.instant() instanceof Instant); assert.strictEqual(new Instant(123456789n).epochMilliseconds, 123); } @@ -62,7 +60,7 @@ if (primordialsStaging.Temporal !== undefined || globalThis.Temporal !== undefin { assert.throws(() => { // eslint-disable-next-line no-unused-vars - const { Temporal: { Instant, Now } } = globalThis; + const { Temporal: { Instant, Now }, TemporalInstant } = globalThis; }, { code: 'ERR_ASSERTION', }); From 204c3c63fc9f9d3c6a04401b0086945903fc53a8 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Thu, 21 May 2026 20:01:37 +0800 Subject: [PATCH 3/4] squash: add `WebAssembly` nested objects to staging primordials --- lib/internal/modules/esm/translators.js | 16 ++++++++-------- lib/internal/primordials_staging.js | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index f7a0215fccf672..06d3de03644615 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -554,14 +554,14 @@ const wasmInstances = new SafeWeakMap(); translators.set('wasm', function(url, translateContext) { const { source } = translateContext; // WebAssembly global is not available during snapshot building, so we need to get it lazily. - const { WebAssembly } = primordialsStaging; + const { WebAssemblyInstance, WebAssemblyLinkError, WebAssemblyModule } = primordialsStaging; assertBufferSource(source, false, 'load'); debug(`Translating WASMModule ${url}`, translateContext); let compiled; try { - compiled = new WebAssembly.Module(source, { + compiled = new WebAssemblyModule(source, { builtins: ['js-string'], importedStringConstants: 'wasm:js/string-constants', }); @@ -572,28 +572,28 @@ translators.set('wasm', function(url, translateContext) { const importsList = new SafeSet(); const wasmGlobalImports = []; - for (const impt of WebAssembly.Module.imports(compiled)) { + for (const impt of WebAssemblyModule.imports(compiled)) { if (impt.kind === 'global') { ArrayPrototypePush(wasmGlobalImports, impt); } // Prefix reservations per https://webassembly.github.io/esm-integration/js-api/index.html#parse-a-webassembly-module. if (impt.module.startsWith('wasm-js:')) { - throw new WebAssembly.LinkError(`Invalid Wasm import "${impt.module}" in ${url}`); + throw new WebAssemblyLinkError(`Invalid Wasm import "${impt.module}" in ${url}`); } if (impt.name.startsWith('wasm:') || impt.name.startsWith('wasm-js:')) { - throw new WebAssembly.LinkError(`Invalid Wasm import name "${impt.module}" in ${url}`); + throw new WebAssemblyLinkError(`Invalid Wasm import name "${impt.module}" in ${url}`); } importsList.add(impt.module); } const exportsList = new SafeSet(); const wasmGlobalExports = new SafeSet(); - for (const expt of WebAssembly.Module.exports(compiled)) { + for (const expt of WebAssemblyModule.exports(compiled)) { if (expt.kind === 'global') { wasmGlobalExports.add(expt.name); } if (expt.name.startsWith('wasm:') || expt.name.startsWith('wasm-js:')) { - throw new WebAssembly.LinkError(`Invalid Wasm export name "${expt.name}" in ${url}`); + throw new WebAssemblyLinkError(`Invalid Wasm export name "${expt.name}" in ${url}`); } exportsList.add(expt.name); } @@ -620,7 +620,7 @@ translators.set('wasm', function(url, translateContext) { } // In cycles importing unexecuted Wasm, wasmInstance will be undefined, which will fail during // instantiation, since all bindings will be in the Temporal Deadzone (TDZ). - const { exports } = new WebAssembly.Instance(compiled, reflect.imports); + const { exports } = new WebAssemblyInstance(compiled, reflect.imports); wasmInstances.set(module.getNamespace(), exports); for (const expt of exportsList) { let val = exports[expt]; diff --git a/lib/internal/primordials_staging.js b/lib/internal/primordials_staging.js index cf9003d945bc42..bb28592df0221f 100644 --- a/lib/internal/primordials_staging.js +++ b/lib/internal/primordials_staging.js @@ -22,6 +22,9 @@ let _TemporalInstant; let _Uint8ArrayFromHex; let _Uint8ArrayFromBase64; let _WebAssembly; +let _WebAssemblyInstance; +let _WebAssemblyLinkError; +let _WebAssemblyModule; module.exports = { get AsyncDisposableStack() { @@ -60,6 +63,15 @@ module.exports = { get WebAssembly() { return _WebAssembly; }, + get WebAssemblyInstance() { + return _WebAssemblyInstance; + }, + get WebAssemblyLinkError() { + return _WebAssemblyLinkError; + }, + get WebAssemblyModule() { + return _WebAssemblyModule; + }, _init({ AsyncDisposableStack, DisposableStack, @@ -87,6 +99,9 @@ module.exports = { _Uint8ArrayFromBase64 = fromBase64; _Uint8ArrayFromHex = fromHex; _WebAssembly = WebAssembly; + _WebAssemblyInstance = WebAssembly?.Instance; + _WebAssemblyLinkError = WebAssembly?.LinkError; + _WebAssemblyModule = WebAssembly?.Module; delete this._init; }, From a56b8278bcd665972734f082136bd17ae1649b91 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Thu, 21 May 2026 20:08:48 +0800 Subject: [PATCH 4/4] squash: add `WebAssemblyLinkError` to linter's known errors --- lib/eslint.config_partial.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index f55f353475d237..d239b91c551f0b 100644 --- a/lib/eslint.config_partial.mjs +++ b/lib/eslint.config_partial.mjs @@ -23,7 +23,7 @@ const noRestrictedSyntax = [ message: "`btoa` supports only latin-1 charset, use Buffer.from(str).toString('base64') instead", }, { - selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError|QuotaExceededError)$/])', + selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError|QuotaExceededError|WebAssemblyLinkError)$/])', message: "Use an error exported by 'internal/errors' instead.", }, {