From 2e69def3b84b388cb362c19e5517d520dd751aaa Mon Sep 17 00:00:00 2001 From: Clemens Backes Date: Tue, 17 Mar 2026 18:10:05 +0100 Subject: [PATCH] [js-api] Handle thenable Module in instantiate(bytes) Add an explicit check in "instantiate a promise of a module" to ensure the fulfilled value of the compilation promise implements the Module interface. This prevents a modified WebAssembly.Module.prototype.then from subverting the instantiation process with a non-Module value. Also add regression tests in test/js-api/constructor/instantiate.any.js. Fixes https://github.com/WebAssembly/spec/issues/2034 --- document/js-api/index.bs | 1 + test/js-api/constructor/instantiate.any.js | 32 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 3f5803815e..a203f5c0f6 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -641,6 +641,7 @@ The verification of WebAssembly type requirements is deferred to the 1. Let |promise| be [=a new promise=]. 1. [=React=] to |promiseOfModule|: * If |promiseOfModule| was fulfilled with value |module|: + 1. If |module| does not [=implement=] {{Module}}, [=Reject=] |promise| with a {{TypeError}} exception and terminate these substeps. 1. [=asynchronously instantiate a WebAssembly module|Instantiate the WebAssembly module=] |module| importing |importObject|, and let |innerPromise| be the result. 1. [=React=] to |innerPromise|: * If |innerPromise| was fulfilled with value |instance|. diff --git a/test/js-api/constructor/instantiate.any.js b/test/js-api/constructor/instantiate.any.js index 8152f3a56f..12ba97b57d 100644 --- a/test/js-api/constructor/instantiate.any.js +++ b/test/js-api/constructor/instantiate.any.js @@ -150,3 +150,35 @@ promise_test(() => { buffer[0] = 1; return promise.then(assert_WebAssemblyInstantiatedSource); }, "Changing the buffer"); + +promise_test(t => { + const originalThen = WebAssembly.Module.prototype.then; + const otherModule = new WebAssembly.Module(emptyModuleBinary); + t.add_cleanup(() => { + WebAssembly.Module.prototype.then = originalThen; + }); + let thenCalledCount = 0; + WebAssembly.Module.prototype.then = function(resolve) { + thenCalledCount++; + WebAssembly.Module.prototype.then = originalThen; + resolve(otherModule); + }; + return WebAssembly.instantiate(emptyModuleBinary).then(result => { + assert_equals(thenCalledCount, 1, "then was called exactly once"); + assert_WebAssemblyInstantiatedSource(result); + assert_equals(result.module, otherModule, "The module from 'then' was used"); + }); +}, "instantiate(bytes) with a thenable WebAssembly.Module returning a different Module"); + +promise_test(t => { + const originalThen = WebAssembly.Module.prototype.then; + t.add_cleanup(() => { + if (originalThen === undefined) { + delete WebAssembly.Module.prototype.then; + } else { + WebAssembly.Module.prototype.then = originalThen; + } + }); + WebAssembly.Module.prototype.then = (resolve) => resolve(17); + return promise_rejects_js(t, TypeError, WebAssembly.instantiate(emptyModuleBinary)); +}, "instantiate(bytes) with a thenable WebAssembly.Module returning a non-Module value");