From 21507ec8a1ca66c6583319f8fab92807433d9eef Mon Sep 17 00:00:00 2001 From: chicoxyzzy Date: Mon, 5 Oct 2026 12:06:51 +0200 Subject: [PATCH 1/2] [js-api] Allow a missing importObject for builtin imports Fixes #2102. read the imports threw TypeError whenever module_imports was non-empty and importObject was missing. A module whose imports are all satisfied by an enabled builtin, or by the imported string constants module, does not read importObject. Throw only if importObject is missing and some import is not supplied by those sources. Do that before allocating builtin instances. --- document/js-api/index.bs | 11 +++++- test/js-api/js-string/imports.any.js | 53 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 64dfd22f77..6cd2b442db 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -486,7 +486,14 @@ To instantiate imported strings with module |module| and |importedStr
To read the imports from a WebAssembly module |module| from imports object |importObject|, enabled builtins |builtinSetNames|, and |importedStringModule|, perform the following steps: - 1. If [=module_imports=](|module|) [=list/is empty|is not empty=], and |importObject| is missing, throw a {{TypeError}} exception. + 1. If |importObject| is missing, + 1. [=list/iterate|For each=] |import| of [=module_imports=](|module|), + 1. Let |moduleName| be |import|[0]. + 1. If |importedStringModule| is not null and |moduleName| equals |importedStringModule|, + 1. [=iteration/continue=]. + 1. If [=find a builtin|finding a builtin=] for |import| and |builtinSetNames| is not null, + 1. [=iteration/continue=]. + 1. Throw a {{TypeError}} exception. 1. Let |builtinOrStringImports| be the ordered map «[ ]». 1. [=list/iterate|For each=] |builtinSetName| of |builtinSetNames|, 1. Let |builtinSetQualifiedName| be |builtinSetName| prefixed with "wasm:" @@ -551,6 +558,8 @@ To instantiate imported strings with module |module| and |importedStr Note: This algorithm only verifies the right kind of JavaScript values are passed. The verification of WebAssembly type requirements is deferred to the "[=instantiate the core of a WebAssembly module=]" algorithm. + +Note: |importObject| is required only for an import that is not supplied by an enabled builtin or by the imported string constants module.
diff --git a/test/js-api/js-string/imports.any.js b/test/js-api/js-string/imports.any.js index c357760bef..267945bd99 100644 --- a/test/js-api/js-string/imports.any.js +++ b/test/js-api/js-string/imports.any.js @@ -24,3 +24,56 @@ test(() => { // reflection. assert_equals(imports.length, 0); }); + +function moduleWithBuiltin() { + const builder = new WasmModuleBuilder(); + builder.addImport( + "wasm:js-string", + "test", + {params: [kWasmExternRef], results: [kWasmI32]}); + return new WebAssembly.Module(builder.toBuffer(), {builtins: ["js-string"]}); +} + +test(() => { + const instance = new WebAssembly.Instance(moduleWithBuiltin()); + assert_equals(typeof instance.exports, "object"); +}, "A builtin-only module can be instantiated without an importObject"); + +test(() => { + const instance = new WebAssembly.Instance(moduleWithBuiltin(), undefined); + assert_equals(typeof instance.exports, "object"); +}, "A builtin-only module can be instantiated with undefined importObject"); + +promise_test(async () => { + const instance = await WebAssembly.instantiate(moduleWithBuiltin()); + assert_equals(typeof instance.exports, "object"); +}, "WebAssembly.instantiate accepts a builtin-only module without an importObject"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("strings", "hi", kWasmExternRef, false); + builder.addExportOfKind("hi", kExternalGlobal, 0); + const module = new WebAssembly.Module(builder.toBuffer(), { + importedStringConstants: "strings", + }); + const instance = new WebAssembly.Instance(module); + assert_equals(instance.exports.hi.value, "hi"); +}, "Imported string constants do not require an importObject"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder.addImport( + "wasm:js-string", + "test", + {params: [kWasmExternRef], results: [kWasmI32]}); + builder.addImport("env", "f", {params: [], results: []}); + const module = new WebAssembly.Module(builder.toBuffer(), {builtins: ["js-string"]}); + assert_throws_js(TypeError, () => new WebAssembly.Instance(module)); +}, "A non-builtin import still requires an importObject"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder.addImport("wasm:js-string", "not-a-builtin", {params: [], results: []}); + const module = new WebAssembly.Module(builder.toBuffer(), {builtins: ["js-string"]}); + assert_throws_js(TypeError, () => new WebAssembly.Instance(module)); +}, "An unknown import from a builtin module requires an importObject"); From 598c35097e47ce4f47bb66c504e708821e73d4c3 Mon Sep 17 00:00:00 2001 From: chicoxyzzy Date: Mon, 5 Oct 2026 23:23:51 +0200 Subject: [PATCH 2/2] [js-api] Assert importObject is present before Get Get requires an Object. importObject can be missing when every import is an enabled builtin or an imported string constant, but those names are own properties of builtinOrStringImports, so the fallback Gets are not reached. Assert that before each fallback Get, and note it with the other notes on this algorithm. --- document/js-api/index.bs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 6cd2b442db..6ee5103b46 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -509,8 +509,10 @@ To instantiate imported strings with module |module| and |importedStr 1. If |builtinOrStringImports|[|moduleName|] [=map/exists=], 1. Let |o| be |builtinOrStringImports|[|moduleName|]. 1. If |o| [=is not an Object=] or if [=?=] [$HasProperty$](|o|, |componentName|) is false, + 1. Assert: |importObject| is not missing. 1. Set |o| to [=?=] [$Get$](|importObject|, |moduleName|). 1. Else, + 1. Assert: |importObject| is not missing. 1. Let |o| be [=?=] [$Get$](|importObject|, |moduleName|). 1. If |o| [=is not an Object=], throw a {{TypeError}} exception. 1. Let |v| be [=?=] [$Get$](|o|, |componentName|). @@ -559,7 +561,12 @@ Note: This algorithm only verifies the right kind of JavaScript values are passe The verification of WebAssembly type requirements is deferred to the "[=instantiate the core of a WebAssembly module=]" algorithm. -Note: |importObject| is required only for an import that is not supplied by an enabled builtin or by the imported string constants module. +Note: |importObject| is required only for an import that is not supplied by an +enabled builtin or by the imported string constants module. If it is missing, +the opening check throws unless every import is supplied that way. Each such +import is then an own property of the object in |builtinOrStringImports| for +its module name, so the [$Get$](|importObject|, |moduleName|) steps are not +reached.