From 8bca94b357e932cb6ff77d4808b2e51e62767f4b Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 19 Sep 2026 21:18:37 -0700 Subject: [PATCH 1/2] [js-api] Skip string constant imports in ESM integration The ESM integration sets the imported string module to "wasm:js/string-constants" but never checked it when computing requested modules or linking imports, so a string constant import would have been resolved as a module specifier. Add an [[ImportedStringModule]] check alongside the existing builtin check in parse and ExecuteModule. Adds ESM and source phase tests for string constants imported from wasm:js/string-constants. --- document/js-api/index.bs | 2 ++ .../resources/js-string-constants.wasm | Bin 0 -> 289 bytes ...ce-phase-string-constants.tentative.any.js | 23 ++++++++++++++++++ .../string-constants.tentative.any.js | 11 +++++++++ 4 files changed, 36 insertions(+) create mode 100644 test/js-api/esm-integration/resources/js-string-constants.wasm create mode 100644 test/js-api/esm-integration/source-phase-string-constants.tentative.any.js create mode 100644 test/js-api/esm-integration/string-constants.tentative.any.js diff --git a/document/js-api/index.bs b/document/js-api/index.bs index fd09453d4..aa5594647 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -2321,6 +2321,7 @@ To parse a WebAssembly module given a byte sequence |by 1. Throw a {{LinkError}} exception. 1. Note: The following step only applies when integrating with the JS String Builtins proposal. 1. If [=Find a builtin=] with (|moduleName|, |name|, |type|) and builtins |module|.\[[BuiltinSets]] is not null, then [=iteration/continue=]. + 1. If |moduleName| equals |module|.\[[ImportedStringModule]], then [=iteration/continue=]. 1. [=set/Append=] |moduleName| to |requestedModules|. 1. For each (|name|, type) in [=module_exports=](|module|.\[[Module]]) 1. If |name| starts with the prefix "wasm:" or "wasm-js:", @@ -2411,6 +2412,7 @@ WebAssembly Module Records have the following methods: 1. Let |imports| be « ». 1. [=list/iterate|For each=] (|importedModuleName|, |name|, |importtype|) in [=module_imports=](|module|), 1. If [=Find a builtin=] with (|importedModuleName|, |name|) and builtins |module|.\[[BuiltinSets]] is not null, then [=iteration/continue=]. + 1. If |importedModuleName| equals |record|.\[[ModuleSource]].\[[ImportedStringModule]], then [=iteration/continue=]. 1. Let |importedModule| be [$GetImportedModule$](|record|, |importedModuleName|). 1. Let |resolution| be |importedModule|.ResolveExport(|name|). 1. Assert: |resolution| is a [=ResolvedBinding Record=], as validated during environment initialization. diff --git a/test/js-api/esm-integration/resources/js-string-constants.wasm b/test/js-api/esm-integration/resources/js-string-constants.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9139123ce4dd35ee87b3ddb4b33592b3ecf0418b GIT binary patch literal 289 zcmaKlJr2S!427SQlp-psPQcDwb%z;=fjiI&Mg38mAQB06D#u{siX4GdDI*NFY`^FC zp)ef*pqS^RX~;p^)xdhOxjSvwn9SH)t;+PGm9d4g23+H(7*w)t^*@Jrzn%|gh^PG2 z=g(THN?QRu5JbeneK0SjJ#<&wgU0bVCWHB_ZffxOdp-GIG0|zU76MyDuF=?7G;>pe c2$@htCn+H;J@@Rn;ff=WBaD0qbDM*F0BTrKB>(^b literal 0 HcmV?d00001 diff --git a/test/js-api/esm-integration/source-phase-string-constants.tentative.any.js b/test/js-api/esm-integration/source-phase-string-constants.tentative.any.js new file mode 100644 index 000000000..2a434af7a --- /dev/null +++ b/test/js-api/esm-integration/source-phase-string-constants.tentative.any.js @@ -0,0 +1,23 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmModuleSource = await import.source("./resources/js-string-constants.wasm"); + + assert_true(wasmModuleSource instanceof WebAssembly.Module); + + const instance = new WebAssembly.Instance(wasmModuleSource, {}); + + assert_equals(instance.exports.getEmpty(), ""); + assert_equals(instance.exports.getHello(), "hello"); + assert_equals(instance.exports.getEmoji(), "\u{1F600}"); + assert_equals(instance.exports.getHelloLength(), 5); + assert_equals(instance.exports.hello.value, "hello"); +}, "String constants should be supported in source phase imports"); + +promise_test(async () => { + const wasmModuleSource = await import.source("./resources/js-string-constants.wasm"); + + const imports = WebAssembly.Module.imports(wasmModuleSource); + + assert_equals(imports.length, 0); +}, "Source phase import should not reflect string constant imports"); diff --git a/test/js-api/esm-integration/string-constants.tentative.any.js b/test/js-api/esm-integration/string-constants.tentative.any.js new file mode 100644 index 000000000..8a652ae90 --- /dev/null +++ b/test/js-api/esm-integration/string-constants.tentative.any.js @@ -0,0 +1,11 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +promise_test(async () => { + const wasmModule = await import("./resources/js-string-constants.wasm"); + + assert_equals(wasmModule.getEmpty(), ""); + assert_equals(wasmModule.getHello(), "hello"); + assert_equals(wasmModule.getEmoji(), "\u{1F600}"); + assert_equals(wasmModule.getHelloLength(), 5); + assert_equals(wasmModule.hello, "hello"); +}, "String constants should be imported from wasm:js/string-constants in ESM integration"); From 9a36459e4b930670db9bae86c3657dea80f4d830 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 21 Sep 2026 09:13:06 -0700 Subject: [PATCH 2/2] [js-api] Factor out external imports for ESM integration --- document/js-api/index.bs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/document/js-api/index.bs b/document/js-api/index.bs index aa5594647..efb570a90 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -768,7 +768,7 @@ In addition, the interface object for {{Module}} must have as its \[[Prototype]]
- The imports(|moduleObject|) method, when invoked, performs the following steps: + To get the external imports of a {{Module}} |moduleObject|, perform the following steps: 1. Let |module| be |moduleObject|.\[[Module]]. 1. Let |builtinSetNames| be |moduleObject|.\[[BuiltinSets]]. 1. Let |importedStringModule| be |moduleObject|.\[[ImportedStringModule]]. @@ -776,6 +776,14 @@ In addition, the interface object for {{Module}} must have as its \[[Prototype]] 1. [=list/iterate|For each=] (|moduleName|, |name|, |type|) of [=module_imports=](|module|), 1. If [=find a builtin=] for (|moduleName|, |name|, |type|) and |builtinSetNames| is not null, then [=iteration/continue=]. 1. If |importedStringModule| is not null and |moduleName| equals |importedStringModule|, then [=iteration/continue=]. + 1. [=list/Append=] (|moduleName|, |name|, |type|) to |imports|. + 1. Return |imports|. +
+ +
+ The imports(|moduleObject|) method, when invoked, performs the following steps: + 1. Let |imports| be « ». + 1. [=list/iterate|For each=] (|moduleName|, |name|, |type|) of the [=external imports=] of |moduleObject|, 1. Let |kind| be the [=string value of the extern type=] |type|. 1. Let |obj| be «[ "{{ModuleImportDescriptor/module}}" → |moduleName|, "{{ModuleImportDescriptor/name}}" → |name|, "{{ModuleImportDescriptor/kind}}" → |kind| ]». 1. [=list/Append=] |obj| to |imports|. @@ -2314,14 +2322,11 @@ To parse a WebAssembly module given a byte sequence |by 1. Let |importedStringModule| be "wasm:js/string-constants". 1. [=Construct a WebAssembly module object=] from |module|, |bytes|, |builtinSetNames| and |importedStringModule|, and let |module| be the result. 1. Let |requestedModules| be a set. -1. For each (|moduleName|, |name|, |type|) in [=module_imports=](|module|.\[[Module]]), +1. For each (|moduleName|, |name|, type) of the [=external imports=] of |module|, 1. If |moduleName| starts with the prefix "wasm-js:", 1. Throw a {{LinkError}} exception. 1. If |name| starts with the prefix "wasm:" or "wasm-js:", 1. Throw a {{LinkError}} exception. - 1. Note: The following step only applies when integrating with the JS String Builtins proposal. - 1. If [=Find a builtin=] with (|moduleName|, |name|, |type|) and builtins |module|.\[[BuiltinSets]] is not null, then [=iteration/continue=]. - 1. If |moduleName| equals |module|.\[[ImportedStringModule]], then [=iteration/continue=]. 1. [=set/Append=] |moduleName| to |requestedModules|. 1. For each (|name|, type) in [=module_exports=](|module|.\[[Module]]) 1. If |name| starts with the prefix "wasm:" or "wasm-js:", @@ -2410,9 +2415,7 @@ WebAssembly Module Records have the following methods: 1. Let |record| be this WebAssembly Module Record. 1. Let |module| be |record|.\[[ModuleSource]].\[[Module]]. 1. Let |imports| be « ». -1. [=list/iterate|For each=] (|importedModuleName|, |name|, |importtype|) in [=module_imports=](|module|), - 1. If [=Find a builtin=] with (|importedModuleName|, |name|) and builtins |module|.\[[BuiltinSets]] is not null, then [=iteration/continue=]. - 1. If |importedModuleName| equals |record|.\[[ModuleSource]].\[[ImportedStringModule]], then [=iteration/continue=]. +1. [=list/iterate|For each=] (|importedModuleName|, |name|, |importtype|) of the [=external imports=] of |record|.\[[ModuleSource]], 1. Let |importedModule| be [$GetImportedModule$](|record|, |importedModuleName|). 1. Let |resolution| be |importedModule|.ResolveExport(|name|). 1. Assert: |resolution| is a [=ResolvedBinding Record=], as validated during environment initialization. @@ -2494,7 +2497,7 @@ WebAssembly Module Record Environment Record. 1. If |specifier|.\[[Module]].\[[ModuleRecord]] is null, 1. Let |realm| be the current agent's realm record. 1. Let |requestedModules| be a set. - 1. For each (|moduleName|, name, type) in [=module_imports=](|specifier|.\[[Module]]), + 1. For each (|moduleName|, name, type) of the [=external imports=] of |specifier|, 1. [=set/Append=] |moduleName| to |requestedModules|. 1. Let |moduleRecord| be { @@ -2503,7 +2506,7 @@ WebAssembly Module Record Environment Record. \[[Realm]]: |realm|, \[[Environment]]: ~empty~, \[[Namespace]]: ~empty~, - \[[ModuleSource]]: |module|, + \[[ModuleSource]]: |specifier|, \[[HostDefined]]: ~empty~, \[[Status]]: "new", @@ -2519,7 +2522,7 @@ WebAssembly Module Record Environment Record. \[[AsyncParentModules]]: « », \[[PendingAsyncDependencies]]: ~empty~, }. - 1. Set |module|.\[[ModuleRecord]] to |moduleRecord|. + 1. Set |specifier|.\[[Module]].\[[ModuleRecord]] to |moduleRecord|. 1. Return |moduleRecord|. 1. Return |specifier|.\[[Module]].\[[ModuleRecord]]. 1. Return ~not-a-source~.