diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/wasm | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/wasm')
197 files changed, 9349 insertions, 0 deletions
diff --git a/testing/web-platform/tests/wasm/META.yml b/testing/web-platform/tests/wasm/META.yml new file mode 100644 index 0000000000..3ea02ee7c9 --- /dev/null +++ b/testing/web-platform/tests/wasm/META.yml @@ -0,0 +1,3 @@ +suggested_reviewers: + - titzer + - Ms2ger diff --git a/testing/web-platform/tests/wasm/compile_worker.js b/testing/web-platform/tests/wasm/compile_worker.js new file mode 100644 index 0000000000..9a28efb970 --- /dev/null +++ b/testing/web-platform/tests/wasm/compile_worker.js @@ -0,0 +1,11 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +this.importScripts("resources/load_wasm.js"); + +onmessage = function(msg) { + createWasmModule() + .then(postMessage) + +} diff --git a/testing/web-platform/tests/wasm/create_multiple_memory.worker.js b/testing/web-platform/tests/wasm/create_multiple_memory.worker.js new file mode 100644 index 0000000000..5bb1dcfc8a --- /dev/null +++ b/testing/web-platform/tests/wasm/create_multiple_memory.worker.js @@ -0,0 +1,13 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The WebAssembly spec doesn't specify a limit on how many Memory objects can +// be allocated. This test makes sure we can have at least two at once. + +importScripts("/resources/testharness.js"); +test(function () { + const mem1 = new WebAssembly.Memory({initial: 1}); + const mem2 = new WebAssembly.Memory({initial: 1}); +}, "WebAssembly#CreateMultipleMemories."); +done(); diff --git a/testing/web-platform/tests/wasm/incrementer.wasm b/testing/web-platform/tests/wasm/incrementer.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/incrementer.wasm diff --git a/testing/web-platform/tests/wasm/jsapi/META.yml b/testing/web-platform/tests/wasm/jsapi/META.yml new file mode 100644 index 0000000000..cf5525ae11 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/META.yml @@ -0,0 +1 @@ +spec: https://webassembly.github.io/spec/js-api/ diff --git a/testing/web-platform/tests/wasm/jsapi/assertions.js b/testing/web-platform/tests/wasm/jsapi/assertions.js new file mode 100644 index 0000000000..3b37019068 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/assertions.js @@ -0,0 +1,105 @@ +function assert_function_name(fn, name, description) { + const propdesc = Object.getOwnPropertyDescriptor(fn, "name"); + assert_equals(typeof propdesc, "object", `${description} should have name property`); + assert_false(propdesc.writable, "writable", `${description} name should not be writable`); + assert_false(propdesc.enumerable, "enumerable", `${description} name should not be enumerable`); + assert_true(propdesc.configurable, "configurable", `${description} name should be configurable`); + assert_equals(propdesc.value, name, `${description} name should be ${name}`); +} +globalThis.assert_function_name = assert_function_name; + +function assert_function_length(fn, length, description) { + const propdesc = Object.getOwnPropertyDescriptor(fn, "length"); + assert_equals(typeof propdesc, "object", `${description} should have length property`); + assert_false(propdesc.writable, "writable", `${description} length should not be writable`); + assert_false(propdesc.enumerable, "enumerable", `${description} length should not be enumerable`); + assert_true(propdesc.configurable, "configurable", `${description} length should be configurable`); + assert_equals(propdesc.value, length, `${description} length should be ${length}`); +} +globalThis.assert_function_length = assert_function_length; + +function assert_exported_function(fn, { name, length }, description) { + if (WebAssembly.Function === undefined) { + assert_equals(Object.getPrototypeOf(fn), Function.prototype, + `${description}: prototype`); + } else { + assert_equals(Object.getPrototypeOf(fn), WebAssembly.Function.prototype, + `${description}: prototype`); + } + + assert_function_name(fn, name, description); + assert_function_length(fn, length, description); +} +globalThis.assert_exported_function = assert_exported_function; + +function assert_Instance(instance, expected_exports) { + assert_equals(Object.getPrototypeOf(instance), WebAssembly.Instance.prototype, + "prototype"); + assert_true(Object.isExtensible(instance), "extensible"); + + assert_equals(instance.exports, instance.exports, "exports should be idempotent"); + const exports = instance.exports; + + assert_equals(Object.getPrototypeOf(exports), null, "exports prototype"); + assert_false(Object.isExtensible(exports), "extensible exports"); + assert_array_equals(Object.keys(exports), Object.keys(expected_exports), "matching export keys"); + for (const [key, expected] of Object.entries(expected_exports)) { + const property = Object.getOwnPropertyDescriptor(exports, key); + assert_equals(typeof property, "object", `${key} should be present`); + assert_false(property.writable, `${key}: writable`); + assert_true(property.enumerable, `${key}: enumerable`); + assert_false(property.configurable, `${key}: configurable`); + const actual = property.value; + assert_true(Object.isExtensible(actual), `${key}: extensible`); + + switch (expected.kind) { + case "function": + assert_exported_function(actual, expected, `value of ${key}`); + break; + case "global": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype, + `value of ${key}: prototype`); + assert_equals(actual.value, expected.value, `value of ${key}: value`); + assert_equals(actual.valueOf(), expected.value, `value of ${key}: valueOf()`); + break; + case "memory": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Memory.prototype, + `value of ${key}: prototype`); + assert_equals(Object.getPrototypeOf(actual.buffer), ArrayBuffer.prototype, + `value of ${key}: prototype of buffer`); + assert_equals(actual.buffer.byteLength, 0x10000 * expected.size, `value of ${key}: size of buffer`); + const array = new Uint8Array(actual.buffer); + assert_equals(array[0], 0, `value of ${key}: first element of buffer`); + assert_equals(array[array.byteLength - 1], 0, `value of ${key}: last element of buffer`); + break; + case "table": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype, + `value of ${key}: prototype`); + assert_equals(actual.length, expected.length, `value of ${key}: length of table`); + break; + } + } +} +globalThis.assert_Instance = assert_Instance; + +function assert_WebAssemblyInstantiatedSource(actual, expected_exports={}) { + assert_equals(Object.getPrototypeOf(actual), Object.prototype, + "Prototype"); + assert_true(Object.isExtensible(actual), "Extensibility"); + + const module = Object.getOwnPropertyDescriptor(actual, "module"); + assert_equals(typeof module, "object", "module: type of descriptor"); + assert_true(module.writable, "module: writable"); + assert_true(module.enumerable, "module: enumerable"); + assert_true(module.configurable, "module: configurable"); + assert_equals(Object.getPrototypeOf(module.value), WebAssembly.Module.prototype, + "module: prototype"); + + const instance = Object.getOwnPropertyDescriptor(actual, "instance"); + assert_equals(typeof instance, "object", "instance: type of descriptor"); + assert_true(instance.writable, "instance: writable"); + assert_true(instance.enumerable, "instance: enumerable"); + assert_true(instance.configurable, "instance: configurable"); + assert_Instance(instance.value, expected_exports); +} +globalThis.assert_WebAssemblyInstantiatedSource = assert_WebAssemblyInstantiatedSource; diff --git a/testing/web-platform/tests/wasm/jsapi/bad-imports.js b/testing/web-platform/tests/wasm/jsapi/bad-imports.js new file mode 100644 index 0000000000..c7c9c6b6cd --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/bad-imports.js @@ -0,0 +1,186 @@ +/** + * `t` should be a function that takes at least three arguments: + * + * - the name of the test; + * - the expected error (to be passed to `assert_throws_js`); + * - a function that takes a `WasmModuleBuilder` and initializes it; + * - (optionally) an options object. + * + * The function is expected to create a test that checks if instantiating a + * module with the result of the `WasmModuleBuilder` and the options object + * (if any) yields the correct error. + */ +function test_bad_imports(t) { + function value_type(type) { + switch (type) { + case "i32": return kWasmI32; + case "i64": return kWasmI64; + case "f32": return kWasmF32; + case "f64": return kWasmF64; + default: throw new TypeError(`Unexpected type ${type}`); + } + } + + for (const value of [null, true, "", Symbol(), 1, 0.1, NaN]) { + t(`Non-object imports argument: ${format_value(value)}`, + TypeError, + builder => {}, + value); + } + + for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN]) { + const imports = { + "module": value, + }; + t(`Non-object module: ${format_value(value)}`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + imports); + } + + t(`Missing imports argument`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }); + + for (const [value, name] of [[undefined, "undefined"], [{}, "empty object"], [{ "module\0": null }, "wrong property"]]) { + t(`Imports argument with missing property: ${name}`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + value); + } + + for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN, {}]) { + t(`Importing a function with an incorrectly-typed value: ${format_value(value)}`, + WebAssembly.LinkError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + { + "module": { + "fn": value, + }, + }); + } + + const nonGlobals = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [{}, "plain object"], + [WebAssembly.Global, "WebAssembly.Global"], + [WebAssembly.Global.prototype, "WebAssembly.Global.prototype"], + [Object.create(WebAssembly.Global.prototype), "Object.create(WebAssembly.Global.prototype)"], + ]; + + for (const type of ["i32", "i64", "f32", "f64"]) { + const extendedNonGlobals = nonGlobals.concat([ + type === "i64" ? [0, "Number"] : [0n, "BigInt"], + [new WebAssembly.Global({value: type === "f32" ? "f64" : "f32"}), "WebAssembly.Global object (wrong value type)"], + ]); + for (const [value, name = format_value(value)] of extendedNonGlobals) { + t(`Importing an ${type} global with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type)); + }, + { + "module": { + "global": value, + }, + }); + } + } + + for (const type of ["i32", "i64", "f32", "f64"]) { + const value = type === "i64" ? 0n : 0; + t(`Importing an ${type} mutable global with a primitive value`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type), true); + }, + { + "module": { + "global": value, + }, + }); + + const global = new WebAssembly.Global({ "value": type }, value); + t(`Importing an ${type} mutable global with an immutable Global object`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type), true); + }, + { + "module": { + "global": global, + }, + }); + } + + const nonMemories = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [1], + [0.1], + [NaN], + [{}, "plain object"], + [WebAssembly.Memory, "WebAssembly.Memory"], + [WebAssembly.Memory.prototype, "WebAssembly.Memory.prototype"], + [Object.create(WebAssembly.Memory.prototype), "Object.create(WebAssembly.Memory.prototype)"], + [new WebAssembly.Memory({"initial": 256}), "WebAssembly.Memory object (too large)"], + ]; + + for (const [value, name = format_value(value)] of nonMemories) { + t(`Importing memory with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedMemory("module", "memory", 0, 128); + }, + { + "module": { + "memory": value, + }, + }); + } + + const nonTables = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [1], + [0.1], + [NaN], + [{}, "plain object"], + [WebAssembly.Table, "WebAssembly.Table"], + [WebAssembly.Table.prototype, "WebAssembly.Table.prototype"], + [Object.create(WebAssembly.Table.prototype), "Object.create(WebAssembly.Table.prototype)"], + [new WebAssembly.Table({"element": "anyfunc", "initial": 256}), "WebAssembly.Table object (too large)"], + ]; + + for (const [value, name = format_value(value)] of nonTables) { + t(`Importing table with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedTable("module", "table", 0, 128); + }, + { + "module": { + "table": value, + }, + }); + } +} +globalThis.test_bad_imports = test_bad_imports; diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/compile.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/compile.any.js new file mode 100644 index 0000000000..f822aa30e6 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/compile.any.js @@ -0,0 +1,85 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_Module(module) { + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype, + "Prototype"); + assert_true(Object.isExtensible(module), "Extensibility"); +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly.compile()); +}, "Missing argument"); + +promise_test(t => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + return Promise.all(invalidArguments.map(argument => { + return promise_rejects_js(t, TypeError, WebAssembly.compile(argument), + `compile(${format_value(argument)})`); + })); +}, "Invalid arguments"); + +promise_test(() => { + const fn = WebAssembly.compile; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + return Promise.all(thisValues.map(thisValue => { + return fn.call(thisValue, emptyModuleBinary).then(assert_Module); + })); +}, "Branding"); + +test(() => { + const promise = WebAssembly.compile(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(promise), Promise.prototype, "prototype"); + assert_true(Object.isExtensible(promise), "extensibility"); +}, "Promise type"); + +promise_test(t => { + const buffer = new Uint8Array(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.compile(buffer)); +}, "Empty buffer"); + +promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.compile(buffer)); +}, "Invalid code"); + +promise_test(() => { + return WebAssembly.compile(emptyModuleBinary).then(assert_Module); +}, "Result type"); + +promise_test(() => { + return WebAssembly.compile(emptyModuleBinary, {}).then(assert_Module); +}, "Stray argument"); + +promise_test(() => { + const buffer = new WasmModuleBuilder().toBuffer(); + assert_equals(buffer[0], 0); + const promise = WebAssembly.compile(buffer); + buffer[0] = 1; + return promise.then(assert_Module); +}, "Changing the buffer"); diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/instantiate-bad-imports.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/instantiate-bad-imports.any.js new file mode 100644 index 0000000000..e4926c8d70 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/instantiate-bad-imports.any.js @@ -0,0 +1,22 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...arguments) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + return promise_rejects_js(t, error, WebAssembly.instantiate(module, ...arguments)); + }, `WebAssembly.instantiate(module): ${name}`); +}); + +test_bad_imports((name, error, build, ...arguments) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + return promise_rejects_js(t, error, WebAssembly.instantiate(buffer, ...arguments)); + }, `WebAssembly.instantiate(buffer): ${name}`); +}); diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/instantiate.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/instantiate.any.js new file mode 100644 index 0000000000..34e005c470 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/instantiate.any.js @@ -0,0 +1,152 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly.instantiate()); +}, "Missing arguments"); + +promise_test(() => { + const fn = WebAssembly.instantiate; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + return Promise.all(thisValues.map(thisValue => { + return fn.call(thisValue, emptyModuleBinary).then(assert_WebAssemblyInstantiatedSource); + })); +}, "Branding"); + +promise_test(t => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + return Promise.all(invalidArguments.map(argument => { + return promise_rejects_js(t, TypeError, WebAssembly.instantiate(argument), + `instantiate(${format_value(argument)})`); + })); +}, "Invalid arguments"); + +test(() => { + const promise = WebAssembly.instantiate(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(promise), Promise.prototype, "prototype"); + assert_true(Object.isExtensible(promise), "extensibility"); +}, "Promise type"); + +for (const [name, fn] of instanceTestFactory) { + promise_test(() => { + const { buffer, args, exports, verify } = fn(); + return WebAssembly.instantiate(buffer, ...args).then(result => { + assert_WebAssemblyInstantiatedSource(result, exports); + verify(result.instance); + }); + }, `${name}: BufferSource argument`); + + promise_test(() => { + const { buffer, args, exports, verify } = fn(); + const module = new WebAssembly.Module(buffer); + return WebAssembly.instantiate(module, ...args).then(instance => { + assert_Instance(instance, exports); + verify(instance); + }); + }, `${name}: Module argument`); +} + +promise_test(() => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiate(buffer, imports); + assert_array_equals(order, []); + return p.then(result => { + assert_WebAssemblyInstantiatedSource(result); + assert_array_equals(order, expected); + }); +}, "Synchronous options handling: Buffer argument"); + +promise_test(() => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiate(module, imports); + assert_array_equals(order, expected); + return p.then(instance => assert_Instance(instance, {})); +}, "Synchronous options handling: Module argument"); + +promise_test(t => { + const buffer = new Uint8Array(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.instantiate(buffer)); +}, "Empty buffer"); + +promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.instantiate(buffer)); +}, "Invalid code"); + +promise_test(() => { + const buffer = new WasmModuleBuilder().toBuffer(); + assert_equals(buffer[0], 0); + const promise = WebAssembly.instantiate(buffer); + buffer[0] = 1; + return promise.then(assert_WebAssemblyInstantiatedSource); +}, "Changing the buffer"); diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/multi-value.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/multi-value.any.js new file mode 100644 index 0000000000..8786f9b953 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/multi-value.any.js @@ -0,0 +1,149 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +const type_if_fi = makeSig([kWasmF64, kWasmI32], [kWasmI32, kWasmF64]); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("swap", type_if_fi) + .addBody([ + kExprLocalGet, 1, + kExprLocalGet, 0, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const result = await WebAssembly.instantiate(buffer); + const swapped = result.instance.exports.swap(4.2, 7); + assert_true(Array.isArray(swapped)); + assert_equals(Object.getPrototypeOf(swapped), Array.prototype); + assert_array_equals(swapped, [7, 4.2]); +}, "multiple return values from wasm to js"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + const swap = builder + .addFunction("swap", type_if_fi) + .addBody([ + kExprLocalGet, 1, + kExprLocalGet, 0, + kExprReturn, + ]); + builder + .addFunction("callswap", kSig_i_v) + .addBody([ + ...wasmF64Const(4.2), + ...wasmI32Const(7), + kExprCallFunction, swap.index, + kExprDrop, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const result = await WebAssembly.instantiate(buffer); + const swapped = result.instance.exports.callswap(); + assert_equals(swapped, 7); +}, "multiple return values inside wasm"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", type_if_fi); + builder + .addFunction("callfn", kSig_i_v) + .addBody([ + ...wasmF64Const(4.2), + ...wasmI32Const(7), + kExprCallFunction, fnIndex, + kExprDrop, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const actual = []; + const imports = { + "module": { + fn(f32, i32) { + assert_equals(f32, 4.2); + assert_equals(i32, 7); + const result = [2, 7.3]; + let i = 0; + return { + get [Symbol.iterator]() { + actual.push("@@iterator getter"); + return function iterator() { + actual.push("@@iterator call"); + return { + get next() { + actual.push("next getter"); + return function next(...args) { + assert_array_equals(args, []); + let j = ++i; + actual.push(`next call ${j}`); + if (j > result.length) { + return { + get done() { + actual.push(`done call ${j}`); + return true; + } + }; + } + return { + get done() { + actual.push(`done call ${j}`); + return false; + }, + get value() { + actual.push(`value call ${j}`); + return { + get valueOf() { + actual.push(`valueOf get ${j}`); + return function() { + actual.push(`valueOf call ${j}`); + return result[j - 1]; + }; + } + }; + } + }; + }; + } + }; + } + }, + }; + }, + } + }; + + const { instance } = await WebAssembly.instantiate(buffer, imports); + const result = instance.exports.callfn(); + assert_equals(result, 2); + assert_array_equals(actual, [ + "@@iterator getter", + "@@iterator call", + "next getter", + "next call 1", + "done call 1", + "value call 1", + "next call 2", + "done call 2", + "value call 2", + "next call 3", + "done call 3", + "valueOf get 1", + "valueOf call 1", + "valueOf get 2", + "valueOf call 2", + ]); +}, "multiple return values from js to wasm"); diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/toStringTag.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/toStringTag.any.js new file mode 100644 index 0000000000..5fae8304f8 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/toStringTag.any.js @@ -0,0 +1,42 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +"use strict"; +// https://webidl.spec.whatwg.org/#es-namespaces +// https://webassembly.github.io/spec/js-api/#namespacedef-webassembly + +test(() => { + assert_own_property(WebAssembly, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly", "value"); + assert_equals(propDesc.writable, false, "writable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.configurable, true, "configurable"); +}, "@@toStringTag exists on the namespace object with the appropriate descriptor"); + +test(() => { + assert_equals(WebAssembly.toString(), "[object WebAssembly]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object WebAssembly]"); +}, "Object.prototype.toString applied to the namespace object"); + +test(t => { + assert_own_property(WebAssembly, Symbol.toStringTag, "Precondition: @@toStringTag on the namespace object"); + t.add_cleanup(() => { + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "WebAssembly" }); + }); + + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "Test" }); + assert_equals(WebAssembly.toString(), "[object Test]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object Test]"); +}, "Object.prototype.toString applied after modifying the namespace object's @@toStringTag"); + +test(t => { + assert_own_property(WebAssembly, Symbol.toStringTag, "Precondition: @@toStringTag on the namespace object"); + t.add_cleanup(() => { + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "WebAssembly" }); + }); + + assert_true(delete WebAssembly[Symbol.toStringTag]); + assert_equals(WebAssembly.toString(), "[object Object]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object Object]"); +}, "Object.prototype.toString applied after deleting @@toStringTag"); diff --git a/testing/web-platform/tests/wasm/jsapi/constructor/validate.any.js b/testing/web-platform/tests/wasm/jsapi/constructor/validate.any.js new file mode 100644 index 0000000000..fce43d1e17 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/constructor/validate.any.js @@ -0,0 +1,99 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.validate()); +}, "Missing argument"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.validate(argument), + `validate(${format_value(argument)})`); + } +}, "Invalid arguments"); + +test(() => { + const fn = WebAssembly.validate; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + for (const thisValue of thisValues) { + assert_true(fn.call(thisValue, emptyModuleBinary), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +const modules = [ + // Incomplete header. + [[], false], + [[0x00], false], + [[0x00, 0x61], false], + [[0x00, 0x61, 0x73], false], + [[0x00, 0x61, 0x73, 0x6d], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00], false], + + // Complete header. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], true], + + // Invalid version. + [[0x00, 0x61, 0x73, 0x6d, 0x00, 0x00, 0x00, 0x00], false], + [[0x00, 0x61, 0x73, 0x6d, 0x02, 0x00, 0x00, 0x00], false], + + // Nameless custom section. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00], false], + + // Custom section with empty name. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00], true], + + // Custom section with name "a". + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x61], true], +]; +const bufferTypes = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, +]; +for (const [module, expected] of modules) { + const name = module.map(n => n.toString(16)).join(" "); + for (const bufferType of bufferTypes) { + if (module.length % bufferType.BYTES_PER_ELEMENT === 0) { + test(() => { + const bytes = new Uint8Array(module); + const moduleBuffer = new bufferType(bytes.buffer); + assert_equals(WebAssembly.validate(moduleBuffer), expected); + }, `Validating module [${name}] in ${bufferType.name}`); + } + } +} + +test(() => { + assert_true(WebAssembly.validate(emptyModuleBinary, {})); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js b/testing/web-platform/tests/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js new file mode 100644 index 0000000000..572db0c01b --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js @@ -0,0 +1,13 @@ +// META: global=jsshell + +test(() => { + assert_not_own_property(WebAssembly.CompileError.prototype, Symbol.toStringTag); +}, "WebAssembly.CompileError"); + +test(() => { + assert_not_own_property(WebAssembly.LinkError.prototype, Symbol.toStringTag); +}, "WebAssembly.LinkError"); + +test(() => { + assert_not_own_property(WebAssembly.RuntimeError.prototype, Symbol.toStringTag); +}, "WebAssembly.RuntimeError"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/basic.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/basic.tentative.any.js new file mode 100644 index 0000000000..cacce99d9c --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/basic.tentative.any.js @@ -0,0 +1,120 @@ +// META: global=window,worker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_throws_wasm(fn, message) { + try { + fn(); + assert_not_reached(`expected to throw with ${message}`); + } catch (e) { + assert_true(e instanceof WebAssembly.Exception, `Error should be a WebAssembly.Exception with ${message}`); + } +} + +promise_test(async () => { + const kSig_v_r = makeSig([kWasmExternRef], []); + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_r); + builder.addFunction("throw_param", kSig_v_r) + .addBody([ + kExprLocalGet, 0, + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + const values = [ + undefined, + null, + true, + false, + "test", + Symbol(), + 0, + 1, + 4.2, + NaN, + Infinity, + {}, + () => {}, + ]; + for (const v of values) { + assert_throws_wasm(() => instance.exports.throw_param(v), String(v)); + } +}, "Wasm function throws argument"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_a); + builder.addFunction("throw_null", kSig_v_v) + .addBody([ + kExprRefNull, kAnyFuncCode, + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_null()); +}, "Wasm function throws null"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_i); + builder.addFunction("throw_int", kSig_v_v) + .addBody([ + ...wasmI32Const(7), + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_int()); +}, "Wasm function throws integer"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + const tagIndex= builder.addTag(kSig_v_r); + builder.addFunction("catch_exception", kSig_r_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, fnIndex, + kExprCatch, tagIndex, + kExprReturn, + kExprEnd, + kExprRefNull, kExternRefCode, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_exception()); +}, "Imported JS function throws"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + builder.addFunction("catch_and_rethrow", kSig_r_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, fnIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd, + kExprRefNull, kExternRefCode, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_and_rethrow()); +}, "Imported JS function throws, Wasm catches and rethrows"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/constructor.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/constructor.tentative.any.js new file mode 100644 index 0000000000..a46d1816c3 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/constructor.tentative.any.js @@ -0,0 +1,62 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +test(() => { + assert_function_name( + WebAssembly.Exception, + "Exception", + "WebAssembly.Exception" + ); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Exception, 1, "WebAssembly.Exception"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Exception()); +}, "No arguments"); + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + assert_throws_js(TypeError, () => WebAssembly.Exception(tag)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js( + TypeError, + () => new WebAssembly.Exception(invalidArgument), + `new Exception(${format_value(invalidArgument)})` + ); + } +}, "Invalid descriptor argument"); + +test(() => { + const typesAndArgs = [ + ["i32", 123n], + ["i32", Symbol()], + ["f32", 123n], + ["f64", 123n], + ["i64", undefined], + ]; + for (const typeAndArg of typesAndArgs) { + const tag = new WebAssembly.Tag({ parameters: [typeAndArg[0]] }); + assert_throws_js( + TypeError, + () => new WebAssembly.Exception(tag, typeAndArg[1]) + ); + } +}, "Invalid exception argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/getArg.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/getArg.tentative.any.js new file mode 100644 index 0000000000..87719c7ebd --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/getArg.tentative.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(TypeError, () => exn.getArg()); + assert_throws_js(TypeError, () => exn.getArg(tag)); +}, "Missing arguments"); + +test(() => { + const invalidValues = [undefined, null, true, "", Symbol(), 1, {}]; + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (argument of invalidValues) { + assert_throws_js(TypeError, () => exn.getArg(argument, 0)); + } +}, "Invalid exception argument"); + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(RangeError, () => exn.getArg(tag, 1)); +}, "Index out of bounds"); + +test(() => { + const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { + valueOf() { + return 0x100000000; + }, + }, + ]; + + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (const value of outOfRangeValues) { + assert_throws_js(RangeError, () => exn.getArg(tag, value)); + } +}, "Getting out-of-range argument"); + +test(() => { + const tag = new WebAssembly.Tag({ parameters: ["i32"] }); + const exn = new WebAssembly.Exception(tag, [42]); + assert_equals(exn.getArg(tag, 0), 42); +}, "getArg"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/identity.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/identity.tentative.any.js new file mode 100644 index 0000000000..2675668ec7 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/identity.tentative.any.js @@ -0,0 +1,61 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const tag = new WebAssembly.Tag({ parameters: ["i32"] }); + const exn = new WebAssembly.Exception(tag, [42]); + const exn_same_payload = new WebAssembly.Exception(tag, [42]); + const exn_diff_payload = new WebAssembly.Exception(tag, [53]); + + const builder = new WasmModuleBuilder(); + const jsfuncIndex = builder.addImport("module", "jsfunc", kSig_v_v); + const tagIndex = builder.addImportedTag("module", "tag", kSig_v_i); + const imports = { + module: { + jsfunc: function() { throw exn; }, + tag: tag + } + }; + + builder + .addFunction("catch_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, jsfuncIndex, + kExprCatch, tagIndex, + kExprDrop, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + builder + .addFunction("catch_all_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, jsfuncIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + WebAssembly.instantiate(buffer, imports).then(result => { + try { + result.instance.exports.catch_rethrow(); + } catch (e) { + assert_equals(e, exn); + assert_not_equals(e, exn_same_payload); + assert_not_equals(e, exn_diff_payload); + } + try { + result.instance.exports.catch_all_rethrow(); + } catch (e) { + assert_equals(e, exn); + assert_not_equals(e, exn_same_payload); + assert_not_equals(e, exn_diff_payload); + } + }); +}, "Identity check"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/is.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/is.tentative.any.js new file mode 100644 index 0000000000..840d00bf0d --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/is.tentative.any.js @@ -0,0 +1,25 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(TypeError, () => exn.is()); +}, "Missing arguments"); + +test(() => { + const invalidValues = [undefined, null, true, "", Symbol(), 1, {}]; + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (argument of invalidValues) { + assert_throws_js(TypeError, () => exn.is(argument)); + } +}, "Invalid exception argument"); + +test(() => { + const tag1 = new WebAssembly.Tag({ parameters: ["i32"] }); + const tag2 = new WebAssembly.Tag({ parameters: ["i32"] }); + const exn = new WebAssembly.Exception(tag1, [42]); + assert_true(exn.is(tag1)); + assert_false(exn.is(tag2)); +}, "is"); diff --git a/testing/web-platform/tests/wasm/jsapi/exception/toString.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/exception/toString.tentative.any.js new file mode 100644 index 0000000000..6885cf0deb --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/exception/toString.tentative.any.js @@ -0,0 +1,21 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const argument = { parameters: [] }; + const tag = new WebAssembly.Tag(argument); + const exn = new WebAssembly.Exception(tag, []); + assert_class_string(exn, "WebAssembly.Exception"); +}, "Object.prototype.toString on an Exception"); + +test(() => { + assert_own_property(WebAssembly.Exception.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor( + WebAssembly.Exception.prototype, + Symbol.toStringTag + ); + assert_equals(propDesc.value, "WebAssembly.Exception", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js new file mode 100644 index 0000000000..2e63d5fa10 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js @@ -0,0 +1,16 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function addxy(x, y) { + return x + y +} + +test(() => { + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_equals(fun(1, 2), 3) +}, "test calling function") + +test(() => { + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_throws_js(TypeError, () => new fun(1, 2)); +}, "test constructing function"); diff --git a/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js new file mode 100644 index 0000000000..fc92fcfaf0 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js @@ -0,0 +1,65 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function addxy(x, y) { + return x + y +} + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_function_name(WebAssembly.Function, "Function", "WebAssembly.Function"); +}, "name"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_function_length(WebAssembly.Function, 2, "WebAssembly.Function"); +}, "length"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function()); + const argument = {parameters: [], results: []}; + assert_throws_js(TypeError, () => new WebAssembly.Function(argument)); +}, "Too few arguments"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + const arguments = [{parameters: ["i32", "i32"], results: ["i32"]}, addxy]; + assert_throws_js(TypeError, () => WebAssembly.Function(...arguments)); +}, "Calling"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_true(fun instanceof WebAssembly.Function) +}, "construct with JS function") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: []}, addxy)) +}, "fail with missing results") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({results: []}, addxy)) +}, "fail with missing parameters") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [1], results: [true]}, addxy)) +}, "fail with non-string parameters & results") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: ["invalid"], results: ["invalid"]}, addxy)) +}, "fail with non-existent parameter and result type") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, 72)) +}, "fail with non-function object") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, {})) +}, "fail to construct with non-callable object") diff --git a/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js new file mode 100644 index 0000000000..f0dd6ea6f8 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js @@ -0,0 +1,30 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function testfunc(n) {} + +test(() => { + var table = new WebAssembly.Table({element: "anyfunc", initial: 3}) + var func1 = new WebAssembly.Function({parameters: ["i32"], results: []}, testfunc) + table.set(0, func1) + var func2 = new WebAssembly.Function({parameters: ["f32"], results: []}, testfunc) + table.set(1, func2) + var func3 = new WebAssembly.Function({parameters: ["i64"], results: []}, testfunc) + table.set(2, func3) + + var first = table.get(0) + assert_true(first instanceof WebAssembly.Function) + assert_equals(first, func1) + assert_equals(first.type().parameters[0], func1.type().parameters[0]) + + var second = table.get(1) + assert_true(second instanceof WebAssembly.Function) + assert_equals(second, func2) + assert_equals(second.type().parameters[0], func2.type().parameters[0]) + + var third = table.get(2) + assert_true(third instanceof WebAssembly.Function) + assert_equals(third, func3) + assert_equals(third.type().parameters[0], func3.type().parameters[0]) + +}, "Test insertion into table") diff --git a/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js new file mode 100644 index 0000000000..72a7f1bfbe --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js @@ -0,0 +1,28 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function addNumbers(x, y, z) { + return x+y+z; +} + +function doNothing() {} + +function assert_function(functype, func) { + var wasmFunc = new WebAssembly.Function(functype, func); + assert_equals(functype.parameters.length, wasmFunc.type().parameters.length); + for(let i = 0; i < functype.parameters.length; i++) { + assert_equals(functype.parameters[i], wasmFunc.type().parameters[i]); + } + assert_equals(functype.results.length, wasmFunc.type().results.length); + for(let i = 0; i < functype.results.length; i++) { + assert_equals(functype.results[i], wasmFunc.type().results[i]); + } +} + +test(() => { + assert_function({results: [], parameters: []}, doNothing); +}, "Check empty results and parameters") + +test(() => { + assert_function({results: ["f64"], parameters: ["i32", "i64", "f32"]}, addNumbers) +}, "Check all types") diff --git a/testing/web-platform/tests/wasm/jsapi/functions/entry-different-function-realm.html b/testing/web-platform/tests/wasm/jsapi/functions/entry-different-function-realm.html new file mode 100644 index 0000000000..3af3dd924f --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/entry-different-function-realm.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Entry settings object for host functions when the function realm is different from the test realm</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/wasm/jsapi/wasm-module-builder.js"></script> +<script src="/wasm/jsapi/functions/helper.js"></script> + +<!-- This is what would normally be considered the entry page. However, we use functions from the + resources/function/function.html realm. So window.open() should resolve relative to that realm + inside host functions. --> + +<iframe src="resources/entry-incumbent.html"></iframe> +<iframe src="resources/function/function.html" id="function-frame"></iframe> + +<script> +setup({ explicit_done: true }); + +const relativeURL = "resources/window-to-open.html"; +const expectedURL = (new URL(relativeURL, document.querySelector("#function-frame").src)).href; + +const incumbentWindow = frames[0]; +const functionWindow = frames[1]; +const FunctionFromAnotherWindow = functionWindow.Function; + +window.onload = () => { + async_test(t => { + t.add_cleanup(() => { delete functionWindow.args; }); + functionWindow.args = [incumbentWindow, relativeURL, t, assert_equals, expectedURL]; + + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = window.args; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + call_later(func); + }, "Start function"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/entry.html b/testing/web-platform/tests/wasm/jsapi/functions/entry.html new file mode 100644 index 0000000000..1501807449 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/entry.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Entry settings object for host functions</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/wasm/jsapi/wasm-module-builder.js"></script> +<script src="/wasm/jsapi/functions/helper.js"></script> + +<!-- This is the entry page, so window.open() should resolve relative to it, even inside host functions. --> + +<iframe src="resources/entry-incumbent.html"></iframe> + +<script> +setup({ explicit_done: true }); + +const relativeURL = "resources/window-to-open.html"; +const expectedURL = (new URL(relativeURL, location.href)).href; + +const incumbentWindow = frames[0]; + +window.onload = () => { + async_test(t => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }, "Sanity check: this all works as expected synchronously"); + + async_test(t => { + // No t.step_func because that could change the realms + call_later(() => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }); + }, "Start function"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/helper.js b/testing/web-platform/tests/wasm/jsapi/functions/helper.js new file mode 100644 index 0000000000..487791c69a --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/helper.js @@ -0,0 +1,12 @@ +function call_later(f) { + const builder = new WasmModuleBuilder(); + const functionIndex = builder.addImport("module", "imported", kSig_v_v); + builder.addStart(functionIndex); + const buffer = builder.toBuffer(); + + WebAssembly.instantiate(buffer, { + "module": { + "imported": f, + } + }); +} diff --git a/testing/web-platform/tests/wasm/jsapi/functions/incumbent.html b/testing/web-platform/tests/wasm/jsapi/functions/incumbent.html new file mode 100644 index 0000000000..cb27632977 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/incumbent.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent settings object for host functions</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- This is the entry page. --> + +<iframe src="resources/incumbent-incumbent.html"></iframe> + +<script> +setup({ explicit_done: true }); + +// postMessage should pick the incumbent page as its .source value to set on the MessageEvent, even +// inside host functions. +const expectedURL = (new URL("resources/incumbent-incumbent.html", location.href)).href; + +let testId = 0; + +window.onload = () => { + const relevantWindow = frames[0].document.querySelector("#r").contentWindow; + + function setupTest(t) { + ++testId; + const thisTestId = testId; + + relevantWindow.addEventListener("messagereceived", t.step_func(e => { + const [receivedTestId, receivedSourceURL] = e.detail; + + if (receivedTestId !== thisTestId) { + return; + } + + assert_equals(receivedSourceURL, expectedURL); + t.done(); + })); + + return thisTestId; + } + + async_test(t => { + const thisTestId = setupTest(t); + + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }, "Sanity check: this all works as expected synchronously"); + + async_test(t => { + const thisTestId = setupTest(t); + frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(thisTestId, "*"); + }, "Start function"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/README.md b/testing/web-platform/tests/wasm/jsapi/functions/resources/README.md new file mode 100644 index 0000000000..a89258a4e0 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/README.md @@ -0,0 +1,5 @@ +A couple notes about the files scattered in this `resources/` directory: + +* The nested directory structure is necessary here so that relative URL resolution can be tested; we need different sub-paths for each document. + +* The semi-duplicate `window-to-open.html`s scattered throughout are present because Firefox, at least, does not fire `Window` `load` events for 404s, so we want to ensure that no matter which global is used, `window`'s `load` event is hit and our tests can proceed. diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/current/current.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/current/current.html new file mode 100644 index 0000000000..63d9c437fc --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/current/current.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Current page used as a test helper</title> + diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/current/resources/window-to-open.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/current/resources/window-to-open.html new file mode 100644 index 0000000000..1bc4cca9a3 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/current/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the current settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/entry-incumbent.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/entry-incumbent.html new file mode 100644 index 0000000000..6b210563e9 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/entry-incumbent.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent page used as a test helper</title> + +<iframe src="relevant/relevant.html" id="r"></iframe> +<iframe src="current/current.html" id="c"></iframe> + +<script> +const relevant = document.querySelector("#r"); +const current = document.querySelector("#c"); + +window.runWindowOpenVeryIndirectly = (...args) => { + return current.contentWindow.open.call(relevant.contentWindow, ...args); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/function/function.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/function/function.html new file mode 100644 index 0000000000..979b902eaa --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/function/function.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Realm for a host function used as a test helper</title> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/function/resources/window-to-open.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/function/resources/window-to-open.html new file mode 100644 index 0000000000..3928c1f8aa --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/function/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the function's settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/incumbent-incumbent.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/incumbent-incumbent.html new file mode 100644 index 0000000000..5e84f65a08 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/incumbent-incumbent.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent page used as a test helper</title> + +<script src="/wasm/jsapi/wasm-module-builder.js"></script> +<script src="/wasm/jsapi/functions/helper.js"></script> + +<iframe src="relevant/relevant.html" id="r"></iframe> +<iframe src="current/current.html" id="c"></iframe> + +<script> +const relevant = document.querySelector("#r"); +const current = document.querySelector("#c"); + +window.runWindowPostMessageVeryIndirectly = (...args) => { + return current.contentWindow.postMessage.call(relevant.contentWindow, ...args); +}; + +// This tests the backup incumbent settings object stack scenario, by avoiding putting user code on the stack. +window.runWindowPostMessageVeryIndirectlyWithNoUserCode = (...args) => { + const runWindowPostMessage = current.contentWindow.postMessage.bind(relevant.contentWindow, ...args); + call_later(runWindowPostMessage); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/relevant.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/relevant.html new file mode 100644 index 0000000000..06df91c237 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/relevant.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Relevant page used as a test helper</title> + +<script> +// incumbent.html will end up posting a message to here. We need to signal back the "source". + +window.onmessage = e => { + const testId = e.data; + const sourceURL = e.source.document.URL; + + window.dispatchEvent(new CustomEvent("messagereceived", { detail: [testId, sourceURL] })); +}; +</script> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html new file mode 100644 index 0000000000..4138b5a084 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the relevant settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/resources/window-to-open.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/resources/window-to-open.html new file mode 100644 index 0000000000..7743b9b578 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the incumbent settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/wasm/jsapi/functions/resources/window-to-open.html b/testing/web-platform/tests/wasm/jsapi/functions/resources/window-to-open.html new file mode 100644 index 0000000000..ce357937f5 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/functions/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the entry settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/wasm/jsapi/gc/casts.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/gc/casts.tentative.any.js new file mode 100644 index 0000000000..cce06224fd --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/gc/casts.tentative.any.js @@ -0,0 +1,332 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structIndex2 = builder.addStruct([makeField(kWasmF32, true)]); + const arrayIndex2 = builder.addArray(kWasmF32, true); + const funcIndex = builder.addType({ params: [], results: [] }); + const funcIndex2 = builder.addType({ params: [], results: [kWasmI32] }); + + const argFunctions = [ + { name: "any", code: kWasmAnyRef }, + { name: "eq", code: kWasmEqRef }, + { name: "struct", code: kWasmStructRef }, + { name: "array", code: kWasmArrayRef }, + { name: "i31", code: kWasmI31Ref }, + { name: "func", code: kWasmFuncRef }, + { name: "extern", code: kWasmExternRef }, + { name: "none", code: kWasmNullRef }, + { name: "nofunc", code: kWasmNullFuncRef }, + { name: "noextern", code: kWasmNullExternRef }, + { name: "concreteStruct", code: structIndex }, + { name: "concreteArray", code: arrayIndex }, + { name: "concreteFunc", code: funcIndex }, + ]; + + for (const desc of argFunctions) { + builder + .addFunction(desc.name + "Arg", makeSig_v_x(wasmRefType(desc.code))) + .addBody([]) + .exportFunc(); + + builder + .addFunction(desc.name + "NullableArg", makeSig_v_x(wasmRefNullType(desc.code))) + .addBody([]) + .exportFunc(); + } + + builder + .addFunction("makeStruct", makeSig_r_v(wasmRefType(structIndex))) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(wasmRefType(arrayIndex))) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + builder + .addFunction("makeStruct2", makeSig_r_v(wasmRefType(structIndex2))) + .addBody([...wasmF32Const(42), + ...GCInstr(kExprStructNew), structIndex2]) + .exportFunc(); + + builder + .addFunction("makeArray2", makeSig_r_v(wasmRefType(arrayIndex2))) + .addBody([...wasmF32Const(42), ...wasmI32Const(5), + ...GCInstr(kExprArrayNew), arrayIndex2]) + .exportFunc(); + + builder + .addFunction("testFunc", funcIndex) + .addBody([]) + .exportFunc(); + + builder + .addFunction("testFunc2", funcIndex2) + .addBody([...wasmI32Const(42)]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + exports.anyArg(exports.makeStruct()); + exports.anyArg(exports.makeArray()); + exports.anyArg(42); + exports.anyArg(42n); + exports.anyArg("foo"); + exports.anyArg({}); + exports.anyArg(() => {}); + exports.anyArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.anyArg(null)); + + exports.anyNullableArg(null); + exports.anyNullableArg(exports.makeStruct()); + exports.anyNullableArg(exports.makeArray()); + exports.anyNullableArg(42); + exports.anyNullableArg(42n); + exports.anyNullableArg("foo"); + exports.anyNullableArg({}); + exports.anyNullableArg(() => {}); + exports.anyNullableArg(exports.testFunc); +}, "anyref casts"); + +test(() => { + exports.eqArg(exports.makeStruct()); + exports.eqArg(exports.makeArray()); + exports.eqArg(42); + assert_throws_js(TypeError, () => exports.eqArg(42n)); + assert_throws_js(TypeError, () => exports.eqArg("foo")); + assert_throws_js(TypeError, () => exports.eqArg({})); + assert_throws_js(TypeError, () => exports.eqArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqArg(() => {})); + assert_throws_js(TypeError, () => exports.eqArg(null)); + + exports.eqNullableArg(null); + exports.eqNullableArg(exports.makeStruct()); + exports.eqNullableArg(exports.makeArray()); + exports.eqNullableArg(42); + assert_throws_js(TypeError, () => exports.eqNullableArg(42n)); + assert_throws_js(TypeError, () => exports.eqNullableArg("foo")); + assert_throws_js(TypeError, () => exports.eqNullableArg({})); + assert_throws_js(TypeError, () => exports.eqNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqNullableArg(() => {})); +}, "eqref casts"); + +test(() => { + exports.structArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structArg(42)); + assert_throws_js(TypeError, () => exports.structArg(42n)); + assert_throws_js(TypeError, () => exports.structArg("foo")); + assert_throws_js(TypeError, () => exports.structArg({})); + assert_throws_js(TypeError, () => exports.structArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structArg(() => {})); + assert_throws_js(TypeError, () => exports.structArg(null)); + + exports.structNullableArg(null); + exports.structNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structNullableArg(42)); + assert_throws_js(TypeError, () => exports.structNullableArg(42n)); + assert_throws_js(TypeError, () => exports.structNullableArg("foo")); + assert_throws_js(TypeError, () => exports.structNullableArg({})); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structNullableArg(() => {})); +}, "structref casts"); + +test(() => { + exports.arrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayArg(42)); + assert_throws_js(TypeError, () => exports.arrayArg(42n)); + assert_throws_js(TypeError, () => exports.arrayArg("foo")); + assert_throws_js(TypeError, () => exports.arrayArg({})); + assert_throws_js(TypeError, () => exports.arrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayArg(() => {})); + assert_throws_js(TypeError, () => exports.arrayArg(null)); + + exports.arrayNullableArg(null); + exports.arrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.arrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.arrayNullableArg({})); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(() => {})); +}, "arrayref casts"); + +test(() => { + exports.i31Arg(42); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31Arg(42n)); + assert_throws_js(TypeError, () => exports.i31Arg("foo")); + assert_throws_js(TypeError, () => exports.i31Arg({})); + assert_throws_js(TypeError, () => exports.i31Arg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31Arg(() => {})); + assert_throws_js(TypeError, () => exports.i31Arg(null)); + + exports.i31NullableArg(null); + exports.i31NullableArg(42); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31NullableArg(42n)); + assert_throws_js(TypeError, () => exports.i31NullableArg("foo")); + assert_throws_js(TypeError, () => exports.i31NullableArg({})); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31NullableArg(() => {})); +}, "i31ref casts"); + +test(() => { + exports.funcArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcArg(42)); + assert_throws_js(TypeError, () => exports.funcArg(42n)); + assert_throws_js(TypeError, () => exports.funcArg("foo")); + assert_throws_js(TypeError, () => exports.funcArg({})); + assert_throws_js(TypeError, () => exports.funcArg(() => {})); + assert_throws_js(TypeError, () => exports.funcArg(null)); + + exports.funcNullableArg(null); + exports.funcNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcNullableArg(42)); + assert_throws_js(TypeError, () => exports.funcNullableArg(42n)); + assert_throws_js(TypeError, () => exports.funcNullableArg("foo")); + assert_throws_js(TypeError, () => exports.funcNullableArg({})); + assert_throws_js(TypeError, () => exports.funcNullableArg(() => {})); +}, "funcref casts"); + +test(() => { + exports.externArg(exports.makeArray()); + exports.externArg(exports.makeStruct()); + exports.externArg(42); + exports.externArg(42n); + exports.externArg("foo"); + exports.externArg({}); + exports.externArg(exports.testFunc); + exports.externArg(() => {}); + assert_throws_js(TypeError, () => exports.externArg(null)); + + exports.externNullableArg(null); + exports.externNullableArg(exports.makeArray()); + exports.externNullableArg(exports.makeStruct()); + exports.externNullableArg(42); + exports.externNullableArg(42n); + exports.externNullableArg("foo"); + exports.externNullableArg({}); + exports.externNullableArg(exports.testFunc); + exports.externNullableArg(() => {}); +}, "externref casts"); + +test(() => { + for (const nullfunc of [exports.noneArg, exports.nofuncArg, exports.noexternArg]) { + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + assert_throws_js(TypeError, () => nullfunc(null)); + } + + for (const nullfunc of [exports.noneNullableArg, exports.nofuncNullableArg, exports.noexternNullableArg]) { + nullfunc(null); + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + } +}, "null casts"); + +test(() => { + exports.concreteStructArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructArg({})); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteStructArg(null)); + + exports.concreteStructNullableArg(null); + exports.concreteStructNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(() => {})); +}, "concrete struct casts"); + +test(() => { + exports.concreteArrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(null)); + + exports.concreteArrayNullableArg(null); + exports.concreteArrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(() => {})); +}, "concrete array casts"); + +test(() => { + exports.concreteFuncArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(null)); + + exports.concreteFuncNullableArg(null); + exports.concreteFuncNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(() => {})); +}, "concrete func casts"); diff --git a/testing/web-platform/tests/wasm/jsapi/gc/exported-object.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/gc/exported-object.tentative.any.js new file mode 100644 index 0000000000..b572f14006 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/gc/exported-object.tentative.any.js @@ -0,0 +1,190 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let functions = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structRef = wasmRefType(structIndex); + const arrayRef = wasmRefType(arrayIndex); + + builder + .addFunction("makeStruct", makeSig_r_v(structRef)) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(arrayRef)) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + functions = instance.exports; +}); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(struct.foo, undefined); + assert_equals(struct[0], undefined); + assert_equals(array.foo, undefined); + assert_equals(array[0], undefined); +}, "property access"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getOwnPropertyNames(struct).length, 0); + assert_equals(Object.getOwnPropertyNames(array).length, 0); +}, "ownPropertyNames"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.defineProperty(struct, "foo", { value: 1 })); + assert_throws_js(TypeError, () => Object.defineProperty(array, "foo", { value: 1 })); +}, "defineProperty"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getPrototypeOf(struct), null); + assert_equals(Object.getPrototypeOf(array), null); +}, "getPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.setPrototypeOf(struct, {})); + assert_throws_js(TypeError, () => Object.setPrototypeOf(array, {})); +}, "setPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_false(Object.isExtensible(struct)); + assert_false(Object.isExtensible(array)); +}, "isExtensible"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.preventExtensions(struct)); + assert_throws_js(TypeError, () => Object.preventExtensions(array)); +}, "preventExtensions"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.seal(struct)); + assert_throws_js(TypeError, () => Object.seal(array)); +}, "sealing"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(typeof struct, "object"); + assert_equals(typeof array, "object"); +}, "typeof"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.toString()); + assert_equals(Object.prototype.toString.call(struct), "[object Object]"); + assert_throws_js(TypeError, () => array.toString()); + assert_equals(Object.prototype.toString.call(array), "[object Object]"); +}, "toString"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.valueOf()); + assert_equals(Object.prototype.valueOf.call(struct), struct); + assert_throws_js(TypeError, () => array.valueOf()); + assert_equals(Object.prototype.valueOf.call(array), array); +}, "valueOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new Map(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new Set(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as set element"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new WeakMap(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as weak map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new WeakSet(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as weak set element"); diff --git a/testing/web-platform/tests/wasm/jsapi/gc/i31.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/gc/i31.tentative.any.js new file mode 100644 index 0000000000..17fd82440c --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/gc/i31.tentative.any.js @@ -0,0 +1,98 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const i31Ref = wasmRefType(kWasmI31Ref); + const i31NullableRef = wasmRefNullType(kWasmI31Ref); + const anyRef = wasmRefType(kWasmAnyRef); + + builder + .addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31New)]) + .exportFunc(); + + builder + .addFunction("castI31", makeSig_r_x(kWasmI32, anyRef)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprRefCast), kI31RefCode, + ...GCInstr(kExprI31GetU)]) + .exportFunc(); + + builder + .addFunction("getI31", makeSig_r_x(kWasmI32, i31Ref)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31GetS)]) + .exportFunc(); + + builder + .addFunction("argI31", makeSig_v_x(i31NullableRef)) + .addBody([]) + .exportFunc(); + + builder + .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprI31New)]) + builder + .addExportOfKind("i31Global", kExternalGlobal, 0); + + builder + .addTable(i31NullableRef, 10) + builder + .addExportOfKind("i31Table", kExternalTable, 0); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + assert_equals(exports.makeI31(42), 42); + assert_equals(exports.makeI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.makeI31(2 ** 30), -(2 ** 30)); + assert_equals(exports.makeI31(-(2 ** 30)), -(2 ** 30)); + assert_equals(exports.makeI31(2 ** 31 - 1), -1); + assert_equals(exports.makeI31(2 ** 31), 0); +}, "i31ref conversion to Number"); + +test(() => { + assert_equals(exports.getI31(exports.makeI31(42)), 42); + assert_equals(exports.getI31(42), 42); + assert_equals(exports.getI31(2.0 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.getI31(-(2 ** 30)), -(2 ** 30)); +}, "Number conversion to i31ref"); + +test(() => { + exports.argI31(null); + assert_throws_js(TypeError, () => exports.argI31(2 ** 30)); + assert_throws_js(TypeError, () => exports.argI31(-(2 ** 30) - 1)); + assert_throws_js(TypeError, () => exports.argI31(2n)); + assert_throws_js(TypeError, () => exports.argI31(() => 3)); + assert_throws_js(TypeError, () => exports.argI31(exports.getI31)); +}, "Check i31ref argument type"); + +test(() => { + assert_equals(exports.castI31(42), 42); + assert_equals(exports.castI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 30); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(-(2 ** 30) - 1); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 32); }); +}, "Numbers in i31 range are i31ref, not hostref"); + +test(() => { + assert_equals(exports.i31Global.value, 0); + exports.i31Global.value = 42; + assert_throws_js(TypeError, () => exports.i31Global.value = 2 ** 30); + assert_throws_js(TypeError, () => exports.i31Global.value = -(2 ** 30) - 1); + assert_equals(exports.i31Global.value, 42); +}, "i31ref global"); + +test(() => { + assert_equals(exports.i31Table.get(0), null); + exports.i31Table.set(0, 42); + assert_throws_js(TypeError, () => exports.i31Table.set(0, 2 ** 30)); + assert_throws_js(TypeError, () => exports.i31Table.set(0, -(2 ** 30) - 1)); + assert_equals(exports.i31Table.get(0), 42); +}, "i31ref table"); diff --git a/testing/web-platform/tests/wasm/jsapi/global/constructor.any.js b/testing/web-platform/tests/wasm/jsapi/global/constructor.any.js new file mode 100644 index 0000000000..f83f77a5c3 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/global/constructor.any.js @@ -0,0 +1,171 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function assert_Global(actual, expected) { + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype, + "prototype"); + assert_true(Object.isExtensible(actual), "extensible"); + + assert_equals(actual.value, expected, "value"); + assert_equals(actual.valueOf(), expected, "valueOf"); +} + +test(() => { + assert_function_name(WebAssembly.Global, "Global", "WebAssembly.Global"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Global, 1, "WebAssembly.Global"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Global()); +}, "No arguments"); + +test(() => { + const argument = { "value": "i32" }; + assert_throws_js(TypeError, () => WebAssembly.Global(argument)); +}, "Calling"); + +test(() => { + const order = []; + + new WebAssembly.Global({ + get value() { + order.push("descriptor value"); + return { + toString() { + order.push("descriptor value toString"); + return "f64"; + }, + }; + }, + + get mutable() { + order.push("descriptor mutable"); + return false; + }, + }, { + valueOf() { + order.push("value valueOf()"); + } + }); + + assert_array_equals(order, [ + "descriptor mutable", + "descriptor value", + "descriptor value toString", + "value valueOf()", + ]); +}, "Order of evaluation"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Global(invalidArgument), + `new Global(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { value }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument)); + } +}, "Invalid type argument"); + +test(() => { + const argument = { "value": "v128" }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument)); +}, "Construct v128 global"); + +test(() => { + const argument = { "value": "i64" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0n); +}, "i64 with default"); + +for (const type of ["i32", "f32", "f64"]) { + test(() => { + const argument = { "value": type }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0); + }, `Default value for type ${type}`); + + const valueArguments = [ + [undefined, 0], + [null, 0], + [true, 1], + [false, 0], + [2, 2], + ["3", 3], + [{ toString() { return "5" } }, 5, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8, "object with valueOf returning string"], + [{ toString() { return 6 } }, 6, "object with toString returning number"], + [{ valueOf() { return 9 } }, 9, "object with valueOf returning number"], + ]; + for (const [value, expected, name = format_value(value)] of valueArguments) { + test(() => { + const argument = { "value": type }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type ${type}`); + } + + test(() => { + const argument = { "value": type }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, 0n)); + }, `BigInt value for type ${type}`); +} + +const valueArguments = [ + [undefined, 0n], + [true, 1n], + [false, 0n], + ["3", 3n], + [123n, 123n], + [{ toString() { return "5" } }, 5n, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8n, "object with valueOf returning string"], + [{ toString() { return 6n } }, 6n, "object with toString returning bigint"], + [{ valueOf() { return 9n } }, 9n, "object with valueOf returning bigint"], +]; +for (const [value, expected, name = format_value(value)] of valueArguments) { + test(() => { + const argument = { "value": "i64" }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type i64`); +} + +const invalidBigints = [ + null, + 666, + { toString() { return 5 } }, + { valueOf() { return 8 } }, + Symbol(), +]; +for (const invalidBigint of invalidBigints) { + test(() => { + var argument = { "value": "i64" }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, invalidBigint)); + }, `Pass non-bigint as i64 Global value: ${format_value(invalidBigint)}`); +} + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument, 0, {}); + assert_Global(global, 0); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/global/toString.any.js b/testing/web-platform/tests/wasm/jsapi/global/toString.any.js new file mode 100644 index 0000000000..b308498982 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/global/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument); + assert_class_string(global, "WebAssembly.Global"); +}, "Object.prototype.toString on an Global"); + +test(() => { + assert_own_property(WebAssembly.Global.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Global", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/global/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/global/type.tentative.any.js new file mode 100644 index 0000000000..78d612529d --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/global/type.tentative.any.js @@ -0,0 +1,65 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); + + assert_equals(globaltype.value, argument.value); + assert_equals(globaltype.mutable, argument.mutable); +} + +test(() => { + assert_type({ "value": "i32", "mutable": true}); +}, "i32, mutable"); + +test(() => { + assert_type({ "value": "i32", "mutable": false}); +}, "i32, immutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": true}); +}, "i64, mutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": false}); +}, "i64, immutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": true}); +}, "f32, mutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": false}); +}, "f32, immutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": true}); +}, "f64, mutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": false}); +}, "f64, immutable"); + +test(() => { + assert_type({"value": "externref", "mutable": true}) +}, "externref, mutable") + +test(() => { + assert_type({"value": "externref", "mutable": false}) +}, "externref, immutable") + +test(() => { + assert_type({"value": "funcref", "mutable": true}) +}, "funcref, mutable") + +test(() => { + assert_type({"value": "funcref", "mutable": false}) +}, "funcref, immutable") + +test(() => { + const myglobal = new WebAssembly.Global({"value": "i32", "mutable": true}); + const propertyNames = Object.getOwnPropertyNames(myglobal.type()); + assert_equals(propertyNames[0], "mutable"); + assert_equals(propertyNames[1], "value"); +}, "key ordering"); diff --git a/testing/web-platform/tests/wasm/jsapi/global/value-get-set.any.js b/testing/web-platform/tests/wasm/jsapi/global/value-get-set.any.js new file mode 100644 index 0000000000..bee5581f41 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/global/value-get-set.any.js @@ -0,0 +1,152 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Global, + WebAssembly.Global.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `getter with this=${format_value(thisValue)}`); + assert_throws_js(TypeError, () => setter.call(thisValue, 1), `setter with this=${format_value(thisValue)}`); + } +}, "Branding"); + +for (const type of ["i32", "i64", "f32", "f64"]) { + const [initial, value, invalid] = type === "i64" ? [0n, 1n, 2] : [0, 1, 2n]; + const immutableOptions = [ + [{}, "missing"], + [{ "mutable": undefined }, "undefined"], + [{ "mutable": null }, "null"], + [{ "mutable": false }, "false"], + [{ "mutable": "" }, "empty string"], + [{ "mutable": 0 }, "zero"], + ]; + for (const [opts, name] of immutableOptions) { + test(() => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + assert_throws_js(TypeError, () => global.value = value); + + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} (${name})`); + + test(t => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + const value = { + valueOf: t.unreached_func("should not call valueOf"), + toString: t.unreached_func("should not call toString"), + }; + assert_throws_js(TypeError, () => global.value = value); + + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} with ToNumber side-effects (${name})`); + } + + const mutableOptions = [ + [{ "mutable": true }, "true"], + [{ "mutable": 1 }, "one"], + [{ "mutable": "x" }, "string"], + [Object.create({ "mutable": true }), "true on prototype"], + ]; + for (const [opts, name] of mutableOptions) { + test(() => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + global.value = value; + + assert_throws_js(TypeError, () => global.value = invalid); + + assert_equals(global.value, value, "post-set value"); + assert_equals(global.valueOf(), value, "post-set valueOf"); + }, `Mutable ${type} (${name})`); + } +} + +test(() => { + const argument = { "value": "i64", "mutable": true }; + const global = new WebAssembly.Global(argument); + + assert_equals(global.value, 0n, "initial value using ToJSValue"); + + const valid = [ + [123n, 123n], + [2n ** 63n, - (2n ** 63n)], + [true, 1n], + [false, 0n], + ["456", 456n], + ]; + for (const [input, output] of valid) { + global.value = input; + assert_equals(global.valueOf(), output, "post-set valueOf"); + } + + const invalid = [ + undefined, + null, + 0, + 1, + 4.2, + Symbol(), + ]; + for (const input of invalid) { + assert_throws_js(TypeError, () => global.value = input); + } +}, "i64 mutability"); + +test(() => { + const argument = { "value": "i32", "mutable": true }; + const global = new WebAssembly.Global(argument); + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + assert_throws_js(TypeError, () => setter.call(global)); +}, "Calling setter without argument"); + +test(() => { + const argument = { "value": "i32", "mutable": true }; + const global = new WebAssembly.Global(argument); + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + assert_equals(getter.call(global, {}), 0); + assert_equals(setter.call(global, 1, {}), undefined); + assert_equals(global.value, 1); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/global/valueOf.any.js b/testing/web-platform/tests/wasm/jsapi/global/valueOf.any.js new file mode 100644 index 0000000000..5bcb171825 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/global/valueOf.any.js @@ -0,0 +1,28 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const argument = { "value": "i32" }; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Global, + WebAssembly.Global.prototype, + ]; + + const fn = WebAssembly.Global.prototype.valueOf; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument, 0); + assert_equals(global.valueOf({}), 0); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/idlharness.any.js b/testing/web-platform/tests/wasm/jsapi/idlharness.any.js new file mode 100644 index 0000000000..98713d4bf6 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/idlharness.any.js @@ -0,0 +1,22 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: script=../resources/load_wasm.js + +'use strict'; + +// https://webassembly.github.io/spec/js-api/ + +idl_test( + ['wasm-js-api'], + [], + async idl_array => { + self.mod = await createWasmModule(); + self.instance = new WebAssembly.Instance(self.mod); + + idl_array.add_objects({ + Memory: [new WebAssembly.Memory({initial: 1024})], + Module: [self.mod], + Instance: [self.instance], + }); + } +); diff --git a/testing/web-platform/tests/wasm/jsapi/instance/constructor-bad-imports.any.js b/testing/web-platform/tests/wasm/jsapi/instance/constructor-bad-imports.any.js new file mode 100644 index 0000000000..1ef4f8423d --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instance/constructor-bad-imports.any.js @@ -0,0 +1,13 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...arguments) => { + test(() => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + assert_throws_js(error, () => new WebAssembly.Instance(module, ...arguments)); + }, `new WebAssembly.Instance(module): ${name}`); +}); diff --git a/testing/web-platform/tests/wasm/jsapi/instance/constructor-caching.any.js b/testing/web-platform/tests/wasm/jsapi/instance/constructor-caching.any.js new file mode 100644 index 0000000000..f969364d93 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instance/constructor-caching.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function getExports() { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + builder.addGlobal(kWasmI32, false).exportAs("global"); + builder.addMemory(4, 8, true); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module); + return instance.exports; +} + +test(() => { + const exports = getExports(); + + const builder = new WasmModuleBuilder(); + const functionIndex = builder.addImport("module", "imported", kSig_v_d); + builder.addExport("exportedFunction", functionIndex); + + const globalIndex = builder.addImportedGlobal("module", "global", kWasmI32); + builder.addExportOfKind("exportedGlobal", kExternalGlobal, globalIndex); + + builder.addImportedMemory("module", "memory", 4); + builder.exportMemoryAs("exportedMemory"); + + const tableIndex = builder.addImportedTable("module", "table", 1); + builder.addExportOfKind("exportedTable", kExternalTable, tableIndex); + + const buffer = builder.toBuffer(); + + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, { + "module": { + "imported": exports.fn, + "global": exports.global, + "memory": exports.memory, + "table": exports.table, + } + }); + + assert_equals(instance.exports.exportedFunction, exports.fn); + assert_equals(instance.exports.exportedGlobal, exports.global); + assert_equals(instance.exports.exportedMemory, exports.memory); + assert_equals(instance.exports.exportedTable, exports.table); +}); diff --git a/testing/web-platform/tests/wasm/jsapi/instance/constructor.any.js b/testing/web-platform/tests/wasm/jsapi/instance/constructor.any.js new file mode 100644 index 0000000000..24bf97356c --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instance/constructor.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_function_name(WebAssembly.Instance, "Instance", "WebAssembly.Instance"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Instance, 1, "WebAssembly.Instance"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Instance()); +}, "No arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => new WebAssembly.Instance(argument), + `new Instance(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_throws_js(TypeError, () => WebAssembly.Instance(module)); +}, "Calling"); + +for (const [name, fn] of instanceTestFactory) { + test(() => { + const { buffer, args, exports, verify } = fn(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, ...args); + assert_Instance(instance, exports); + verify(instance); + }, name); +} diff --git a/testing/web-platform/tests/wasm/jsapi/instance/exports.any.js b/testing/web-platform/tests/wasm/jsapi/instance/exports.any.js new file mode 100644 index 0000000000..f7244923d8 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instance/exports.any.js @@ -0,0 +1,66 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Instance, + WebAssembly.Instance.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, "exports"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, "exports"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(instance, {}), exports); +}, "Stray argument"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + instance.exports = {}; + assert_equals(instance.exports, exports, "Should not change the exports"); +}, "Setting (sloppy mode)"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + assert_throws_js(TypeError, () => { + "use strict"; + instance.exports = {}; + }); + assert_equals(instance.exports, exports, "Should not change the exports"); +}, "Setting (strict mode)"); diff --git a/testing/web-platform/tests/wasm/jsapi/instance/toString.any.js b/testing/web-platform/tests/wasm/jsapi/instance/toString.any.js new file mode 100644 index 0000000000..d77037d65b --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instance/toString.any.js @@ -0,0 +1,19 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const emptyModuleBinary = new WasmModuleBuilder().toBuffer(); + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + assert_class_string(instance, "WebAssembly.Instance"); +}, "Object.prototype.toString on an Instance"); + +test(() => { + assert_own_property(WebAssembly.Instance.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Instance", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/instanceTestFactory.js b/testing/web-platform/tests/wasm/jsapi/instanceTestFactory.js new file mode 100644 index 0000000000..2e015af819 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/instanceTestFactory.js @@ -0,0 +1,763 @@ +const instanceTestFactory = [ + [ + "Empty module without imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "Empty module with undefined imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [undefined], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "Empty module with empty imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [{}], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "getter order for imports object", + function() { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global1", kWasmI32); + builder.addImportedGlobal("module2", "global3", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedGlobal("module", "global2", kWasmI32); + const buffer = builder.toBuffer(); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global1() { + order.push("global1 getter"); + return 0; + }, + get global2() { + order.push("global2 getter"); + return 0; + }, + get memory() { + order.push("memory getter"); + return new WebAssembly.Memory({ "initial": 64, maximum: 128 }); + }, + } + }, + get module2() { + order.push("module2 getter"); + return { + get global3() { + order.push("global3 getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global1 getter", + "module2 getter", + "global3 getter", + "module getter", + "memory getter", + "module getter", + "global2 getter", + ]; + return { + buffer, + args: [imports], + exports: {}, + verify: () => assert_array_equals(order, expected), + }; + } + ], + + [ + "imports", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("module", "fn", kSig_v_v); + builder.addImportedGlobal("module", "global", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedTable("module", "table", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "module": { + "fn": function() {}, + "global": 0, + "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }), + "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }), + }, + get "module2"() { + assert_unreached("Should not get modules that are not imported"); + }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "imports with empty module names", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "fn", kSig_v_v); + builder.addImportedGlobal("", "global", kWasmI32); + builder.addImportedMemory("", "memory", 0, 128); + builder.addImportedTable("", "table", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "": { + "fn": function() {}, + "global": 0, + "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }), + "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }), + }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "imports with empty names", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("a", "", kSig_v_v); + builder.addImportedGlobal("b", "", kWasmI32); + builder.addImportedMemory("c", "", 0, 128); + builder.addImportedTable("d", "", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "a": { "": function() {} }, + "b": { "": 0 }, + "c": { "": new WebAssembly.Memory({ "initial": 64, maximum: 128 }) }, + "d": { "": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }) }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: function", + function() { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("", kSig_v_d) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "function", "name": "0", "length": 1 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: table", + function() { + const builder = new WasmModuleBuilder(); + + builder.setTableBounds(1); + builder.addExportOfKind("", kExternalTable, 0); + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "table", "length": 1 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: global", + function() { + const builder = new WasmModuleBuilder(); + + builder.addGlobal(kWasmI32, true) + .exportAs("") + .init = wasmI32Const(7); + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "global", "value": 7 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "No imports", + function() { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + + builder.addGlobal(kWasmI32, true) + .exportAs("global") + .init = wasmI32Const(7); + builder.addGlobal(kWasmF64, true) + .exportAs("global2") + .init = wasmF64Const(1.2); + + builder.addMemory(4, 8, true); + + const buffer = builder.toBuffer(); + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 1 }, + "fn2": { "kind": "function", "name": "1", "length": 0 }, + "table": { "kind": "table", "length": 1 }, + "global": { "kind": "global", "value": 7 }, + "global2": { "kind": "global", "value": 1.2 }, + "memory": { "kind": "memory", "size": 4 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports and imports", + function() { + const value = 102; + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI32); + builder + .addFunction("fn", kSig_i_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => assert_equals(instance.exports.fn(), value) + }; + } + ], + + [ + "i64 exports and imports", + function() { + const value = 102n; + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64); + builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const index2 = builder.addImportedGlobal("module", "global2", kWasmI64); + builder.addExportOfKind("global", kExternalGlobal, index2); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + "global2": 2n ** 63n, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 0 }, + "global": { "kind": "global", "value": -(2n ** 63n) }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => assert_equals(instance.exports.fn(), value) + }; + } + ], + + [ + "import with i32-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6n; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i32", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_i); + const fn2 = builder + .addFunction("fn2", kSig_i_v) + .addBody([ + kExprI32Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26); + return { valueOf() { return 6; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i64", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_l); + const fn2 = builder + .addFunction("fn2", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26n); + return { valueOf() { return 6n; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6n); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i32-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_i) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6n)) + }; + } + ], + + [ + "import with i64-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_l) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6)) + }; + } + ], + + [ + "export i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }, + verify: instance => assert_equals(instance.exports.fn(), -26n) + }; + } + ], + + [ + "i32 mutable WebAssembly.Global import", + function() { + const initial = 102; + const value = new WebAssembly.Global({ "value": "i32", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI32, true); + const fn = builder + .addFunction("fn", kSig_i_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "i64 mutable WebAssembly.Global import", + function() { + const initial = 102n; + const value = new WebAssembly.Global({ "value": "i64", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64, true); + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201n; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "Multiple i64 arguments", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_ll) + .addBody([ + kExprLocalGet, 1, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 2 }, + }; + + return { + buffer, + args: [], + exports, + verify: instance => { + const fn = instance.exports.fn; + assert_equals(fn(1n, 0n), 0n); + assert_equals(fn(1n, 123n), 123n); + assert_equals(fn(1n, -123n), -123n); + assert_equals(fn(1n, "5"), 5n); + assert_throws_js(TypeError, () => fn(1n, 5)); + } + }; + } + ], + + [ + "stray argument", + function() { + return { + buffer: emptyModuleBinary, + args: [{}, {}], + exports: {}, + verify: () => {} + }; + } + ], +]; + +globalThis.instanceTestFactory = instanceTestFactory; diff --git a/testing/web-platform/tests/wasm/jsapi/interface.any.js b/testing/web-platform/tests/wasm/jsapi/interface.any.js new file mode 100644 index 0000000000..8256fc209a --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/interface.any.js @@ -0,0 +1,160 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function test_operations(object, object_name, operations) { + for (const [name, length] of operations) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_true(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, object[name]); + }, `${object_name}.${name}`); + + test(() => { + assert_function_name(object[name], name, `${object_name}.${name}`); + }, `${object_name}.${name}: name`); + + test(() => { + assert_function_length(object[name], length, `${object_name}.${name}`); + }, `${object_name}.${name}: length`); + } +} + +function test_attributes(object, object_name, attributes) { + for (const [name, mutable] of attributes) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + }, `${object_name}.${name}`); + + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_equals(typeof propdesc.get, "function"); + assert_function_name(propdesc.get, "get " + name, `getter for "${name}"`); + assert_function_length(propdesc.get, 0, `getter for "${name}"`); + }, `${object_name}.${name}: getter`); + + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + if (mutable) { + assert_equals(typeof propdesc.set, "function"); + assert_function_name(propdesc.set, "set " + name, `setter for "${name}"`); + assert_function_length(propdesc.set, 1, `setter for "${name}"`); + } else { + assert_equals(typeof propdesc.set, "undefined"); + } + }, `${object_name}.${name}: setter`); + } +} + +test(() => { + const propdesc = Object.getOwnPropertyDescriptor(this, "WebAssembly"); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, this.WebAssembly); +}, "WebAssembly: property descriptor"); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly()); +}, "WebAssembly: calling"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly()); +}, "WebAssembly: constructing"); + +const interfaces = [ + "Module", + "Instance", + "Memory", + "Table", + "Global", + "CompileError", + "LinkError", + "RuntimeError", +]; + +for (const name of interfaces) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(WebAssembly, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, WebAssembly[name]); + }, `WebAssembly.${name}: property descriptor`); + + test(() => { + const interface_object = WebAssembly[name]; + const propdesc = Object.getOwnPropertyDescriptor(interface_object, "prototype"); + assert_equals(typeof propdesc, "object"); + assert_false(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_false(propdesc.configurable, "configurable"); + }, `WebAssembly.${name}: prototype`); + + test(() => { + const interface_object = WebAssembly[name]; + const interface_prototype_object = interface_object.prototype; + const propdesc = Object.getOwnPropertyDescriptor(interface_prototype_object, "constructor"); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, interface_object); + }, `WebAssembly.${name}: prototype.constructor`); +} + +test_operations(WebAssembly, "WebAssembly", [ + ["validate", 1], + ["compile", 1], + ["instantiate", 1], +]); + + +test_operations(WebAssembly.Module, "WebAssembly.Module", [ + ["exports", 1], + ["imports", 1], + ["customSections", 2], +]); + + +test_attributes(WebAssembly.Instance.prototype, "WebAssembly.Instance", [ + ["exports", false], +]); + + +test_operations(WebAssembly.Memory.prototype, "WebAssembly.Memory", [ + ["grow", 1], +]); + +test_attributes(WebAssembly.Memory.prototype, "WebAssembly.Memory", [ + ["buffer", false], +]); + + +test_operations(WebAssembly.Table.prototype, "WebAssembly.Table", [ + ["grow", 1], + ["get", 1], + ["set", 1], +]); + +test_attributes(WebAssembly.Table.prototype, "WebAssembly.Table", [ + ["length", false], +]); + + +test_operations(WebAssembly.Global.prototype, "WebAssembly.Global", [ + ["valueOf", 0], +]); + +test_attributes(WebAssembly.Global.prototype, "WebAssembly.Global", [ + ["value", true], +]); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/assertions.js b/testing/web-platform/tests/wasm/jsapi/memory/assertions.js new file mode 100644 index 0000000000..b539513adc --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/assertions.js @@ -0,0 +1,38 @@ +function assert_ArrayBuffer(actual, { size=0, shared=false, detached=false }, message) { + // https://github.com/WebAssembly/spec/issues/840 + // See https://github.com/whatwg/html/issues/5380 for why not `self.SharedArrayBuffer` + const isShared = !("isView" in actual.constructor); + assert_equals(isShared, shared, `${message}: constructor`); + const sharedString = shared ? "Shared" : ""; + assert_equals(actual.toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: toString()`); + assert_equals(Object.getPrototypeOf(actual).toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: prototype toString()`); + if (detached) { + // https://github.com/tc39/ecma262/issues/678 + let byteLength; + try { + byteLength = actual.byteLength; + } catch (e) { + byteLength = 0; + } + assert_equals(byteLength, 0, `${message}: detached size`); + } else { + assert_equals(actual.byteLength, 0x10000 * size, `${message}: size`); + if (size > 0) { + const array = new Uint8Array(actual); + assert_equals(array[0], 0, `${message}: first element`); + assert_equals(array[array.byteLength - 1], 0, `${message}: last element`); + } + } + assert_equals(Object.isFrozen(actual), shared, "buffer frozen"); + assert_equals(Object.isExtensible(actual), !shared, "buffer extensibility"); +} + +function assert_Memory(memory, { size=0, shared=false }) { + assert_equals(Object.getPrototypeOf(memory), WebAssembly.Memory.prototype, + "prototype"); + assert_true(Object.isExtensible(memory), "extensible"); + + // https://github.com/WebAssembly/spec/issues/840 + assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent"); + assert_ArrayBuffer(memory.buffer, { size, shared }); +} diff --git a/testing/web-platform/tests/wasm/jsapi/memory/buffer.any.js b/testing/web-platform/tests/wasm/jsapi/memory/buffer.any.js new file mode 100644 index 0000000000..fb1d1200b8 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/buffer.any.js @@ -0,0 +1,64 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "buffer"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "buffer"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(memory, {}), buffer); +}, "Stray argument"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const memory2 = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + assert_not_equals(buffer, memory2.buffer, "Need two distinct buffers"); + memory.buffer = memory2.buffer; + assert_equals(memory.buffer, buffer, "Should not change the buffer"); +}, "Setting (sloppy mode)"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const memory2 = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + assert_not_equals(buffer, memory2.buffer, "Need two distinct buffers"); + assert_throws_js(TypeError, () => { + "use strict"; + memory.buffer = memory2.buffer; + }); + assert_equals(memory.buffer, buffer, "Should not change the buffer"); +}, "Setting (strict mode)"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/constructor-shared.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/memory/constructor-shared.tentative.any.js new file mode 100644 index 0000000000..0134b30774 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/constructor-shared.tentative.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 10, "shared": true })); +}, "Shared memory without maximum"); + +test(t => { + const order = []; + + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + + get shared() { + order.push("shared"); + return { + valueOf: t.unreached_func("should not call shared valueOf"), + }; + }, + }); + + assert_array_equals(order, [ + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + "shared", + ]); +}, "Order of evaluation for descriptor (with shared)"); + +test(() => { + const argument = { "initial": 4, "maximum": 10, shared: true }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4, "shared": true }); +}, "Shared memory"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/constructor-types.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/memory/constructor-types.tentative.any.js new file mode 100644 index 0000000000..4653c6686a --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/constructor-types.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const argument = { initial: 5, minimum: 6 }; + assert_throws_js(TypeError, () => new WebAssembly.Memory(argument)); +}, "Initializing with both initial and minimum"); + +test(() => { + const argument = { minimum: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 0 }); + }, "Zero minimum"); + +test(() => { + const argument = { minimum: 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4 }); + }, "Non-zero minimum"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/constructor.any.js b/testing/web-platform/tests/wasm/jsapi/memory/constructor.any.js new file mode 100644 index 0000000000..8f413c9f48 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/constructor.any.js @@ -0,0 +1,139 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + assert_function_name(WebAssembly.Memory, "Memory", "WebAssembly.Memory"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Memory, 1, "WebAssembly.Memory"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory()); +}, "No arguments"); + +test(() => { + const argument = { "initial": 0 }; + assert_throws_js(TypeError, () => WebAssembly.Memory(argument)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Memory(invalidArgument), + `new Memory(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": undefined })); +}, "Undefined initial value in descriptor"); + +const outOfRangeValues = [ + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, +]; + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": value })); + }, `Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 0, "maximum": value })); + }, `Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js(RangeError, () => new WebAssembly.Memory({ "initial": 10, "maximum": 9 })); +}, "Initial value exceeds maximum"); + +test(() => { + const proxy = new Proxy({}, { + has(o, x) { + assert_unreached(`Should not call [[HasProperty]] with ${x}`); + }, + get(o, x) { + // Due to the requirement not to supply both minimum and initial, we need to ignore one of them. + switch (x) { + case "shared": + return false; + case "initial": + case "maximum": + return 0; + default: + return undefined; + } + }, + }); + new WebAssembly.Memory(proxy); +}, "Proxy descriptor"); + +test(() => { + const order = []; + + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + }); + + assert_array_equals(order, [ + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + ]); +}, "Order of evaluation for descriptor"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 0 }); +}, "Zero initial"); + +test(() => { + const argument = { "initial": 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4 }); +}, "Non-zero initial"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument, {}); + assert_Memory(memory, { "size": 0 }); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/grow.any.js b/testing/web-platform/tests/wasm/jsapi/memory/grow.any.js new file mode 100644 index 0000000000..2eafe5761e --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/grow.any.js @@ -0,0 +1,189 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js(TypeError, () => memory.grow()); +}, "Missing arguments"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Memory.prototype.grow; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial"); + +test(() => { + const argument = { "initial": { valueOf() { return 0 } } }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow({ valueOf() { return 2 } }); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial with valueOf"); + +test(() => { + const argument = { "initial": 3 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 3 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 3); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 5 }, "New buffer after growing"); +}, "Non-zero initial"); + +test(() => { + const argument = { "initial": 0, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial with respected maximum"); + +test(() => { + const argument = { "initial": 0, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(1); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing once"); + assert_ArrayBuffer(newMemory, { "size": 1 }, "New buffer after growing once"); + + const result2 = memory.grow(1); + assert_equals(result2, 1); + + const newestMemory = memory.buffer; + assert_not_equals(newMemory, newestMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "New buffer after growing twice"); + assert_ArrayBuffer(newMemory, { "detached": true }, "New buffer after growing twice"); + assert_ArrayBuffer(newestMemory, { "size": 2 }, "Newest buffer after growing twice"); +}, "Zero initial with respected maximum grown twice"); + +test(() => { + const argument = { "initial": 1, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 1 }, "Buffer before growing"); + + assert_throws_js(RangeError, () => memory.grow(2)); + assert_equals(memory.buffer, oldMemory); + assert_ArrayBuffer(memory.buffer, { "size": 1 }, "Buffer before trying to grow"); +}, "Zero initial growing too much"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js(TypeError, () => memory.grow(value)); + }, `Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2, {}); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Stray argument"); + +test(() => { + const argument = { "initial": 1, "maximum": 2, "shared": true }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Buffer before growing"); + + const result = memory.grow(1); + assert_equals(result, 1); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2, "shared": true }, "New buffer after growing"); + + // The old and new buffers must have the same value for the + // [[ArrayBufferData]] internal slot. + const oldArray = new Uint8Array(oldMemory); + const newArray = new Uint8Array(newMemory); + assert_equals(oldArray[0], 0, "old first element"); + assert_equals(newArray[0], 0, "new first element"); + oldArray[0] = 1; + assert_equals(oldArray[0], 1, "old first element"); + assert_equals(newArray[0], 1, "new first element"); + +}, "Growing shared memory does not detach old buffer"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/toString.any.js b/testing/web-platform/tests/wasm/jsapi/memory/toString.any.js new file mode 100644 index 0000000000..f4059f7657 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_class_string(memory, "WebAssembly.Memory"); +}, "Object.prototype.toString on an Memory"); + +test(() => { + assert_own_property(WebAssembly.Memory.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Memory", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/memory/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/memory/type.tentative.any.js new file mode 100644 index 0000000000..3f6531f596 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/memory/type.tentative.any.js @@ -0,0 +1,37 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const memory = new WebAssembly.Memory(argument); + const memorytype = memory.type() + + assert_equals(memorytype.minimum, argument.minimum); + assert_equals(memorytype.maximum, argument.maximum); + if (argument.shared !== undefined) { + assert_equals(memorytype.shared, argument.shared); + } +} + +test(() => { + assert_type({ "minimum": 0 }); +}, "Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5 }); +}, "Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0 }); +}, "Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5 }); +}, "None-zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 10, "shared": false}); +}, "non-shared memory"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 10, "shared": true}); +}, "shared memory"); diff --git a/testing/web-platform/tests/wasm/jsapi/module/constructor.any.js b/testing/web-platform/tests/wasm/jsapi/module/constructor.any.js new file mode 100644 index 0000000000..95604aabe4 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/constructor.any.js @@ -0,0 +1,69 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_function_name(WebAssembly.Module, "Module", "WebAssembly.Module"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Module, 1, "WebAssembly.Module"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Module()); +}, "No arguments"); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module(emptyModuleBinary)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "test", + Symbol(), + 7, + NaN, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => new WebAssembly.Module(argument), + `new Module(${format_value(argument)})`); + } +}, "Invalid arguments"); + +test(() => { + const buffer = new Uint8Array(); + assert_throws_js(WebAssembly.CompileError, () => new WebAssembly.Module(buffer)); +}, "Empty buffer"); + +test(() => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + assert_throws_js(WebAssembly.CompileError, () => new WebAssembly.Module(buffer)); +}, "Invalid code"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype); +}, "Prototype"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_true(Object.isExtensible(module)); +}, "Extensibility"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary, {}); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/module/customSections.any.js b/testing/web-platform/tests/wasm/jsapi/module/customSections.any.js new file mode 100644 index 0000000000..96958316e0 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/customSections.any.js @@ -0,0 +1,140 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_ArrayBuffer(buffer, expected) { + assert_equals(Object.getPrototypeOf(buffer), ArrayBuffer.prototype, "Prototype"); + assert_true(Object.isExtensible(buffer), "isExtensible"); + assert_array_equals(new Uint8Array(buffer), expected); +} + +function assert_sections(sections, expected) { + assert_true(Array.isArray(sections), "Should be array"); + assert_equals(Object.getPrototypeOf(sections), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(sections), "isExtensible"); + + assert_equals(sections.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ArrayBuffer(sections[i], expected[i]); + } +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.customSections()); + const module = new WebAssembly.Module(emptyModuleBinary); + assert_throws_js(TypeError, () => WebAssembly.Module.customSections(module)); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.customSections(argument, ""), + `customSections(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.customSections; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_sections(fn.call(thisValue, module, ""), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_sections(WebAssembly.Module.customSections(module, ""), []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.customSections(module, ""), + WebAssembly.Module.customSections(module, "")); +}, "Empty module: array caching"); + +test(() => { + const bytes1 = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + const bytes2 = [74, 83, 65, 80, 73]; + + const builder = new WasmModuleBuilder(); + builder.addCustomSection("name", bytes1); + builder.addCustomSection("name", bytes2); + builder.addCustomSection("foo", bytes1); + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, "name"), [ + bytes1, + bytes2, + ]) + + assert_sections(WebAssembly.Module.customSections(module, "foo"), [ + bytes1, + ]) + + assert_sections(WebAssembly.Module.customSections(module, ""), []) + assert_sections(WebAssembly.Module.customSections(module, "\0"), []) + assert_sections(WebAssembly.Module.customSections(module, "name\0"), []) + assert_sections(WebAssembly.Module.customSections(module, "foo\0"), []) +}, "Custom sections"); + +test(() => { + const bytes = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + const name = "yee\uD801\uDC37eey" + + const builder = new WasmModuleBuilder(); + builder.addCustomSection(name, bytes); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, name), [ + bytes, + ]); + assert_sections(WebAssembly.Module.customSections(module, "yee\uFFFDeey"), []); + assert_sections(WebAssembly.Module.customSections(module, "yee\uFFFD\uFFFDeey"), []); +}, "Custom sections with surrogate pairs"); + +test(() => { + const bytes = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + + const builder = new WasmModuleBuilder(); + builder.addCustomSection("na\uFFFDme", bytes); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, "name"), []); + assert_sections(WebAssembly.Module.customSections(module, "na\uFFFDme"), [ + bytes, + ]); + assert_sections(WebAssembly.Module.customSections(module, "na\uDC01me"), []); +}, "Custom sections with U+FFFD"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_sections(WebAssembly.Module.customSections(module, "", {}), []); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/module/exports.any.js b/testing/web-platform/tests/wasm/jsapi/module/exports.any.js new file mode 100644 index 0000000000..0c32e984a2 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/exports.any.js @@ -0,0 +1,185 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +function assert_ModuleExportDescriptor(export_, expected) { + assert_equals(Object.getPrototypeOf(export_), Object.prototype, "Prototype"); + assert_true(Object.isExtensible(export_), "isExtensible"); + + const name = Object.getOwnPropertyDescriptor(export_, "name"); + assert_true(name.writable, "name: writable"); + assert_true(name.enumerable, "name: enumerable"); + assert_true(name.configurable, "name: configurable"); + assert_equals(name.value, expected.name); + + const kind = Object.getOwnPropertyDescriptor(export_, "kind"); + assert_true(kind.writable, "kind: writable"); + assert_true(kind.enumerable, "kind: enumerable"); + assert_true(kind.configurable, "kind: configurable"); + assert_equals(kind.value, expected.kind); +} + +function assert_exports(exports, expected) { + assert_true(Array.isArray(exports), "Should be array"); + assert_equals(Object.getPrototypeOf(exports), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(exports), "isExtensible"); + + assert_equals(exports.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ModuleExportDescriptor(exports[i], expected[i]); + } +} + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.exports()); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.exports(argument), + `exports(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.exports; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_array_equals(fn.call(thisValue, module), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module); + assert_true(Array.isArray(exports)); +}, "Return type"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module); + assert_exports(exports, []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.exports(module), WebAssembly.Module.exports(module)); +}, "Empty module: array caching"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + + builder.addGlobal(kWasmI32, true) + .exportAs("global") + .init = wasmI32Const(7); + builder.addGlobal(kWasmF64, true) + .exportAs("global2") + .init = wasmF64Const(1.2); + + builder.addMemory(0, 256, true); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "function", "name": "fn" }, + { "kind": "function", "name": "fn2" }, + { "kind": "table", "name": "table" }, + { "kind": "global", "name": "global" }, + { "kind": "global", "name": "global2" }, + { "kind": "memory", "name": "memory" }, + ]; + assert_exports(exports, expected); +}, "exports"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("", kSig_v_v) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "function", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: function"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.setTableBounds(1); + builder.addExportOfKind("", kExternalTable, 0); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "table", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: table"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addGlobal(kWasmI32, true) + .exportAs("") + .init = wasmI32Const(7); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "global", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: global"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module, {}); + assert_exports(exports, []); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/module/imports.any.js b/testing/web-platform/tests/wasm/jsapi/module/imports.any.js new file mode 100644 index 0000000000..2ab1725359 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/imports.any.js @@ -0,0 +1,185 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_ModuleImportDescriptor(import_, expected) { + assert_equals(Object.getPrototypeOf(import_), Object.prototype, "Prototype"); + assert_true(Object.isExtensible(import_), "isExtensible"); + + const module = Object.getOwnPropertyDescriptor(import_, "module"); + assert_true(module.writable, "module: writable"); + assert_true(module.enumerable, "module: enumerable"); + assert_true(module.configurable, "module: configurable"); + assert_equals(module.value, expected.module); + + const name = Object.getOwnPropertyDescriptor(import_, "name"); + assert_true(name.writable, "name: writable"); + assert_true(name.enumerable, "name: enumerable"); + assert_true(name.configurable, "name: configurable"); + assert_equals(name.value, expected.name); + + const kind = Object.getOwnPropertyDescriptor(import_, "kind"); + assert_true(kind.writable, "kind: writable"); + assert_true(kind.enumerable, "kind: enumerable"); + assert_true(kind.configurable, "kind: configurable"); + assert_equals(kind.value, expected.kind); +} + +function assert_imports(imports, expected) { + assert_true(Array.isArray(imports), "Should be array"); + assert_equals(Object.getPrototypeOf(imports), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(imports), "isExtensible"); + + assert_equals(imports.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ModuleImportDescriptor(imports[i], expected[i]); + } +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.imports()); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.imports(argument), + `imports(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.imports; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_array_equals(fn.call(thisValue, module), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module); + assert_true(Array.isArray(imports)); +}, "Return type"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module); + assert_imports(imports, []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.imports(module), WebAssembly.Module.imports(module)); +}, "Empty module: array caching"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("module", "fn", kSig_v_v); + builder.addImportedGlobal("module", "global", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedTable("module", "table", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "module", "kind": "function", "name": "fn" }, + { "module": "module", "kind": "global", "name": "global" }, + { "module": "module", "kind": "memory", "name": "memory" }, + { "module": "module", "kind": "table", "name": "table" }, + ]; + assert_imports(imports, expected); +}, "imports"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "fn", kSig_v_v); + builder.addImportedGlobal("", "global", kWasmI32); + builder.addImportedMemory("", "memory", 0, 128); + builder.addImportedTable("", "table", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "", "kind": "function", "name": "fn" }, + { "module": "", "kind": "global", "name": "global" }, + { "module": "", "kind": "memory", "name": "memory" }, + { "module": "", "kind": "table", "name": "table" }, + ]; + assert_imports(imports, expected); +}, "imports with empty module name"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("a", "", kSig_v_v); + builder.addImportedGlobal("b", "", kWasmI32); + builder.addImportedMemory("c", "", 0, 128); + builder.addImportedTable("d", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "a", "kind": "function", "name": "" }, + { "module": "b", "kind": "global", "name": "" }, + { "module": "c", "kind": "memory", "name": "" }, + { "module": "d", "kind": "table", "name": "" }, + ]; + assert_imports(imports, expected); +}, "imports with empty names"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "", kSig_v_v); + builder.addImportedGlobal("", "", kWasmI32); + builder.addImportedMemory("", "", 0, 128); + builder.addImportedTable("", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "", "kind": "function", "name": "" }, + { "module": "", "kind": "global", "name": "" }, + { "module": "", "kind": "memory", "name": "" }, + { "module": "", "kind": "table", "name": "" }, + ]; + assert_imports(imports, expected); +}, "imports with empty module names and names"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module, {}); + assert_imports(imports, []); +}, "Stray argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/module/moduleSource.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/module/moduleSource.tentative.any.js new file mode 100644 index 0000000000..a3d09d55d6 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/moduleSource.tentative.any.js @@ -0,0 +1,41 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_equals(typeof AbstractModuleSource, "undefined"); + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + assert_equals(AbstractModuleSource.name, "AbstractModuleSource"); + assert_not_equals(AbstractModuleSource, Function); +}, "AbstractModuleSource intrinsic"); + +test(() => { + const AbstractModuleSourceProto = Object.getPrototypeOf(WebAssembly.Module.prototype); + assert_not_equals(AbstractModuleSourceProto, Object); + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + assert_equals(AbstractModuleSource.prototype, AbstractModuleSourceProto); +}, "AbstractModuleSourceProto intrinsic"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + builder.addMemory(0, 256, true); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + + const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); + const toStringTag = Object.getOwnPropertyDescriptor(AbstractModuleSource.prototype, Symbol.toStringTag).get; + + assert_equals(toStringTag.call(module), "WebAssembly.Module"); + assert_throws_js(TypeError, () => toStringTag.call({})); +}, "AbstractModuleSourceProto toStringTag brand check");
\ No newline at end of file diff --git a/testing/web-platform/tests/wasm/jsapi/module/toString.any.js b/testing/web-platform/tests/wasm/jsapi/module/toString.any.js new file mode 100644 index 0000000000..10c707979d --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/module/toString.any.js @@ -0,0 +1,18 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const emptyModuleBinary = new WasmModuleBuilder().toBuffer(); + const module = new WebAssembly.Module(emptyModuleBinary); + assert_class_string(module, "WebAssembly.Module"); +}, "Object.prototype.toString on an Module"); + +test(() => { + assert_own_property(WebAssembly.Module.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Module.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Module", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/proto-from-ctor-realm.html b/testing/web-platform/tests/wasm/jsapi/proto-from-ctor-realm.html new file mode 100644 index 0000000000..45405b5290 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/proto-from-ctor-realm.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>WebAssembly JS API: Default [[Prototype]] value is from NewTarget's Realm</title> +<link rel="help" href="https://webidl.spec.whatwg.org/#internally-create-a-new-object-implementing-the-interface"> +<link rel="help" href="https://tc39.es/ecma262/#sec-nativeerror"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="wasm-module-builder.js"></script> +<body> +<iframe id="constructor-iframe" hidden></iframe> +<iframe id="new-target-iframe" hidden></iframe> +<iframe id="other-iframe" hidden></iframe> +<script> +"use strict"; + +const constructorRealm = document.querySelector("#constructor-iframe").contentWindow; +const newTargetRealm = document.querySelector("#new-target-iframe").contentWindow; +const otherRealm = document.querySelector("#other-iframe").contentWindow; + +const emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +const interfaces = [ + ["Module", emptyModuleBinary], + ["Instance", new WebAssembly.Module(emptyModuleBinary)], + ["Memory", {initial: 0}], + ["Table", {element: "anyfunc", initial: 0}], + ["Global", {value: "i32"}], + + // See step 2 of https://tc39.es/ecma262/#sec-nativeerror + ["CompileError"], + ["LinkError"], + ["RuntimeError"], +]; + +const primitives = [ + undefined, + null, + false, + true, + 0, + -1, + "", + "str", + Symbol(), +]; + +const getNewTargets = function* (realm) { + for (const primitive of primitives) { + const newTarget = new realm.Function(); + newTarget.prototype = primitive; + yield [newTarget, "cross-realm NewTarget with `" + format_value(primitive) + "` prototype"]; + } + + // GetFunctionRealm (https://tc39.es/ecma262/#sec-getfunctionrealm) coverage: + const bindOther = otherRealm.Function.prototype.bind; + const ProxyOther = otherRealm.Proxy; + + const bound = new realm.Function(); + bound.prototype = undefined; + yield [bindOther.call(bound), "bound cross-realm NewTarget with `undefined` prototype"]; + + const boundBound = new realm.Function(); + boundBound.prototype = null; + yield [bindOther.call(bindOther.call(boundBound)), "bound bound cross-realm NewTarget with `null` prototype"]; + + const boundProxy = new realm.Function(); + boundProxy.prototype = false; + yield [bindOther.call(new ProxyOther(boundProxy, {})), "bound Proxy of cross-realm NewTarget with `false` prototype"]; + + const proxy = new realm.Function(); + proxy.prototype = true; + yield [new ProxyOther(proxy, {}), "Proxy of cross-realm NewTarget with `true` prototype"]; + + const proxyProxy = new realm.Function(); + proxyProxy.prototype = -0; + yield [new ProxyOther(new ProxyOther(proxyProxy, {}), {}), "Proxy of Proxy of cross-realm NewTarget with `-0` prototype"]; + + const proxyBound = new realm.Function(); + proxyBound.prototype = NaN; + yield [new ProxyOther(bindOther.call(proxyBound), {}), "Proxy of bound cross-realm NewTarget with `NaN` prototype"]; +}; + +for (const [interfaceName, constructorArg] of interfaces) { + for (const [newTarget, testDescription] of getNewTargets(newTargetRealm)) { + test(() => { + const Constructor = constructorRealm.WebAssembly[interfaceName]; + const object = Reflect.construct(Constructor, [constructorArg], newTarget); + + const NewTargetConstructor = newTargetRealm.WebAssembly[interfaceName]; + assert_true(object instanceof NewTargetConstructor); + assert_equals(Object.getPrototypeOf(object), NewTargetConstructor.prototype); + }, `WebAssembly.${interfaceName}: ${testDescription}`); + } +} +</script> +</body> diff --git a/testing/web-platform/tests/wasm/jsapi/prototypes.any.js b/testing/web-platform/tests/wasm/jsapi/prototypes.any.js new file mode 100644 index 0000000000..2316f7d9b4 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/prototypes.any.js @@ -0,0 +1,43 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + class _Module extends WebAssembly.Module {} + let module = new _Module(emptyModuleBinary); + assert_true(module instanceof _Module, "_Module instanceof _Module"); + assert_true(module instanceof WebAssembly.Module, "_Module instanceof WebAssembly.Module"); +}, "_Module"); + +test(() => { + class _Instance extends WebAssembly.Instance {} + let instance = new _Instance(new WebAssembly.Module(emptyModuleBinary)); + assert_true(instance instanceof _Instance, "_Instance instanceof _Instance"); + assert_true(instance instanceof WebAssembly.Instance, "_Instance instanceof WebAssembly.Instance"); +}, "_Instance"); + +test(() => { + class _Memory extends WebAssembly.Memory {} + let memory = new _Memory({initial: 0, maximum: 1}); + assert_true(memory instanceof _Memory, "_Memory instanceof _Memory"); + assert_true(memory instanceof WebAssembly.Memory, "_Memory instanceof WebAssembly.Memory"); +}, "_Memory"); + +test(() => { + class _Table extends WebAssembly.Table {} + let table = new _Table({initial: 0, element: "anyfunc"}); + assert_true(table instanceof _Table, "_Table instanceof _Table"); + assert_true(table instanceof WebAssembly.Table, "_Table instanceof WebAssembly.Table"); +}, "_Table"); + +test(() => { + class _Global extends WebAssembly.Global {} + let global = new _Global({value: "i32", mutable: false}, 0); + assert_true(global instanceof _Global, "_Global instanceof _Global"); + assert_true(global instanceof WebAssembly.Global, "_Global instanceof WebAssembly.Global"); +}, "_Global"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/assertions.js b/testing/web-platform/tests/wasm/jsapi/table/assertions.js new file mode 100644 index 0000000000..19cc5c3b92 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/assertions.js @@ -0,0 +1,24 @@ +function assert_equal_to_array(table, expected, message) { + assert_equals(table.length, expected.length, `${message}: length`); + // The argument check in get() happens before the range check, and negative numbers + // are illegal, hence will throw TypeError per spec. + assert_throws_js(TypeError, () => table.get(-1), `${message}: table.get(-1)`); + for (let i = 0; i < expected.length; ++i) { + assert_equals(table.get(i), expected[i], `${message}: table.get(${i} of ${expected.length})`); + } + assert_throws_js(RangeError, () => table.get(expected.length), + `${message}: table.get(${expected.length} of ${expected.length})`); + assert_throws_js(RangeError, () => table.get(expected.length + 1), + `${message}: table.get(${expected.length + 1} of ${expected.length})`); +} + +function assert_Table(actual, expected) { + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype, + "prototype"); + assert_true(Object.isExtensible(actual), "extensible"); + + assert_equals(actual.length, expected.length, "length"); + for (let i = 0; i < expected.length; ++i) { + assert_equals(actual.get(i), null, `actual.get(${i})`); + } +} diff --git a/testing/web-platform/tests/wasm/jsapi/table/constructor-types.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/table/constructor-types.tentative.any.js new file mode 100644 index 0000000000..b4015bdc6f --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/constructor-types.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/table/assertions.js + +test(() => { + const argument = { "element": "anyfunc", "initial": 0, "minimum": 0 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument)); +}, "Initializing with both initial and minimum"); + +test(() => { + const argument = { "element": "anyfunc", "minimum": 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 0 }); +}, "Zero minimum"); + +test(() => { + const argument = { "element": "anyfunc", "minimum": 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 5 }); +}, "Non-zero minimum"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/constructor.any.js b/testing/web-platform/tests/wasm/jsapi/table/constructor.any.js new file mode 100644 index 0000000000..51b1070d41 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/constructor.any.js @@ -0,0 +1,208 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/table/assertions.js + +test(() => { + assert_function_name(WebAssembly.Table, "Table", "WebAssembly.Table"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Table, 1, "WebAssembly.Table"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table()); +}, "No arguments"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + assert_throws_js(TypeError, () => WebAssembly.Table(argument)); +}, "Calling"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({})); +}, "Empty descriptor"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Table(invalidArgument), + `new Table(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": undefined })); +}, "Undefined initial value in descriptor"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": undefined, "initial": 0 })); +}, "Undefined element value in descriptor"); + +const outOfRangeValues = [ + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, +]; + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": value })); + }, `Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": 0, "maximum": value })); + }, `Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js(RangeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": 10, "maximum": 9 })); +}, "Initial value exceeds maximum"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 0 }); +}, "Basic (zero)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 5 }); +}, "Basic (non-zero)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument, null, {}); + assert_Table(table, { "length": 0 }); +}, "Stray argument"); + +test(() => { + const proxy = new Proxy({}, { + has(o, x) { + assert_unreached(`Should not call [[HasProperty]] with ${x}`); + }, + get(o, x) { + switch (x) { + case "element": + return "anyfunc"; + case "initial": + case "maximum": + return 0; + default: + return undefined; + } + }, + }); + const table = new WebAssembly.Table(proxy); + assert_Table(table, { "length": 0 }); +}, "Proxy descriptor"); + +test(() => { + const table = new WebAssembly.Table({ + "element": { + toString() { return "anyfunc"; }, + }, + "initial": 1, + }); + assert_Table(table, { "length": 1 }); +}, "Type conversion for descriptor.element"); + +test(() => { + const order = []; + + new WebAssembly.Table({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + + get element() { + order.push("element"); + return { + toString() { + order.push("element toString"); + return "anyfunc"; + }, + }; + }, + }); + + assert_array_equals(order, [ + "element", + "element toString", + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + ]); +}, "Order of evaluation for descriptor"); + +test(() => { + const testObject = {}; + const argument = { "element": "externref", "initial": 3 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.length, 3); + assert_equals(table.get(0), testObject); + assert_equals(table.get(1), testObject); + assert_equals(table.get(2), testObject); +}, "initialize externref table with default value"); + +test(() => { + const argument = { "element": "i32", "initial": 3 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument)); +}, "initialize table with a wrong element value"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer(); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + const argument = { "element": "anyfunc", "initial": 3 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.length, 3); + assert_equals(table.get(0), fn); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "initialize anyfunc table with default value"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 3 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, {})); + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, "cannot be used as a wasm function")); + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, 37)); +}, "initialize anyfunc table with a bad default value"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/get-set.any.js b/testing/web-platform/tests/wasm/jsapi/table/get-set.any.js new file mode 100644 index 0000000000..abe6fecac9 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/get-set.any.js @@ -0,0 +1,263 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=assertions.js + +let functions = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + functions = instance.exports; +}); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.get()); +}, "Missing arguments: get"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.get; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding: get"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.set()); +}, "Missing arguments: set"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.set; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument, null), `this=${format_value(thisValue)}`); + } +}, "Branding: set"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn, fn2} = functions; + + assert_equals(table.set(0, fn), undefined, "set() returns undefined."); + table.set(2, fn2); + table.set(4, fn); + + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + + table.set(0, null); + assert_equal_to_array(table, [null, null, fn2, null, fn]); +}, "Basic"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn, fn2} = functions; + + table.set(0, fn); + table.set(2, fn2); + table.set(4, fn); + + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + + table.grow(4); + + assert_equal_to_array(table, [fn, null, fn2, null, fn, null, null, null, null]); +}, "Growing"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn} = functions; + + // -1 is the wrong type hence the type check on entry gets this + // before the range check does. + assert_throws_js(TypeError, () => table.set(-1, fn)); + assert_throws_js(RangeError, () => table.set(5, fn)); + assert_equal_to_array(table, [null, null, null, null, null]); +}, "Setting out-of-bounds"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const invalidArguments = [ + undefined, + true, + false, + "test", + Symbol(), + 7, + NaN, + {}, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => table.set(0, argument), + `set(${format_value(argument)})`); + } + assert_equal_to_array(table, [null]); +}, "Setting non-function"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const fn = function() {}; + assert_throws_js(TypeError, () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm function"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const fn = () => {}; + assert_throws_js(TypeError, () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm arrow function"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.get(value)); + }, `Getting out-of-range argument: ${format_value(value)}`); + + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.set(value, null)); + }, `Setting out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + let called = 0; + const value = { + valueOf() { + called++; + return 0; + }, + }; + assert_throws_js(TypeError, () => table.set(value, {})); + assert_equals(called, 1); +}, "Order of argument conversion"); + +test(() => { + const {fn} = functions; + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + + assert_equals(table.get(0, {}), null); + assert_equals(table.set(0, fn, {}), undefined); +}, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer(); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument, fn); + + assert_equals(table.get(0), fn); + table.set(0); + assert_equals(table.get(0), null); + + table.set(0, fn); + assert_equals(table.get(0), fn); + + assert_throws_js(TypeError, () => table.set(0, {})); + assert_throws_js(TypeError, () => table.set(0, 37)); +}, "Arguments for anyfunc table set"); + +test(() => { + const testObject = {}; + const argument = { "element": "externref", "initial": 1 }; + const table = new WebAssembly.Table(argument, testObject); + + assert_equals(table.get(0), testObject); + table.set(0); + assert_equals(table.get(0), undefined); + + table.set(0, testObject); + assert_equals(table.get(0), testObject); + + table.set(0, 37); + assert_equals(table.get(0), 37); +}, "Arguments for externref table set"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/grow.any.js b/testing/web-platform/tests/wasm/jsapi/table/grow.any.js new file mode 100644 index 0000000000..4038f1e649 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/grow.any.js @@ -0,0 +1,126 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=assertions.js + +function nulls(n) { + return Array(n).fill(null); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow()); +}, "Missing arguments"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.grow; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + + const result = table.grow(3); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Basic"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 3, "maximum": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(3), "before"); + + const result = table.grow(2); + assert_equals(result, 3); + assert_equal_to_array(table, nulls(5), "after"); +}, "Reached maximum"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2, "maximum": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(2), "before"); + + assert_throws_js(RangeError, () => table.grow(4)); + assert_equal_to_array(table, nulls(2), "after"); +}, "Exceeded maximum"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(value)); + }, `Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + + const result = table.grow(3, null, {}); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer() + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + const result = table.grow(2, fn); + assert_equals(result, 1); + assert_equals(table.get(0), null); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "Grow with exported-function argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(2, {})); +}, "Grow with non-function argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(2, () => true)); +}, "Grow with JS-function argument"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/length.any.js b/testing/web-platform/tests/wasm/jsapi/table/length.any.js new file mode 100644 index 0000000000..0e6de3f83e --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/length.any.js @@ -0,0 +1,60 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, "length"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, "length"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(table, {}), 2); +}, "Stray argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + table.length = 4; + assert_equals(table.length, 2, "Should not change the length"); +}, "Setting (sloppy mode)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + assert_throws_js(TypeError, () => { + "use strict"; + table.length = 4; + }); + assert_equals(table.length, 2, "Should not change the length"); +}, "Setting (strict mode)"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/toString.any.js b/testing/web-platform/tests/wasm/jsapi/table/toString.any.js new file mode 100644 index 0000000000..b5fb25b9c1 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument); + assert_class_string(table, "WebAssembly.Table"); +}, "Object.prototype.toString on an Table"); + +test(() => { + assert_own_property(WebAssembly.Table.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Table", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/table/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/table/type.tentative.any.js new file mode 100644 index 0000000000..ef9bbc5aa4 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/table/type.tentative.any.js @@ -0,0 +1,26 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const mytable = new WebAssembly.Table(argument); + const tabletype = mytable.type() + assert_equals(tabletype.minimum, argument.minimum); + assert_equals(tabletype.maximum, argument.maximum); + assert_equals(tabletype.element, argument.element); +} + +test(() => { + assert_type({ "minimum": 0, "element": "funcref"}); +}, "Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5, "element": "funcref" }); +}, "Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0, "element": "funcref" }); +}, "Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5, "element": "funcref" }); +}, "Non-zero maximum"); diff --git a/testing/web-platform/tests/wasm/jsapi/tag/constructor.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/tag/constructor.tentative.any.js new file mode 100644 index 0000000000..54edf8c8f5 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/tag/constructor.tentative.any.js @@ -0,0 +1,49 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +test(() => { + assert_function_name(WebAssembly.Tag, "Tag", "WebAssembly.Tag"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Tag, 1, "WebAssembly.Tag"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Tag()); +}, "No arguments"); + +test(() => { + const argument = { parameters: [] }; + assert_throws_js(TypeError, () => WebAssembly.Tag(argument)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js( + TypeError, + () => new WebAssembly.Tag(invalidArgument), + `new Tag(${format_value(invalidArgument)})` + ); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { parameters: [value] }; + assert_throws_js(TypeError, () => new WebAssembly.Tag(argument)); + } +}, "Invalid type parameter"); diff --git a/testing/web-platform/tests/wasm/jsapi/tag/toString.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/tag/toString.tentative.any.js new file mode 100644 index 0000000000..76fff0feef --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/tag/toString.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm + +test(() => { + const argument = { parameters: [] }; + const tag = new WebAssembly.Tag(argument); + assert_class_string(tag, "WebAssembly.Tag"); +}, "Object.prototype.toString on a Tag"); + +test(() => { + assert_own_property(WebAssembly.Tag.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor( + WebAssembly.Tag.prototype, + Symbol.toStringTag + ); + assert_equals(propDesc.value, "WebAssembly.Tag", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/testing/web-platform/tests/wasm/jsapi/tag/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/tag/type.tentative.any.js new file mode 100644 index 0000000000..58c96078bf --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/tag/type.tentative.any.js @@ -0,0 +1,21 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const tag = new WebAssembly.Tag(argument); + const tagtype = tag.type(); + + assert_array_equals(tagtype.parameters, argument.parameters); +} + +test(() => { + assert_type({ parameters: [] }); +}, "[]"); + +test(() => { + assert_type({ parameters: ["i32", "i64"] }); +}, "[i32 i64]"); + +test(() => { + assert_type({ parameters: ["i32", "i64", "f32", "f64"] }); +}, "[i32 i64 f32 f64]"); diff --git a/testing/web-platform/tests/wasm/jsapi/wasm-module-builder.js b/testing/web-platform/tests/wasm/jsapi/wasm-module-builder.js new file mode 100644 index 0000000000..1d8db0a6e6 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/wasm-module-builder.js @@ -0,0 +1,1531 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Used for encoding f32 and double constants to bits. +let byte_view = new Uint8Array(8); +let data_view = new DataView(byte_view.buffer); + +// The bytes function receives one of +// - several arguments, each of which is either a number or a string of length +// 1; if it's a string, the charcode of the contained character is used. +// - a single array argument containing the actual arguments +// - a single string; the returned buffer will contain the char codes of all +// contained characters. +function bytes(...input) { + if (input.length == 1 && typeof input[0] == 'array') input = input[0]; + if (input.length == 1 && typeof input[0] == 'string') { + let len = input[0].length; + let view = new Uint8Array(len); + for (let i = 0; i < len; i++) view[i] = input[0].charCodeAt(i); + return view.buffer; + } + let view = new Uint8Array(input.length); + for (let i = 0; i < input.length; i++) { + let val = input[i]; + if (typeof val == 'string') { + assertEquals(1, val.length, 'string inputs must have length 1'); + val = val.charCodeAt(0); + } + view[i] = val | 0; + } + return view.buffer; +} + +// Header declaration constants +var kWasmH0 = 0; +var kWasmH1 = 0x61; +var kWasmH2 = 0x73; +var kWasmH3 = 0x6d; + +var kWasmV0 = 0x1; +var kWasmV1 = 0; +var kWasmV2 = 0; +var kWasmV3 = 0; + +var kHeaderSize = 8; +var kPageSize = 65536; +var kSpecMaxPages = 65535; +var kMaxVarInt32Size = 5; +var kMaxVarInt64Size = 10; + +let kDeclNoLocals = 0; + +// Section declaration constants +let kUnknownSectionCode = 0; +let kTypeSectionCode = 1; // Function signature declarations +let kImportSectionCode = 2; // Import declarations +let kFunctionSectionCode = 3; // Function declarations +let kTableSectionCode = 4; // Indirect function table and other tables +let kMemorySectionCode = 5; // Memory attributes +let kGlobalSectionCode = 6; // Global declarations +let kExportSectionCode = 7; // Exports +let kStartSectionCode = 8; // Start function declaration +let kElementSectionCode = 9; // Elements section +let kCodeSectionCode = 10; // Function code +let kDataSectionCode = 11; // Data segments +let kDataCountSectionCode = 12; // Data segment count (between Element & Code) +let kTagSectionCode = 13; // Tag section (between Memory & Global) + +// Name section types +let kModuleNameCode = 0; +let kFunctionNamesCode = 1; +let kLocalNamesCode = 2; + +let kWasmFunctionTypeForm = 0x60; +let kWasmAnyFunctionTypeForm = 0x70; +let kWasmStructTypeForm = 0x5f; +let kWasmArrayTypeForm = 0x5e; +let kWasmSubtypeForm = 0x50; +let kWasmSubtypeFinalForm = 0x4f; +let kWasmRecursiveTypeGroupForm = 0x4e; + +let kNoSuperType = 0xFFFFFFFF; + +let kHasMaximumFlag = 1; +let kSharedHasMaximumFlag = 3; + +// Segment flags +let kActiveNoIndex = 0; +let kPassive = 1; +let kActiveWithIndex = 2; +let kPassiveWithElements = 5; + +// Function declaration flags +let kDeclFunctionName = 0x01; +let kDeclFunctionImport = 0x02; +let kDeclFunctionLocals = 0x04; +let kDeclFunctionExport = 0x08; + +// Local types +let kWasmStmt = 0x40; +let kWasmI32 = 0x7f; +let kWasmI64 = 0x7e; +let kWasmF32 = 0x7d; +let kWasmF64 = 0x7c; +let kWasmS128 = 0x7b; + +// These are defined as negative integers to distinguish them from positive type +// indices. +let kWasmNullFuncRef = -0x0d; +let kWasmNullExternRef = -0x0e; +let kWasmNullRef = -0x0f; +let kWasmFuncRef = -0x10; +let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec +let kWasmExternRef = -0x11; +let kWasmAnyRef = -0x12; +let kWasmEqRef = -0x13; +let kWasmI31Ref = -0x14; +let kWasmStructRef = -0x15; +let kWasmArrayRef = -0x16; + +// Use the positive-byte versions inside function bodies. +let kLeb128Mask = 0x7f; +let kFuncRefCode = kWasmFuncRef & kLeb128Mask; +let kAnyFuncCode = kFuncRefCode; // Alias named as in the JS API spec +let kExternRefCode = kWasmExternRef & kLeb128Mask; +let kAnyRefCode = kWasmAnyRef & kLeb128Mask; +let kEqRefCode = kWasmEqRef & kLeb128Mask; +let kI31RefCode = kWasmI31Ref & kLeb128Mask; +let kNullExternRefCode = kWasmNullExternRef & kLeb128Mask; +let kNullFuncRefCode = kWasmNullFuncRef & kLeb128Mask; +let kStructRefCode = kWasmStructRef & kLeb128Mask; +let kArrayRefCode = kWasmArrayRef & kLeb128Mask; +let kNullRefCode = kWasmNullRef & kLeb128Mask; + +let kWasmRefNull = 0x63; +let kWasmRef = 0x64; +function wasmRefNullType(heap_type) { + return {opcode: kWasmRefNull, heap_type: heap_type}; +} +function wasmRefType(heap_type) { + return {opcode: kWasmRef, heap_type: heap_type}; +} + +let kExternalFunction = 0; +let kExternalTable = 1; +let kExternalMemory = 2; +let kExternalGlobal = 3; +let kExternalTag = 4; + +let kTableZero = 0; +let kMemoryZero = 0; +let kSegmentZero = 0; + +let kTagAttribute = 0; + +// Useful signatures +let kSig_i_i = makeSig([kWasmI32], [kWasmI32]); +let kSig_l_l = makeSig([kWasmI64], [kWasmI64]); +let kSig_i_l = makeSig([kWasmI64], [kWasmI32]); +let kSig_i_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32]); +let kSig_i_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]); +let kSig_v_iiii = makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmI32], []); +let kSig_f_ff = makeSig([kWasmF32, kWasmF32], [kWasmF32]); +let kSig_d_dd = makeSig([kWasmF64, kWasmF64], [kWasmF64]); +let kSig_l_ll = makeSig([kWasmI64, kWasmI64], [kWasmI64]); +let kSig_i_dd = makeSig([kWasmF64, kWasmF64], [kWasmI32]); +let kSig_v_v = makeSig([], []); +let kSig_i_v = makeSig([], [kWasmI32]); +let kSig_l_v = makeSig([], [kWasmI64]); +let kSig_f_v = makeSig([], [kWasmF32]); +let kSig_d_v = makeSig([], [kWasmF64]); +let kSig_v_i = makeSig([kWasmI32], []); +let kSig_v_ii = makeSig([kWasmI32, kWasmI32], []); +let kSig_v_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], []); +let kSig_v_l = makeSig([kWasmI64], []); +let kSig_v_d = makeSig([kWasmF64], []); +let kSig_v_dd = makeSig([kWasmF64, kWasmF64], []); +let kSig_v_ddi = makeSig([kWasmF64, kWasmF64, kWasmI32], []); +let kSig_ii_v = makeSig([], [kWasmI32, kWasmI32]); +let kSig_iii_v = makeSig([], [kWasmI32, kWasmI32, kWasmI32]); +let kSig_ii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32]); +let kSig_iii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32, kWasmI32]); +let kSig_ii_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32, kWasmI32]); +let kSig_iii_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32, kWasmI32, kWasmI32]); + +let kSig_v_f = makeSig([kWasmF32], []); +let kSig_f_f = makeSig([kWasmF32], [kWasmF32]); +let kSig_f_d = makeSig([kWasmF64], [kWasmF32]); +let kSig_d_d = makeSig([kWasmF64], [kWasmF64]); +let kSig_r_r = makeSig([kWasmExternRef], [kWasmExternRef]); +let kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]); +let kSig_i_r = makeSig([kWasmExternRef], [kWasmI32]); +let kSig_v_r = makeSig([kWasmExternRef], []); +let kSig_v_a = makeSig([kWasmAnyFunc], []); +let kSig_v_rr = makeSig([kWasmExternRef, kWasmExternRef], []); +let kSig_v_aa = makeSig([kWasmAnyFunc, kWasmAnyFunc], []); +let kSig_r_v = makeSig([], [kWasmExternRef]); +let kSig_a_v = makeSig([], [kWasmAnyFunc]); +let kSig_a_i = makeSig([kWasmI32], [kWasmAnyFunc]); + +function makeSig(params, results) { + return {params: params, results: results}; +} + +function makeSig_v_x(x) { + return makeSig([x], []); +} + +function makeSig_v_xx(x) { + return makeSig([x, x], []); +} + +function makeSig_r_v(r) { + return makeSig([], [r]); +} + +function makeSig_r_x(r, x) { + return makeSig([x], [r]); +} + +function makeSig_r_xx(r, x) { + return makeSig([x, x], [r]); +} + +// Opcodes +let kExprUnreachable = 0x00; +let kExprNop = 0x01; +let kExprBlock = 0x02; +let kExprLoop = 0x03; +let kExprIf = 0x04; +let kExprElse = 0x05; +let kExprTry = 0x06; +let kExprCatch = 0x07; +let kExprCatchAll = 0x19; +let kExprThrow = 0x08; +let kExprRethrow = 0x09; +let kExprBrOnExn = 0x0a; +let kExprEnd = 0x0b; +let kExprBr = 0x0c; +let kExprBrIf = 0x0d; +let kExprBrTable = 0x0e; +let kExprReturn = 0x0f; +let kExprCallFunction = 0x10; +let kExprCallIndirect = 0x11; +let kExprReturnCall = 0x12; +let kExprReturnCallIndirect = 0x13; +let kExprDrop = 0x1a; +let kExprSelect = 0x1b; +let kExprLocalGet = 0x20; +let kExprLocalSet = 0x21; +let kExprLocalTee = 0x22; +let kExprGlobalGet = 0x23; +let kExprGlobalSet = 0x24; +let kExprTableGet = 0x25; +let kExprTableSet = 0x26; +let kExprI32LoadMem = 0x28; +let kExprI64LoadMem = 0x29; +let kExprF32LoadMem = 0x2a; +let kExprF64LoadMem = 0x2b; +let kExprI32LoadMem8S = 0x2c; +let kExprI32LoadMem8U = 0x2d; +let kExprI32LoadMem16S = 0x2e; +let kExprI32LoadMem16U = 0x2f; +let kExprI64LoadMem8S = 0x30; +let kExprI64LoadMem8U = 0x31; +let kExprI64LoadMem16S = 0x32; +let kExprI64LoadMem16U = 0x33; +let kExprI64LoadMem32S = 0x34; +let kExprI64LoadMem32U = 0x35; +let kExprI32StoreMem = 0x36; +let kExprI64StoreMem = 0x37; +let kExprF32StoreMem = 0x38; +let kExprF64StoreMem = 0x39; +let kExprI32StoreMem8 = 0x3a; +let kExprI32StoreMem16 = 0x3b; +let kExprI64StoreMem8 = 0x3c; +let kExprI64StoreMem16 = 0x3d; +let kExprI64StoreMem32 = 0x3e; +let kExprMemorySize = 0x3f; +let kExprMemoryGrow = 0x40; +let kExprI32Const = 0x41; +let kExprI64Const = 0x42; +let kExprF32Const = 0x43; +let kExprF64Const = 0x44; +let kExprI32Eqz = 0x45; +let kExprI32Eq = 0x46; +let kExprI32Ne = 0x47; +let kExprI32LtS = 0x48; +let kExprI32LtU = 0x49; +let kExprI32GtS = 0x4a; +let kExprI32GtU = 0x4b; +let kExprI32LeS = 0x4c; +let kExprI32LeU = 0x4d; +let kExprI32GeS = 0x4e; +let kExprI32GeU = 0x4f; +let kExprI64Eqz = 0x50; +let kExprI64Eq = 0x51; +let kExprI64Ne = 0x52; +let kExprI64LtS = 0x53; +let kExprI64LtU = 0x54; +let kExprI64GtS = 0x55; +let kExprI64GtU = 0x56; +let kExprI64LeS = 0x57; +let kExprI64LeU = 0x58; +let kExprI64GeS = 0x59; +let kExprI64GeU = 0x5a; +let kExprF32Eq = 0x5b; +let kExprF32Ne = 0x5c; +let kExprF32Lt = 0x5d; +let kExprF32Gt = 0x5e; +let kExprF32Le = 0x5f; +let kExprF32Ge = 0x60; +let kExprF64Eq = 0x61; +let kExprF64Ne = 0x62; +let kExprF64Lt = 0x63; +let kExprF64Gt = 0x64; +let kExprF64Le = 0x65; +let kExprF64Ge = 0x66; +let kExprI32Clz = 0x67; +let kExprI32Ctz = 0x68; +let kExprI32Popcnt = 0x69; +let kExprI32Add = 0x6a; +let kExprI32Sub = 0x6b; +let kExprI32Mul = 0x6c; +let kExprI32DivS = 0x6d; +let kExprI32DivU = 0x6e; +let kExprI32RemS = 0x6f; +let kExprI32RemU = 0x70; +let kExprI32And = 0x71; +let kExprI32Ior = 0x72; +let kExprI32Xor = 0x73; +let kExprI32Shl = 0x74; +let kExprI32ShrS = 0x75; +let kExprI32ShrU = 0x76; +let kExprI32Rol = 0x77; +let kExprI32Ror = 0x78; +let kExprI64Clz = 0x79; +let kExprI64Ctz = 0x7a; +let kExprI64Popcnt = 0x7b; +let kExprI64Add = 0x7c; +let kExprI64Sub = 0x7d; +let kExprI64Mul = 0x7e; +let kExprI64DivS = 0x7f; +let kExprI64DivU = 0x80; +let kExprI64RemS = 0x81; +let kExprI64RemU = 0x82; +let kExprI64And = 0x83; +let kExprI64Ior = 0x84; +let kExprI64Xor = 0x85; +let kExprI64Shl = 0x86; +let kExprI64ShrS = 0x87; +let kExprI64ShrU = 0x88; +let kExprI64Rol = 0x89; +let kExprI64Ror = 0x8a; +let kExprF32Abs = 0x8b; +let kExprF32Neg = 0x8c; +let kExprF32Ceil = 0x8d; +let kExprF32Floor = 0x8e; +let kExprF32Trunc = 0x8f; +let kExprF32NearestInt = 0x90; +let kExprF32Sqrt = 0x91; +let kExprF32Add = 0x92; +let kExprF32Sub = 0x93; +let kExprF32Mul = 0x94; +let kExprF32Div = 0x95; +let kExprF32Min = 0x96; +let kExprF32Max = 0x97; +let kExprF32CopySign = 0x98; +let kExprF64Abs = 0x99; +let kExprF64Neg = 0x9a; +let kExprF64Ceil = 0x9b; +let kExprF64Floor = 0x9c; +let kExprF64Trunc = 0x9d; +let kExprF64NearestInt = 0x9e; +let kExprF64Sqrt = 0x9f; +let kExprF64Add = 0xa0; +let kExprF64Sub = 0xa1; +let kExprF64Mul = 0xa2; +let kExprF64Div = 0xa3; +let kExprF64Min = 0xa4; +let kExprF64Max = 0xa5; +let kExprF64CopySign = 0xa6; +let kExprI32ConvertI64 = 0xa7; +let kExprI32SConvertF32 = 0xa8; +let kExprI32UConvertF32 = 0xa9; +let kExprI32SConvertF64 = 0xaa; +let kExprI32UConvertF64 = 0xab; +let kExprI64SConvertI32 = 0xac; +let kExprI64UConvertI32 = 0xad; +let kExprI64SConvertF32 = 0xae; +let kExprI64UConvertF32 = 0xaf; +let kExprI64SConvertF64 = 0xb0; +let kExprI64UConvertF64 = 0xb1; +let kExprF32SConvertI32 = 0xb2; +let kExprF32UConvertI32 = 0xb3; +let kExprF32SConvertI64 = 0xb4; +let kExprF32UConvertI64 = 0xb5; +let kExprF32ConvertF64 = 0xb6; +let kExprF64SConvertI32 = 0xb7; +let kExprF64UConvertI32 = 0xb8; +let kExprF64SConvertI64 = 0xb9; +let kExprF64UConvertI64 = 0xba; +let kExprF64ConvertF32 = 0xbb; +let kExprI32ReinterpretF32 = 0xbc; +let kExprI64ReinterpretF64 = 0xbd; +let kExprF32ReinterpretI32 = 0xbe; +let kExprF64ReinterpretI64 = 0xbf; +let kExprI32SExtendI8 = 0xc0; +let kExprI32SExtendI16 = 0xc1; +let kExprI64SExtendI8 = 0xc2; +let kExprI64SExtendI16 = 0xc3; +let kExprI64SExtendI32 = 0xc4; +let kExprRefNull = 0xd0; +let kExprRefIsNull = 0xd1; +let kExprRefFunc = 0xd2; + +// Prefix opcodes +let kGCPrefix = 0xfb; +let kNumericPrefix = 0xfc; +let kSimdPrefix = 0xfd; +let kAtomicPrefix = 0xfe; + +// Use these for multi-byte instructions (opcode > 0x7F needing two LEB bytes): +function GCInstr(opcode) { + if (opcode <= 0x7F) return [kGCPrefix, opcode]; + return [kGCPrefix, 0x80 | (opcode & 0x7F), opcode >> 7]; +} + +// GC opcodes +let kExprStructNew = 0x00; +let kExprStructNewDefault = 0x01; +let kExprStructGet = 0x02; +let kExprStructGetS = 0x03; +let kExprStructGetU = 0x04; +let kExprStructSet = 0x05; +let kExprArrayNew = 0x06; +let kExprArrayNewDefault = 0x07; +let kExprArrayNewFixed = 0x08; +let kExprArrayNewData = 0x09; +let kExprArrayNewElem = 0x0a; +let kExprArrayGet = 0x0b; +let kExprArrayGetS = 0x0c; +let kExprArrayGetU = 0x0d; +let kExprArraySet = 0x0e; +let kExprArrayLen = 0x0f; +let kExprArrayFill = 0x10; +let kExprArrayCopy = 0x11; +let kExprArrayInitData = 0x12; +let kExprArrayInitElem = 0x13; +let kExprRefTest = 0x14; +let kExprRefTestNull = 0x15; +let kExprRefCast = 0x16; +let kExprRefCastNull = 0x17; +let kExprBrOnCast = 0x18; +let kExprBrOnCastFail = 0x19; +let kExprExternInternalize = 0x1a; +let kExprExternExternalize = 0x1b; +let kExprI31New = 0x1c; +let kExprI31GetS = 0x1d; +let kExprI31GetU = 0x1e; + +// Numeric opcodes. +let kExprMemoryInit = 0x08; +let kExprDataDrop = 0x09; +let kExprMemoryCopy = 0x0a; +let kExprMemoryFill = 0x0b; +let kExprTableInit = 0x0c; +let kExprElemDrop = 0x0d; +let kExprTableCopy = 0x0e; +let kExprTableGrow = 0x0f; +let kExprTableSize = 0x10; +let kExprTableFill = 0x11; + +// Atomic opcodes. +let kExprAtomicNotify = 0x00; +let kExprI32AtomicWait = 0x01; +let kExprI64AtomicWait = 0x02; +let kExprI32AtomicLoad = 0x10; +let kExprI32AtomicLoad8U = 0x12; +let kExprI32AtomicLoad16U = 0x13; +let kExprI32AtomicStore = 0x17; +let kExprI32AtomicStore8U = 0x19; +let kExprI32AtomicStore16U = 0x1a; +let kExprI32AtomicAdd = 0x1e; +let kExprI32AtomicAdd8U = 0x20; +let kExprI32AtomicAdd16U = 0x21; +let kExprI32AtomicSub = 0x25; +let kExprI32AtomicSub8U = 0x27; +let kExprI32AtomicSub16U = 0x28; +let kExprI32AtomicAnd = 0x2c; +let kExprI32AtomicAnd8U = 0x2e; +let kExprI32AtomicAnd16U = 0x2f; +let kExprI32AtomicOr = 0x33; +let kExprI32AtomicOr8U = 0x35; +let kExprI32AtomicOr16U = 0x36; +let kExprI32AtomicXor = 0x3a; +let kExprI32AtomicXor8U = 0x3c; +let kExprI32AtomicXor16U = 0x3d; +let kExprI32AtomicExchange = 0x41; +let kExprI32AtomicExchange8U = 0x43; +let kExprI32AtomicExchange16U = 0x44; +let kExprI32AtomicCompareExchange = 0x48; +let kExprI32AtomicCompareExchange8U = 0x4a; +let kExprI32AtomicCompareExchange16U = 0x4b; + +let kExprI64AtomicLoad = 0x11; +let kExprI64AtomicLoad8U = 0x14; +let kExprI64AtomicLoad16U = 0x15; +let kExprI64AtomicLoad32U = 0x16; +let kExprI64AtomicStore = 0x18; +let kExprI64AtomicStore8U = 0x1b; +let kExprI64AtomicStore16U = 0x1c; +let kExprI64AtomicStore32U = 0x1d; +let kExprI64AtomicAdd = 0x1f; +let kExprI64AtomicAdd8U = 0x22; +let kExprI64AtomicAdd16U = 0x23; +let kExprI64AtomicAdd32U = 0x24; +let kExprI64AtomicSub = 0x26; +let kExprI64AtomicSub8U = 0x29; +let kExprI64AtomicSub16U = 0x2a; +let kExprI64AtomicSub32U = 0x2b; +let kExprI64AtomicAnd = 0x2d; +let kExprI64AtomicAnd8U = 0x30; +let kExprI64AtomicAnd16U = 0x31; +let kExprI64AtomicAnd32U = 0x32; +let kExprI64AtomicOr = 0x34; +let kExprI64AtomicOr8U = 0x37; +let kExprI64AtomicOr16U = 0x38; +let kExprI64AtomicOr32U = 0x39; +let kExprI64AtomicXor = 0x3b; +let kExprI64AtomicXor8U = 0x3e; +let kExprI64AtomicXor16U = 0x3f; +let kExprI64AtomicXor32U = 0x40; +let kExprI64AtomicExchange = 0x42; +let kExprI64AtomicExchange8U = 0x45; +let kExprI64AtomicExchange16U = 0x46; +let kExprI64AtomicExchange32U = 0x47; +let kExprI64AtomicCompareExchange = 0x49 +let kExprI64AtomicCompareExchange8U = 0x4c; +let kExprI64AtomicCompareExchange16U = 0x4d; +let kExprI64AtomicCompareExchange32U = 0x4e; + +// Simd opcodes. +let kExprS128LoadMem = 0x00; +let kExprS128StoreMem = 0x01; +let kExprI32x4Splat = 0x0c; +let kExprI32x4Eq = 0x2c; +let kExprS1x4AllTrue = 0x75; +let kExprF32x4Min = 0x9e; + +class Binary { + constructor() { + this.length = 0; + this.buffer = new Uint8Array(8192); + } + + ensure_space(needed) { + if (this.buffer.length - this.length >= needed) return; + let new_capacity = this.buffer.length * 2; + while (new_capacity - this.length < needed) new_capacity *= 2; + let new_buffer = new Uint8Array(new_capacity); + new_buffer.set(this.buffer); + this.buffer = new_buffer; + } + + trunc_buffer() { + return new Uint8Array(this.buffer.buffer, 0, this.length); + } + + reset() { + this.length = 0; + } + + emit_u8(val) { + this.ensure_space(1); + this.buffer[this.length++] = val; + } + + emit_u16(val) { + this.ensure_space(2); + this.buffer[this.length++] = val; + this.buffer[this.length++] = val >> 8; + } + + emit_u32(val) { + this.ensure_space(4); + this.buffer[this.length++] = val; + this.buffer[this.length++] = val >> 8; + this.buffer[this.length++] = val >> 16; + this.buffer[this.length++] = val >> 24; + } + + emit_leb_u(val, max_len) { + this.ensure_space(max_len); + for (let i = 0; i < max_len; ++i) { + let v = val & 0xff; + val = val >>> 7; + if (val == 0) { + this.buffer[this.length++] = v; + return; + } + this.buffer[this.length++] = v | 0x80; + } + throw new Error("Leb value exceeds maximum length of " + max_len); + } + + emit_u32v(val) { + this.emit_leb_u(val, kMaxVarInt32Size); + } + + emit_u64v(val) { + this.emit_leb_u(val, kMaxVarInt64Size); + } + + emit_bytes(data) { + this.ensure_space(data.length); + this.buffer.set(data, this.length); + this.length += data.length; + } + + emit_string(string) { + // When testing illegal names, we pass a byte array directly. + if (string instanceof Array) { + this.emit_u32v(string.length); + this.emit_bytes(string); + return; + } + + // This is the hacky way to convert a JavaScript string to a UTF8 encoded + // string only containing single-byte characters. + let string_utf8 = unescape(encodeURIComponent(string)); + this.emit_u32v(string_utf8.length); + for (let i = 0; i < string_utf8.length; i++) { + this.emit_u8(string_utf8.charCodeAt(i)); + } + } + + emit_heap_type(heap_type) { + this.emit_bytes(wasmSignedLeb(heap_type, kMaxVarInt32Size)); + } + + emit_type(type) { + if ((typeof type) == 'number') { + this.emit_u8(type >= 0 ? type : type & kLeb128Mask); + } else { + this.emit_u8(type.opcode); + if ('depth' in type) this.emit_u8(type.depth); + this.emit_heap_type(type.heap_type); + } + } + + emit_init_expr(expr) { + this.emit_bytes(expr); + this.emit_u8(kExprEnd); + } + + emit_header() { + this.emit_bytes([ + kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 + ]); + } + + emit_section(section_code, content_generator) { + // Emit section name. + this.emit_u8(section_code); + // Emit the section to a temporary buffer: its full length isn't know yet. + const section = new Binary; + content_generator(section); + // Emit section length. + this.emit_u32v(section.length); + // Copy the temporary buffer. + // Avoid spread because {section} can be huge. + this.emit_bytes(section.trunc_buffer()); + } +} + +class WasmFunctionBuilder { + constructor(module, name, type_index) { + this.module = module; + this.name = name; + this.type_index = type_index; + this.body = []; + this.locals = []; + this.local_names = []; + } + + numLocalNames() { + let num_local_names = 0; + for (let loc_name of this.local_names) { + if (loc_name !== undefined) ++num_local_names; + } + return num_local_names; + } + + exportAs(name) { + this.module.addExport(name, this.index); + return this; + } + + exportFunc() { + this.exportAs(this.name); + return this; + } + + addBody(body) { + for (let b of body) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0 ) + throw new Error('invalid body (entries must be 8 bit numbers): ' + body); + } + this.body = body.slice(); + // Automatically add the end for the function block to the body. + this.body.push(kExprEnd); + return this; + } + + addBodyWithEnd(body) { + this.body = body; + return this; + } + + getNumLocals() { + let total_locals = 0; + for (let l of this.locals) { + for (let type of ["i32", "i64", "f32", "f64", "s128"]) { + total_locals += l[type + "_count"] || 0; + } + } + return total_locals; + } + + addLocals(locals, names) { + const old_num_locals = this.getNumLocals(); + this.locals.push(locals); + if (names) { + const missing_names = old_num_locals - this.local_names.length; + this.local_names.push(...new Array(missing_names), ...names); + } + return this; + } + + end() { + return this.module; + } +} + +class WasmGlobalBuilder { + constructor(module, type, mutable, init) { + this.module = module; + this.type = type; + this.mutable = mutable; + this.init = init; + } + + exportAs(name) { + this.module.exports.push({name: name, kind: kExternalGlobal, + index: this.index}); + return this; + } +} + +function checkExpr(expr) { + for (let b of expr) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0) { + throw new Error( + 'invalid body (entries must be 8 bit numbers): ' + expr); + } + } +} + +class WasmTableBuilder { + constructor(module, type, initial_size, max_size, init_expr) { + this.module = module; + this.type = type; + this.initial_size = initial_size; + this.has_max = max_size != undefined; + this.max_size = max_size; + this.init_expr = init_expr; + this.has_init = init_expr !== undefined; + } + + exportAs(name) { + this.module.exports.push({name: name, kind: kExternalTable, + index: this.index}); + return this; + } +} + +function makeField(type, mutability) { + if ((typeof mutability) != 'boolean') { + throw new Error('field mutability must be boolean'); + } + return {type: type, mutability: mutability}; +} + +class WasmStruct { + constructor(fields, is_final, supertype_idx) { + if (!Array.isArray(fields)) { + throw new Error('struct fields must be an array'); + } + this.fields = fields; + this.type_form = kWasmStructTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + +class WasmArray { + constructor(type, mutability, is_final, supertype_idx) { + this.type = type; + this.mutability = mutability; + this.type_form = kWasmArrayTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + +class WasmModuleBuilder { + constructor() { + this.types = []; + this.imports = []; + this.exports = []; + this.globals = []; + this.tables = []; + this.tags = []; + this.functions = []; + this.element_segments = []; + this.data_segments = []; + this.explicit = []; + this.rec_groups = []; + this.num_imported_funcs = 0; + this.num_imported_globals = 0; + this.num_imported_tables = 0; + this.num_imported_tags = 0; + return this; + } + + addStart(start_index) { + this.start_index = start_index; + return this; + } + + addMemory(min, max, exp, shared) { + this.memory = {min: min, max: max, exp: exp, shared: shared}; + return this; + } + + addExplicitSection(bytes) { + this.explicit.push(bytes); + return this; + } + + stringToBytes(name) { + var result = new Binary(); + result.emit_string(name); + return result.trunc_buffer() + } + + createCustomSection(name, bytes) { + name = this.stringToBytes(name); + var section = new Binary(); + section.emit_u8(kUnknownSectionCode); + section.emit_u32v(name.length + bytes.length); + section.emit_bytes(name); + section.emit_bytes(bytes); + return section.trunc_buffer(); + } + + addCustomSection(name, bytes) { + this.explicit.push(this.createCustomSection(name, bytes)); + } + + // We use {is_final = true} so that the MVP syntax is generated for + // signatures. + addType(type, supertype_idx = kNoSuperType, is_final = true) { + var pl = type.params.length; // should have params + var rl = type.results.length; // should have results + var type_copy = {params: type.params, results: type.results, + is_final: is_final, supertype: supertype_idx}; + this.types.push(type_copy); + return this.types.length - 1; + } + + addStruct(fields, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmStruct(fields, is_final, supertype_idx)); + return this.types.length - 1; + } + + addArray(type, mutability, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmArray(type, mutability, is_final, supertype_idx)); + return this.types.length - 1; + } + + static defaultFor(type) { + switch (type) { + case kWasmI32: + return wasmI32Const(0); + case kWasmI64: + return wasmI64Const(0); + case kWasmF32: + return wasmF32Const(0.0); + case kWasmF64: + return wasmF64Const(0.0); + case kWasmS128: + return [kSimdPrefix, kExprS128Const, ...(new Array(16).fill(0))]; + default: + if ((typeof type) != 'number' && type.opcode != kWasmRefNull) { + throw new Error("Non-defaultable type"); + } + let heap_type = (typeof type) == 'number' ? type : type.heap_type; + return [kExprRefNull, ...wasmSignedLeb(heap_type, kMaxVarInt32Size)]; + } + } + + addGlobal(type, mutable, init) { + if (init === undefined) init = WasmModuleBuilder.defaultFor(type); + checkExpr(init); + let glob = new WasmGlobalBuilder(this, type, mutable, init); + glob.index = this.globals.length + this.num_imported_globals; + this.globals.push(glob); + return glob; + } + + addTable(type, initial_size, max_size = undefined, init_expr = undefined) { + if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 || + type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) { + throw new Error('Tables must be of a reference type'); + } + if (init_expr != undefined) checkExpr(init_expr); + let table = new WasmTableBuilder( + this, type, initial_size, max_size, init_expr); + table.index = this.tables.length + this.num_imported_tables; + this.tables.push(table); + return table; + } + + addTag(type) { + let type_index = (typeof type) == "number" ? type : this.addType(type); + let tag_index = this.tags.length + this.num_imported_tags; + this.tags.push(type_index); + return tag_index; + } + + addFunction(name, type) { + let type_index = (typeof type) == "number" ? type : this.addType(type); + let func = new WasmFunctionBuilder(this, name, type_index); + func.index = this.functions.length + this.num_imported_funcs; + this.functions.push(func); + return func; + } + + addImport(module, name, type) { + if (this.functions.length != 0) { + throw new Error('Imported functions must be declared before local ones'); + } + let type_index = (typeof type) == "number" ? type : this.addType(type); + this.imports.push({module: module, name: name, kind: kExternalFunction, + type: type_index}); + return this.num_imported_funcs++; + } + + addImportedGlobal(module, name, type, mutable = false) { + if (this.globals.length != 0) { + throw new Error('Imported globals must be declared before local ones'); + } + let o = {module: module, name: name, kind: kExternalGlobal, type: type, + mutable: mutable}; + this.imports.push(o); + return this.num_imported_globals++; + } + + addImportedMemory(module, name, initial = 0, maximum, shared) { + let o = {module: module, name: name, kind: kExternalMemory, + initial: initial, maximum: maximum, shared: shared}; + this.imports.push(o); + return this; + } + + addImportedTable(module, name, initial, maximum, type) { + if (this.tables.length != 0) { + throw new Error('Imported tables must be declared before local ones'); + } + let o = {module: module, name: name, kind: kExternalTable, initial: initial, + maximum: maximum, type: type || kWasmAnyFunctionTypeForm}; + this.imports.push(o); + return this.num_imported_tables++; + } + + addImportedTag(module, name, type) { + if (this.tags.length != 0) { + throw new Error('Imported tags must be declared before local ones'); + } + let type_index = (typeof type) == "number" ? type : this.addType(type); + let o = {module: module, name: name, kind: kExternalTag, type: type_index}; + this.imports.push(o); + return this.num_imported_tags++; + } + + addExport(name, index) { + this.exports.push({name: name, kind: kExternalFunction, index: index}); + return this; + } + + addExportOfKind(name, kind, index) { + this.exports.push({name: name, kind: kind, index: index}); + return this; + } + + addDataSegment(addr, data, is_global = false) { + this.data_segments.push( + {addr: addr, data: data, is_global: is_global, is_active: true}); + return this.data_segments.length - 1; + } + + addPassiveDataSegment(data) { + this.data_segments.push({data: data, is_active: false}); + return this.data_segments.length - 1; + } + + exportMemoryAs(name) { + this.exports.push({name: name, kind: kExternalMemory, index: 0}); + } + + addElementSegment(table, base, is_global, array) { + this.element_segments.push({table: table, base: base, is_global: is_global, + array: array, is_active: true}); + return this; + } + + addPassiveElementSegment(array, is_import = false) { + this.element_segments.push({array: array, is_active: false}); + return this; + } + + appendToTable(array) { + for (let n of array) { + if (typeof n != 'number') + throw new Error('invalid table (entries have to be numbers): ' + array); + } + if (this.tables.length == 0) { + this.addTable(kWasmAnyFunc, 0); + } + // Adjust the table to the correct size. + let table = this.tables[0]; + const base = table.initial_size; + const table_size = base + array.length; + table.initial_size = table_size; + if (table.has_max && table_size > table.max_size) { + table.max_size = table_size; + } + return this.addElementSegment(0, base, false, array); + } + + setTableBounds(min, max = undefined) { + if (this.tables.length != 0) { + throw new Error("The table bounds of table '0' have already been set."); + } + this.addTable(kWasmAnyFunc, min, max); + return this; + } + + startRecGroup() { + this.rec_groups.push({start: this.types.length, size: 0}); + } + + endRecGroup() { + if (this.rec_groups.length == 0) { + throw new Error("Did not start a recursive group before ending one") + } + let last_element = this.rec_groups[this.rec_groups.length - 1] + if (last_element.size != 0) { + throw new Error("Did not start a recursive group before ending one") + } + last_element.size = this.types.length - last_element.start; + } + + setName(name) { + this.name = name; + return this; + } + + toBuffer(debug = false) { + let binary = new Binary; + let wasm = this; + + // Add header + binary.emit_header(); + + // Add type section + if (wasm.types.length > 0) { + if (debug) print('emitting types @ ' + binary.length); + binary.emit_section(kTypeSectionCode, section => { + let length_with_groups = wasm.types.length; + for (let group of wasm.rec_groups) { + length_with_groups -= group.size - 1; + } + section.emit_u32v(length_with_groups); + + let rec_group_index = 0; + + for (let i = 0; i < wasm.types.length; i++) { + if (rec_group_index < wasm.rec_groups.length && + wasm.rec_groups[rec_group_index].start == i) { + section.emit_u8(kWasmRecursiveTypeGroupForm); + section.emit_u32v(wasm.rec_groups[rec_group_index].size); + rec_group_index++; + } + + let type = wasm.types[i]; + if (type.supertype != kNoSuperType) { + section.emit_u8(type.is_final ? kWasmSubtypeFinalForm + : kWasmSubtypeForm); + section.emit_u8(1); // supertype count + section.emit_u32v(type.supertype); + } else if (!type.is_final) { + section.emit_u8(kWasmSubtypeForm); + section.emit_u8(0); // no supertypes + } + if (type instanceof WasmStruct) { + section.emit_u8(kWasmStructTypeForm); + section.emit_u32v(type.fields.length); + for (let field of type.fields) { + section.emit_type(field.type); + section.emit_u8(field.mutability ? 1 : 0); + } + } else if (type instanceof WasmArray) { + section.emit_u8(kWasmArrayTypeForm); + section.emit_type(type.type); + section.emit_u8(type.mutability ? 1 : 0); + } else { + section.emit_u8(kWasmFunctionTypeForm); + section.emit_u32v(type.params.length); + for (let param of type.params) { + section.emit_type(param); + } + section.emit_u32v(type.results.length); + for (let result of type.results) { + section.emit_type(result); + } + } + } + }); + } + + // Add imports section + if (wasm.imports.length > 0) { + if (debug) print("emitting imports @ " + binary.length); + binary.emit_section(kImportSectionCode, section => { + section.emit_u32v(wasm.imports.length); + for (let imp of wasm.imports) { + section.emit_string(imp.module); + section.emit_string(imp.name || ''); + section.emit_u8(imp.kind); + if (imp.kind == kExternalFunction) { + section.emit_u32v(imp.type_index); + } else if (imp.kind == kExternalGlobal) { + section.emit_type(imp.type); + section.emit_u8(imp.mutable); + } else if (imp.kind == kExternalMemory) { + var has_max = (typeof imp.maximum) != "undefined"; + var is_shared = (typeof imp.shared) != "undefined"; + if (is_shared) { + section.emit_u8(has_max ? 3 : 2); // flags + } else { + section.emit_u8(has_max ? 1 : 0); // flags + } + section.emit_u32v(imp.initial); // initial + if (has_max) section.emit_u32v(imp.maximum); // maximum + } else if (imp.kind == kExternalTable) { + section.emit_type(imp.type); + var has_max = (typeof imp.maximum) != "undefined"; + section.emit_u8(has_max ? 1 : 0); // flags + section.emit_u32v(imp.initial); // initial + if (has_max) section.emit_u32v(imp.maximum); // maximum + } else if (imp.kind == kExternalTag) { + section.emit_u32v(kTagAttribute); + section.emit_u32v(imp.type); + } else { + throw new Error("unknown/unsupported import kind " + imp.kind); + } + } + }); + } + + // Add functions declarations + if (wasm.functions.length > 0) { + if (debug) print("emitting function decls @ " + binary.length); + binary.emit_section(kFunctionSectionCode, section => { + section.emit_u32v(wasm.functions.length); + for (let func of wasm.functions) { + section.emit_u32v(func.type_index); + } + }); + } + + // Add table section + if (wasm.tables.length > 0) { + if (debug) print ("emitting tables @ " + binary.length); + binary.emit_section(kTableSectionCode, section => { + section.emit_u32v(wasm.tables.length); + for (let table of wasm.tables) { + section.emit_type(table.type); + section.emit_u8(table.has_max); + section.emit_u32v(table.initial_size); + if (table.has_max) section.emit_u32v(table.max_size); + if (table.has_init) section.emit_init_expr(table.init_expr); + } + }); + } + + // Add memory section + if (wasm.memory !== undefined) { + if (debug) print("emitting memory @ " + binary.length); + binary.emit_section(kMemorySectionCode, section => { + section.emit_u8(1); // one memory entry + const has_max = wasm.memory.max !== undefined; + const is_shared = wasm.memory.shared !== undefined; + // Emit flags (bit 0: reszeable max, bit 1: shared memory) + if (is_shared) { + section.emit_u8(has_max ? kSharedHasMaximumFlag : 2); + } else { + section.emit_u8(has_max ? kHasMaximumFlag : 0); + } + section.emit_u32v(wasm.memory.min); + if (has_max) section.emit_u32v(wasm.memory.max); + }); + } + + // Add global section. + if (wasm.globals.length > 0) { + if (debug) print ("emitting globals @ " + binary.length); + binary.emit_section(kGlobalSectionCode, section => { + section.emit_u32v(wasm.globals.length); + for (let global of wasm.globals) { + section.emit_type(global.type); + section.emit_u8(global.mutable); + section.emit_init_expr(global.init); + } + }); + } + + // Add tags. + if (wasm.tags.length > 0) { + if (debug) print("emitting tags @ " + binary.length); + binary.emit_section(kTagSectionCode, section => { + section.emit_u32v(wasm.tags.length); + for (let type of wasm.tags) { + section.emit_u32v(kTagAttribute); + section.emit_u32v(type); + } + }); + } + + // Add export table. + var mem_export = (wasm.memory !== undefined && wasm.memory.exp); + var exports_count = wasm.exports.length + (mem_export ? 1 : 0); + if (exports_count > 0) { + if (debug) print("emitting exports @ " + binary.length); + binary.emit_section(kExportSectionCode, section => { + section.emit_u32v(exports_count); + for (let exp of wasm.exports) { + section.emit_string(exp.name); + section.emit_u8(exp.kind); + section.emit_u32v(exp.index); + } + if (mem_export) { + section.emit_string("memory"); + section.emit_u8(kExternalMemory); + section.emit_u8(0); + } + }); + } + + // Add start function section. + if (wasm.start_index !== undefined) { + if (debug) print("emitting start function @ " + binary.length); + binary.emit_section(kStartSectionCode, section => { + section.emit_u32v(wasm.start_index); + }); + } + + // Add element segments + if (wasm.element_segments.length > 0) { + if (debug) print("emitting element segments @ " + binary.length); + binary.emit_section(kElementSectionCode, section => { + var inits = wasm.element_segments; + section.emit_u32v(inits.length); + + for (let init of inits) { + if (init.is_active) { + // Active segment. + if (init.table == 0) { + section.emit_u32v(kActiveNoIndex); + } else { + section.emit_u32v(kActiveWithIndex); + section.emit_u32v(init.table); + } + if (init.is_global) { + section.emit_u8(kExprGlobalGet); + } else { + section.emit_u8(kExprI32Const); + } + section.emit_u32v(init.base); + section.emit_u8(kExprEnd); + if (init.table != 0) { + section.emit_u8(kExternalFunction); + } + section.emit_u32v(init.array.length); + for (let index of init.array) { + section.emit_u32v(index); + } + } else { + // Passive segment. + section.emit_u8(kPassiveWithElements); // flags + section.emit_u8(kWasmAnyFunc); + section.emit_u32v(init.array.length); + for (let index of init.array) { + if (index === null) { + section.emit_u8(kExprRefNull); + section.emit_u8(kExprEnd); + } else { + section.emit_u8(kExprRefFunc); + section.emit_u32v(index); + section.emit_u8(kExprEnd); + } + } + } + } + }); + } + + // If there are any passive data segments, add the DataCount section. + if (wasm.data_segments.some(seg => !seg.is_active)) { + binary.emit_section(kDataCountSectionCode, section => { + section.emit_u32v(wasm.data_segments.length); + }); + } + + // Add function bodies. + if (wasm.functions.length > 0) { + // emit function bodies + if (debug) print("emitting code @ " + binary.length); + binary.emit_section(kCodeSectionCode, section => { + section.emit_u32v(wasm.functions.length); + let header = new Binary; + for (let func of wasm.functions) { + header.reset(); + // Function body length will be patched later. + let local_decls = []; + for (let l of func.locals || []) { + if (l.i32_count > 0) { + local_decls.push({count: l.i32_count, type: kWasmI32}); + } + if (l.i64_count > 0) { + local_decls.push({count: l.i64_count, type: kWasmI64}); + } + if (l.f32_count > 0) { + local_decls.push({count: l.f32_count, type: kWasmF32}); + } + if (l.f64_count > 0) { + local_decls.push({count: l.f64_count, type: kWasmF64}); + } + if (l.s128_count > 0) { + local_decls.push({count: l.s128_count, type: kWasmS128}); + } + if (l.anyref_count > 0) { + local_decls.push({count: l.anyref_count, type: kWasmExternRef}); + } + if (l.anyfunc_count > 0) { + local_decls.push({count: l.anyfunc_count, type: kWasmAnyFunc}); + } + } + + header.emit_u32v(local_decls.length); + for (let decl of local_decls) { + header.emit_u32v(decl.count); + header.emit_type(decl.type); + } + + section.emit_u32v(header.length + func.body.length); + section.emit_bytes(header.trunc_buffer()); + section.emit_bytes(func.body); + } + }); + } + + // Add data segments. + if (wasm.data_segments.length > 0) { + if (debug) print("emitting data segments @ " + binary.length); + binary.emit_section(kDataSectionCode, section => { + section.emit_u32v(wasm.data_segments.length); + for (let seg of wasm.data_segments) { + if (seg.is_active) { + section.emit_u8(0); // linear memory index 0 / flags + if (seg.is_global) { + // initializer is a global variable + section.emit_u8(kExprGlobalGet); + section.emit_u32v(seg.addr); + } else { + // initializer is a constant + section.emit_u8(kExprI32Const); + section.emit_u32v(seg.addr); + } + section.emit_u8(kExprEnd); + } else { + section.emit_u8(kPassive); // flags + } + section.emit_u32v(seg.data.length); + section.emit_bytes(seg.data); + } + }); + } + + // Add any explicitly added sections + for (let exp of wasm.explicit) { + if (debug) print("emitting explicit @ " + binary.length); + binary.emit_bytes(exp); + } + + // Add names. + let num_function_names = 0; + let num_functions_with_local_names = 0; + for (let func of wasm.functions) { + if (func.name !== undefined) ++num_function_names; + if (func.numLocalNames() > 0) ++num_functions_with_local_names; + } + if (num_function_names > 0 || num_functions_with_local_names > 0 || + wasm.name !== undefined) { + if (debug) print('emitting names @ ' + binary.length); + binary.emit_section(kUnknownSectionCode, section => { + section.emit_string('name'); + // Emit module name. + if (wasm.name !== undefined) { + section.emit_section(kModuleNameCode, name_section => { + name_section.emit_string(wasm.name); + }); + } + // Emit function names. + if (num_function_names > 0) { + section.emit_section(kFunctionNamesCode, name_section => { + name_section.emit_u32v(num_function_names); + for (let func of wasm.functions) { + if (func.name === undefined) continue; + name_section.emit_u32v(func.index); + name_section.emit_string(func.name); + } + }); + } + // Emit local names. + if (num_functions_with_local_names > 0) { + section.emit_section(kLocalNamesCode, name_section => { + name_section.emit_u32v(num_functions_with_local_names); + for (let func of wasm.functions) { + if (func.numLocalNames() == 0) continue; + name_section.emit_u32v(func.index); + name_section.emit_u32v(func.numLocalNames()); + for (let i = 0; i < func.local_names.length; ++i) { + if (func.local_names[i] === undefined) continue; + name_section.emit_u32v(i); + name_section.emit_string(func.local_names[i]); + } + } + }); + } + }); + } + + return binary.trunc_buffer(); + } + + toArray(debug = false) { + return Array.from(this.toBuffer(debug)); + } + + instantiate(ffi) { + let module = this.toModule(); + let instance = new WebAssembly.Instance(module, ffi); + return instance; + } + + asyncInstantiate(ffi) { + return WebAssembly.instantiate(this.toBuffer(), ffi) + .then(({module, instance}) => instance); + } + + toModule(debug = false) { + return new WebAssembly.Module(this.toBuffer(debug)); + } +} +globalThis.WasmModuleBuilder = WasmModuleBuilder; + +function wasmSignedLeb(val, max_len = 5) { + let res = []; + for (let i = 0; i < max_len; ++i) { + let v = val & 0x7f; + // If {v} sign-extended from 7 to 32 bits is equal to val, we are done. + if (((v << 25) >> 25) == val) { + res.push(v); + return res; + } + res.push(v | 0x80); + val = val >> 7; + } + throw new Error( + 'Leb value <' + val + '> exceeds maximum length of ' + max_len); +} +globalThis.wasmSignedLeb = wasmSignedLeb; + +function wasmI32Const(val) { + return [kExprI32Const, ...wasmSignedLeb(val, 5)]; +} +globalThis.wasmI32Const = wasmI32Const; + +function wasmF32Const(f) { + // Write in little-endian order at offset 0. + data_view.setFloat32(0, f, true); + return [ + kExprF32Const, byte_view[0], byte_view[1], byte_view[2], byte_view[3] + ]; +} +globalThis.wasmI32Const = wasmI32Const; + +function wasmF64Const(f) { + // Write in little-endian order at offset 0. + data_view.setFloat64(0, f, true); + return [ + kExprF64Const, byte_view[0], byte_view[1], byte_view[2], + byte_view[3], byte_view[4], byte_view[5], byte_view[6], byte_view[7] + ]; +} +globalThis.wasmF64Const = wasmF64Const; diff --git a/testing/web-platform/tests/wasm/resources/load_wasm.js b/testing/web-platform/tests/wasm/resources/load_wasm.js new file mode 100644 index 0000000000..7f280ac032 --- /dev/null +++ b/testing/web-platform/tests/wasm/resources/load_wasm.js @@ -0,0 +1,12 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function createWasmModule() { + return fetch('/wasm/incrementer.wasm') + .then(response => { + if (!response.ok) throw new Error(response.statusText); + return response.arrayBuffer(); + }) + .then(WebAssembly.compile); +} diff --git a/testing/web-platform/tests/wasm/serialization/arraybuffer/transfer.window.js b/testing/web-platform/tests/wasm/serialization/arraybuffer/transfer.window.js new file mode 100644 index 0000000000..0258581b1e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/arraybuffer/transfer.window.js @@ -0,0 +1,11 @@ +test(() => { + const buffer = new WebAssembly.Memory({initial: 4}).buffer; + postMessage(buffer, '*'); +}, "Serializing a WebAssembly.Memory-backed ArrayBuffer works"); + +test(() => { + const buffer = new WebAssembly.Memory({initial: 4}).buffer; + assert_throws_js(TypeError, () => { + postMessage('foo', '*', [buffer]); + }); +}, "Transfering a WebAssembly.Memory-backed ArrayBuffer throws"); diff --git a/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success-and-failure.html b/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success-and-failure.html new file mode 100644 index 0000000000..0d11cc595b --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success-and-failure.html @@ -0,0 +1,38 @@ +<!doctype html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<title>WebAssembly.Module cannot cross agent clusters, BroadcastChannel edition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> +<script> +async_test(t => { + const channel = new BroadcastChannel("anne was here"), + dw = new Worker("resources/broadcastchannel-worker.js"), + sw = new SharedWorker("resources/broadcastchannel-sharedworker.js"); + let startCounter = 0, + dwStatus = "unknown", + swStatus = "unknown"; + + channel.onmessage = t.step_func(({ data }) => { + if(data === "hi") { + startCounter++; + if(startCounter === 2) { + createWasmModule().then(module => { + channel.postMessage(module); + }); + } else if(startCounter > 2) { + assert_unreached(); + } + } else if(data === "dw-success") { + dwStatus = "success"; + } else if(data === "sw-success") { + swStatus = "success"; + } else { + assert_unreached(); + } + if(dwStatus === "success" && swStatus === "success") { + t.done(); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success.html b/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success.html new file mode 100644 index 0000000000..ddc242b82e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/broadcastchannel-success.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>Structured cloning of WebAssembly.Module: BroadcastChannel within the same agent cluster</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + return createWasmModule().then(module => { + let loadedIframes = 0; + return Promise.all([ + createIFrame("resources/broadcastchannel-iframe.html"), + createIFrame("resources/broadcastchannel-iframe.html"), + createIFrame("resources/broadcastchannel-iframe.html") + ]).then(() => { + const thisIframe = loadedIframes++; + const channel = new BroadcastChannel("channel name"); + + return new Promise(resolve => { + let soFar = 0; + channel.onmessage = t.step_func(msg => { + if (msg.module) { + // We only care about "broadcasts" from the workers. + return; + } + + let {i, result} = msg.data; + + assert_in_array(i, [0, 1, 2], "Any message events must come from expected sources"); + assert_equals(result, i + 1, `iframe ${i} must return ${i+1}`); + ++soFar; + + if (soFar === 3) { + resolve(); + } + }); + + channel.postMessage({ module, i: thisIframe }); + }); + }); + }); +}); + +function createIFrame(src) { + return new Promise((resolve, reject) => { + const iframe = document.createElement("iframe"); + iframe.src = src; + iframe.onload = () => resolve(iframe); + iframe.onerror = () => reject(`iframe with URL ${src} failed to load`); + document.body.appendChild(iframe); + }); +} +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/cross-origin-module-sharing-fails.html b/testing/web-platform/tests/wasm/serialization/module/cross-origin-module-sharing-fails.html new file mode 100644 index 0000000000..cd3e99b9ec --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/cross-origin-module-sharing-fails.html @@ -0,0 +1,38 @@ +<title>Postmessage of a WebAssembly.Module cross-origin fails with a messageerror</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script src="resources/test-incrementer.js"></script> + +<body> +<script> +async function testPostMessageErrorForOrigin(t, remoteOrigin){ + const iframe = document.createElement('iframe'); + iframe.src = `${remoteOrigin}${base_path()}resources/incrementer-iframe-failure.html`; + const iframeLoaded = new Promise(resolve => iframe.onload = resolve); + document.body.appendChild(iframe); + t.add_cleanup(() => { + iframe.remove(); + }); + await iframeLoaded; + + const module = await createWasmModule(); + const messageErrorReceived = + new Promise(resolve => window.onmessage = resolve); + iframe.contentWindow.postMessage({message: 'send module', module}, "*"); + let reply = await messageErrorReceived; + assert_equals('messageerror received', reply.data); +} + +promise_test(async t => { + const remoteOrigin = get_host_info().OTHER_ORIGIN; + await testPostMessageErrorForOrigin(t, remoteOrigin); +}, "postMessaging a wasm module to an iframe in a different agent cluster fails"); + +promise_test(async t => { + const remoteOrigin = get_host_info().HTTPS_ORIGIN; + await testPostMessageErrorForOrigin(t, remoteOrigin); +}, "postMessaging a wasm module to a cross-origin iframe in the same agent cluster fails"); +</script> +</body> diff --git a/testing/web-platform/tests/wasm/serialization/module/identity-not-preserved.html b/testing/web-platform/tests/wasm/serialization/module/identity-not-preserved.html new file mode 100644 index 0000000000..24bb3b16d8 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/identity-not-preserved.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>WebAssembly.Modules, when cloned, do not give back the same object</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; + +async_test(t => { + createWasmModule().then(module => { + window.addEventListener("message", t.step_func(({ data }) => { + if (data.testId !== 1) { + return; + } + + assert_not_equals(data.module, module); + + t.done(); + })); + + window.postMessage({ module, testId: 1 }, "*"); + }); +}, "postMessaging to this window does not give back the same WebAssembly.Module"); + +async_test(t => { + createWasmModule().then(module => { + const worker = new Worker("resources/echo-worker.js"); + + worker.addEventListener("message", t.step_func(({ data }) => { + if (data.testId !== 2) { + return; + } + + assert_not_equals(data.module, module); + t.done(); + })); + + worker.postMessage({ testId: 2, module }); + }); +}, "postMessaging to a worker and back does not give back the same WebAssembly.Module"); + +async_test(t => { + createWasmModule().then(module => { + window.addEventListener("message", t.step_func(({ data }) => { + if (data.testId !== 3) { + return; + } + + assert_not_equals(data.module, module); + t.done(); + })); + + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + iframe.contentWindow.postMessage({ testId: 3, module }, "*"); + }); + iframe.src = "resources/echo-iframe.html"; + document.body.appendChild(iframe); + }); +}, "postMessaging to an iframe and back does not give back the same WebAssembly.Module"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/incrementer.wasm b/testing/web-platform/tests/wasm/serialization/module/incrementer.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/incrementer.wasm diff --git a/testing/web-platform/tests/wasm/serialization/module/nested-worker-success.any.js b/testing/web-platform/tests/wasm/serialization/module/nested-worker-success.any.js new file mode 100644 index 0000000000..84fbd92a29 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/nested-worker-success.any.js @@ -0,0 +1,9 @@ +// META: global=dedicatedworker,sharedworker +// META: script=resources/test-incrementer.js +"use strict"; + +promise_test(t => { + const worker = new Worker("resources/incrementer-worker.js"); + + return testSharingViaIncrementerScript(t, worker, "parent worker", worker, "sub-worker"); +}, "postMessaging to a dedicated sub-worker allows them to see each others' modifications"); diff --git a/testing/web-platform/tests/wasm/serialization/module/no-transferring.html b/testing/web-platform/tests/wasm/serialization/module/no-transferring.html new file mode 100644 index 0000000000..2f0f674b1f --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/no-transferring.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>WebAssembly.Modules cannot be transferred</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/create-empty-wasm-module.js"></script> + +<script> +"use strict"; + +test(() => { + const module = createEmptyWasmModule(); + assert_throws_dom("DataCloneError", () => window.postMessage(module, "*", [module])); + assert_throws_dom("DataCloneError", () => window.postMessage("test", "*", [module])); +}, "Trying to transfer a WebAssembly.Module to this window throws"); + +test(() => { + const module = createEmptyWasmModule(); + const worker = new Worker("resources/echo-worker.js"); + assert_throws_dom("DataCloneError", () => worker.postMessage(module, [module])); + assert_throws_dom("DataCloneError", () => worker.postMessage("test", [module])); +}, "Trying to transfer a WebAssembly.Module to a worker throws"); + +test(() => { + const module = createEmptyWasmModule(); + const channel = new MessageChannel(); + assert_throws_dom("DataCloneError", () => channel.port1.postMessage(module, [module])); + assert_throws_dom("DataCloneError", () => channel.port1.postMessage("test", [module])); +}, "Trying to transfer a WebAssembly.Module through a MessagePort throws"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/blank.html b/testing/web-platform/tests/wasm/serialization/module/resources/blank.html new file mode 100644 index 0000000000..a3c3a4689a --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/blank.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<title>Empty doc</title> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-iframe.html b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-iframe.html new file mode 100644 index 0000000000..83e347b5cb --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-iframe.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that uses a given WebAssembly.Module sent from a BroadcastChannel</title> + +<script> +"use strict"; +const channel = new BroadcastChannel("channel name"); + +channel.onmessage = ({ data: { module, i }, source }) => { + if (!module) { + // We only care about "broadcasts" from the window + return; + } + + let instance = new WebAssembly.Instance(module); + let increment = instance.exports["increment"]; + let result = increment(i); + channel.postMessage({i, result}); +}; +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-sharedworker.js b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-sharedworker.js new file mode 100644 index 0000000000..310e0e9358 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-sharedworker.js @@ -0,0 +1,7 @@ +const channel = new BroadcastChannel("anne was here"); +channel.onmessageerror = ({ data }) => { + if(data === null) { + channel.postMessage("sw-success"); + } +} +channel.postMessage("hi"); diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-worker.js b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-worker.js new file mode 100644 index 0000000000..76a8177060 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/broadcastchannel-worker.js @@ -0,0 +1,9 @@ +const channel = new BroadcastChannel("anne was here"); +channel.onmessage = ({ data }) => { + if(data === "hi" || data === "sw-success") { + return; + } else if(data instanceof WebAssembly.Module) { + channel.postMessage("dw-success"); + } +} +channel.postMessage("hi"); diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/create-empty-wasm-module.js b/testing/web-platform/tests/wasm/serialization/module/resources/create-empty-wasm-module.js new file mode 100644 index 0000000000..7326710c9e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/create-empty-wasm-module.js @@ -0,0 +1,4 @@ +function createEmptyWasmModule() { + return new WebAssembly.Module( + new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])); +} diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/echo-iframe.html b/testing/web-platform/tests/wasm/serialization/module/resources/echo-iframe.html new file mode 100644 index 0000000000..c4fd5824a1 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/echo-iframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that echos back anything postMessaged to it to its parent</title> + +<script> +"use strict"; + +window.onmessage = ({ data }) => { + parent.postMessage(data, "*"); +}; +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/echo-worker.js b/testing/web-platform/tests/wasm/serialization/module/resources/echo-worker.js new file mode 100644 index 0000000000..cbbde8a73c --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/echo-worker.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onmessage = ({ data }) => { + self.postMessage(data); +}; diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html new file mode 100644 index 0000000000..d2d18de499 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that is sent a WebAssembly Module</title> +<script src="/resources/testharness.js"></script> +<script src="test-incrementer.js"></script> + +<script> +"use strict"; + +document.domain = "{{host}}"; +setupDestinationIncrementer(self, parent, "*"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-failure.html b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-failure.html new file mode 100644 index 0000000000..5212a9ec72 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe-failure.html @@ -0,0 +1,5 @@ +<title>A test page that is sent a WebAssembly Module cross-origin cannot receive it</title> + +<script> +self.onmessageerror = () => parent.postMessage('messageerror received', "*"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe.html b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe.html new file mode 100644 index 0000000000..5c8bc0735e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-iframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that is sent a WebAssembly Module</title> +<script src="/resources/testharness.js"></script> +<script src="test-incrementer.js"></script> + +<script> +"use strict"; + +setupDestinationIncrementer(self, parent, "*"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-popup.html b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-popup.html new file mode 100644 index 0000000000..660e472b27 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-popup.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that is sent a WebAssembly Module</title> +<script src="/resources/testharness.js"></script> +<script src="test-incrementer.js"></script> + +<script> +"use strict"; + +setupDestinationIncrementer(self, opener, "*"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker-with-channel.js b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker-with-channel.js new file mode 100644 index 0000000000..0323b3e52e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker-with-channel.js @@ -0,0 +1,8 @@ +"use strict"; +importScripts("/resources/testharness.js"); +importScripts("./test-incrementer.js"); + +self.onmessage = ({ data }) => { + // data will be a MessagePort + setupDestinationIncrementer(data, data); +}; diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker.js b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker.js new file mode 100644 index 0000000000..1779ceea52 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer-worker.js @@ -0,0 +1,5 @@ +"use strict"; +importScripts("/resources/testharness.js"); +importScripts("./test-incrementer.js"); + +setupDestinationIncrementer(self, self); diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/incrementer.wasm b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/incrementer.wasm diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-1.html b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-1.html new file mode 100644 index 0000000000..fe93cc0c4b --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Nesting level 1</title> + +<iframe src="nested-iframe-2.html"></iframe> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-2.html b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-2.html new file mode 100644 index 0000000000..fad52ce9de --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-2.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Nesting level 2</title> + +<iframe src="nested-iframe-3.html"></iframe> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-3.html b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-3.html new file mode 100644 index 0000000000..7971022b2c --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-3.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Nesting level 3</title> + +<iframe src="nested-iframe-4-incrementer.html"></iframe> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-4-incrementer.html b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-4-incrementer.html new file mode 100644 index 0000000000..f419f4bc36 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/nested-iframe-4-incrementer.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>A test page that is sent a WebAssembly Module, nested 4 levels deep in iframes</title> +<script src="/resources/testharness.js"></script> +<script src="test-incrementer.js"></script> + +<script> +"use strict"; + +setupDestinationIncrementer(self, parent.parent.parent.parent.parent, "*"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/serviceworker-failure.js b/testing/web-platform/tests/wasm/serialization/module/resources/serviceworker-failure.js new file mode 100644 index 0000000000..4ef380c253 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/serviceworker-failure.js @@ -0,0 +1,35 @@ +// Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/. +"use strict"; +self.importScripts("/resources/testharness.js"); +self.importScripts("./create-empty-wasm-module.js"); + +let state = "start in worker"; + +self.onmessage = e => { + if (e.data === "start in window") { + assert_equals(state, "start in worker"); + e.source.postMessage(state); + state = "we are expecting a messageerror due to the window sending us a WebAssembly.Module"; + } else if (e.data === "we are expecting a messageerror due to the worker sending us a WebAssembly.Module") { + assert_equals(state, "onmessageerror was received in worker"); + e.source.postMessage(createEmptyWasmModule()); + state = "done in worker"; + } else { + e.source.postMessage(`worker onmessage was reached when in state "${state}" and data ${e.data}`); + } +}; + +self.onmessageerror = e => { + if (state === "we are expecting a messageerror due to the window sending us a WebAssembly.Module") { + assert_equals(e.constructor.name, "ExtendableMessageEvent", "type"); + assert_equals(e.data, null, "data"); + assert_equals(e.origin, self.origin, "origin"); + assert_not_equals(e.source, null, "source"); + assert_equals(e.ports.length, 0, "ports length"); + + state = "onmessageerror was received in worker"; + e.source.postMessage(state); + } else { + e.source.postMessage(`worker onmessageerror was reached when in state "${state}" and data ${e.data}`); + } +}; diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/sharedworker-failure.js b/testing/web-platform/tests/wasm/serialization/module/resources/sharedworker-failure.js new file mode 100644 index 0000000000..854c70b9e8 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/sharedworker-failure.js @@ -0,0 +1,21 @@ +importScripts("./test-incrementer.js"); +importScripts("./create-empty-wasm-module.js"); + +let state = "send-sw-failure" +onconnect = initialE => { + let port = initialE.source; + port.postMessage(state) + port.onmessage = e => { + if(state === "" && e.data === "send-window-failure") { + port.postMessage(createEmptyWasmModule()) + } else { + port.postMessage("failure") + } + } + port.onmessageerror = e => { + if(state === "send-sw-failure") { + port.postMessage("send-sw-failure-success") + state = "" + } + } +} diff --git a/testing/web-platform/tests/wasm/serialization/module/resources/test-incrementer.js b/testing/web-platform/tests/wasm/serialization/module/resources/test-incrementer.js new file mode 100644 index 0000000000..65cb33227a --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/resources/test-incrementer.js @@ -0,0 +1,57 @@ +// Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/. +// +// This file is simplified from the one there, because it only tests that the +// module can be passed and that functions can be run. The SharedArrayBuffer +// version also tests that the memory is shared between the agents. + +"use strict"; + +function createWasmModule() { + return fetch('incrementer.wasm') + .then(response => { + if (!response.ok) + throw new Error(response.statusText); + return response.arrayBuffer(); + }) + .then(WebAssembly.compile); +} + +function testModule(module) { + let instance = new WebAssembly.Instance(module); + let increment = instance.exports["increment"]; + assert_equals(typeof increment, "function", `The type of the increment export should be "function", got ${typeof increment}`); + let result = increment(42); + assert_equals(result, 43, `increment(42) should be 43, got ${result}`); +} + +self.testSharingViaIncrementerScript = (t, whereToListen, whereToListenLabel, whereToSend, whereToSendLabel, origin) => { + return createWasmModule().then(module => { + return new Promise(resolve => { + + whereToListen.onmessage = t.step_func(({ data }) => { + switch (data.message) { + case "module received": { + testModule(data.module); + resolve(); + break; + } + } + }); + + whereToSend.postMessage({ message: "send module", module }, origin); + }); + }); +}; + +self.setupDestinationIncrementer = (whereToListen, whereToSendBackTo, origin) => { + whereToListen.onmessage = ({ data }) => { + switch (data.message) { + case "send module": { + let module = data.module; + testModule(data.module); + whereToSendBackTo.postMessage({ message: "module received", module }, origin); + break; + } + } + }; +}; diff --git a/testing/web-platform/tests/wasm/serialization/module/serialization-via-history.html b/testing/web-platform/tests/wasm/serialization/module/serialization-via-history.html new file mode 100644 index 0000000000..38d4301d70 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/serialization-via-history.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>WebAssembly.Module cloning via history's methods invoking StructuredSerializeForStorage</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/create-empty-wasm-module.js"></script> + +<script> +"use strict"; + +for (const method of ["pushState", "replaceState"]) { + test(() => { + assert_throws_dom("DataCloneError", () => { + history[method](createEmptyWasmModule(), "dummy title"); + }); + }, `history.${method}(): simple case`); + + test(() => { + let getter1Called = false; + let getter2Called = false; + assert_throws_dom("DataCloneError", () => { + history[method]([ + { get x() { getter1Called = true; return 5; } }, + createEmptyWasmModule(), + { get x() { getter2Called = true; return 5; } } + ], "dummy title"); + }); + + assert_true(getter1Called, "The getter before the WebAssembly.Module must have been called"); + assert_false(getter2Called, "The getter after the WebAssembly.Module must not have been called"); + }, `history.${method}(): is interleaved correctly`); +} +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/serialization-via-idb.any.js b/testing/web-platform/tests/wasm/serialization/module/serialization-via-idb.any.js new file mode 100644 index 0000000000..2591d2fe74 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/serialization-via-idb.any.js @@ -0,0 +1,45 @@ +// META: script=/IndexedDB/resources/support.js +"use strict"; + +function createEmptyWasmModule() { + return new WebAssembly.Module( + new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])); +} + +async_test(t => { + const openReq = createdb(t); + + openReq.onupgradeneeded = e => { + const db = e.target.result; + const store = db.createObjectStore("store", { keyPath: "key" }); + + assert_throws_dom("DataCloneError", () => { + store.put({ key: 1, property: createEmptyWasmModule() }); + }); + t.done(); + }; +}, "WebAssembly.Module cloning via IndexedDB: basic case"); + +async_test(t => { + const openReq = createdb(t); + + openReq.onupgradeneeded = e => { + const db = e.target.result; + const store = db.createObjectStore("store", { keyPath: "key" }); + + let getter1Called = false; + let getter2Called = false; + + assert_throws_dom("DataCloneError", () => { + store.put({ key: 1, property: [ + { get x() { getter1Called = true; return 5; } }, + createEmptyWasmModule(), + { get x() { getter2Called = true; return 5; } } + ]}); + }); + + assert_true(getter1Called, "The getter before the WebAssembly.Module must have been called"); + assert_false(getter2Called, "The getter after the WebAssembly.Module must not have been called"); + t.done(); + }; +}, "WebAssembly.Module cloning via the IndexedDB: is interleaved correctly"); diff --git a/testing/web-platform/tests/wasm/serialization/module/serialization-via-notifications-api.any.js b/testing/web-platform/tests/wasm/serialization/module/serialization-via-notifications-api.any.js new file mode 100644 index 0000000000..3672192df7 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/serialization-via-notifications-api.any.js @@ -0,0 +1,28 @@ +"use strict"; + +function createEmptyWasmModule() { + return new WebAssembly.Module( + new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])); +} + +test(() => { + assert_throws_dom("DataCloneError", () => { + new Notification("Bob: Hi", { data: createEmptyWasmModule() }); + }) +}, "WebAssembly.Module cloning via the Notifications API's data member: basic case"); + +test(() => { + let getter1Called = false; + let getter2Called = false; + + assert_throws_dom("DataCloneError", () => { + new Notification("Bob: Hi", { data: [ + { get x() { getter1Called = true; return 5; } }, + createEmptyWasmModule(), + { get x() { getter2Called = true; return 5; } } + ]}); + }); + + assert_true(getter1Called, "The getter before the SAB must have been called"); + assert_false(getter2Called, "The getter after the SAB must not have been called"); +}, "WebAssembly.Module cloning via the Notifications API's data member: is interleaved correctly"); diff --git a/testing/web-platform/tests/wasm/serialization/module/share-module-cross-origin-fails.sub.html b/testing/web-platform/tests/wasm/serialization/module/share-module-cross-origin-fails.sub.html new file mode 100644 index 0000000000..5a52b96698 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/share-module-cross-origin-fails.sub.html @@ -0,0 +1,31 @@ +<title>Postmessage of a WebAssembly.Module cross-origin fails with a messageerror</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<body></body> + +<script> +promise_test(async t => { + // Create an iframe. It will notify this document when the "messageerror" + // event is triggered. + const iframe = document.createElement("iframe"); + iframe.src = "//{{domains[www1]}}:{{location[port]}}" + + "/wasm/serialization/module/resources/incrementer-iframe-failure.html"; + + // Instantiate and wait for the document to be loaded. + const iframeLoaded = new Promise(resolve => iframe.onload = resolve); + document.body.appendChild(iframe); + await iframeLoaded; + + const module = await createWasmModule(); + const messageErrorReceived = new Promise(resolve => { + window.onmessage = ({data}) => { + if (data == 'messageerror received') + resolve(); + }; + }); + iframe.contentWindow.postMessage({message: 'send module', module}, "*"); + await messageErrorReceived; +}, "postMessaging a wasm module to a cross-origin iframe fails"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-domain-success.sub.html b/testing/web-platform/tests/wasm/serialization/module/window-domain-success.sub.html new file mode 100644 index 0000000000..07360d8264 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-domain-success.sub.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>Structured cloning of WebAssembly.Module into same-origin-domain windows</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; +document.domain = "{{host}}"; + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*")); + }); + iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html"; + document.body.appendChild(iframe); + }); +}, "postMessaging to a same-origin-domain (but not same-origin) iframe allows them to instantiate"); + +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-messagechannel-success.html b/testing/web-platform/tests/wasm/serialization/module/window-messagechannel-success.html new file mode 100644 index 0000000000..e686c81135 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-messagechannel-success.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>Structured cloning of WebAssembly.Module using MessageChannel</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + const worker = new Worker("resources/incrementer-worker-with-channel.js"); + const channel = new MessageChannel(); + worker.postMessage(channel.port2, [channel.port2]); + + return testSharingViaIncrementerScript(t, channel.port1, "window", channel.port1, "worker"); +}, "postMessaging to a dedicated worker via MessageChannel allows them to instantiate"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-serviceworker-failure.https.html b/testing/web-platform/tests/wasm/serialization/module/window-serviceworker-failure.https.html new file mode 100644 index 0000000000..d7285e2fa3 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-serviceworker-failure.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<title>WebAssembly.Module cannot cross agent clusters, service worker edition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script src="./resources/create-empty-wasm-module.js"></script> + +<script> +"use strict"; +promise_test(t => { + const scope = "resources/blank.html"; + return service_worker_unregister_and_register(t, "resources/serviceworker-failure.js", scope) + .then(reg => { + t.add_cleanup(() => service_worker_unregister(t, scope)); + return wait_for_state(t, reg.installing, "activated"); + }) + .then(() => with_iframe(scope)) + .then(iframe => { + t.add_cleanup(() => iframe.remove()); + const sw = iframe.contentWindow.navigator.serviceWorker; + let state = "start in window"; + + return new Promise(resolve => { + sw.onmessage = t.step_func(e => { + if (e.data === "start in worker") { + assert_equals(state, "start in window"); + sw.controller.postMessage(createEmptyWasmModule()); + state = "we are expecting confirmation of an onmessageerror in the worker"; + } else if (e.data === "onmessageerror was received in worker") { + assert_equals(state, "we are expecting confirmation of an onmessageerror in the worker"); + state = "we are expecting a messageerror due to the worker sending us a WebAssembly.Module"; + sw.controller.postMessage(state); + } else { + assert_unreached("Got an unexpected message from the service worker: " + e.data); + } + }); + + sw.onmessageerror = t.step_func(e => { + assert_equals(state, "we are expecting a messageerror due to the worker sending us a WebAssembly.Module"); + + assert_equals(e.data, null, "data"); + assert_equals(e.origin, self.origin, "origin"); + assert_not_equals(e.source, null, "source"); + assert_equals(e.ports.length, 0, "ports length"); + + state = "done in window"; + resolve(); + }); + + sw.controller.postMessage(state); + }); + }); +}); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-sharedworker-failure.html b/testing/web-platform/tests/wasm/serialization/module/window-sharedworker-failure.html new file mode 100644 index 0000000000..667e985a30 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-sharedworker-failure.html @@ -0,0 +1,33 @@ +<!doctype html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<title>WebAssembly.Modules cannot cross agent clusters, shared worker edition</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/create-empty-wasm-module.js"></script> +<script> +async_test(t => { + const sw = new SharedWorker("resources/sharedworker-failure.js") + let state = "" + sw.port.onmessage = t.step_func(e => { + if(e.data === "send-sw-failure") { + sw.port.postMessage(createEmptyWasmModule()) + } else if(e.data === "send-sw-failure-success") { + state = "send-window-failure" + sw.port.postMessage(state) + } else { + assert_unreached() + } + }) + sw.port.onmessageerror = t.step_func(e => { + if(state === "send-window-failure") { + assert_equals(e.data, null, "data") + assert_equals(e.origin, "", "origin") + assert_equals(e.source, null, "source") + assert_equals(e.ports.length, 0, "ports length") + t.done() + } else { + assert_unreached() + } + }) +}) +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html b/testing/web-platform/tests/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html new file mode 100644 index 0000000000..a615547de0 --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>Structured cloning of WebAssembly.Module to similar-origin, but not same-origin, windows</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; +document.domain = "{{host}}"; + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*")); + }); + iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/module/resources/incrementer-iframe.html"; + document.body.appendChild(iframe); + }); +}, "postMessaging to a not same-origin-domain, but similar origin, iframe allows them to instantiate"); +</script> diff --git a/testing/web-platform/tests/wasm/serialization/module/window-simple-success.html b/testing/web-platform/tests/wasm/serialization/module/window-simple-success.html new file mode 100644 index 0000000000..6f2ccf465e --- /dev/null +++ b/testing/web-platform/tests/wasm/serialization/module/window-simple-success.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<!-- Based on similar tests in html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/ --> +<meta charset="utf-8"> +<title>Structured cloning of WebAssembly.Module: simple success cases that don't need dedicated files</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/test-incrementer.js"></script> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + const worker = new Worker("resources/incrementer-worker.js"); + + return testSharingViaIncrementerScript(t, worker, "window", worker, "worker", undefined); +}, "postMessaging to a dedicated worker allows them to instantiate"); + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*")); + }); + iframe.src = "resources/incrementer-iframe.html"; + document.body.appendChild(iframe); + }); +}, "postMessaging to a same-origin iframe allows them to instantiate"); + +promise_test(t => { + return new Promise(resolve => { + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func(() => { + const level1 = iframe.contentWindow; + const level2 = level1.frames[0]; + const level3 = level2.frames[0]; + const targetWindow = level3.frames[0]; + resolve(testSharingViaIncrementerScript(t, window, "window", targetWindow, "nested iframe", "*")); + }); + iframe.src = "resources/nested-iframe-1.html"; + document.body.appendChild(iframe); + }); +}, "postMessaging to a same-origin deeply-nested iframe allows them to instantiate"); + +promise_test(t => { + return new Promise(resolve => { + const w = window.open("resources/incrementer-popup.html"); + w.onload = t.step_func(() => { + resolve(testSharingViaIncrementerScript(t, window, "window", w, "popup window", "*").then(() => { + w.close(); + })); + }); + }); +}, "postMessaging to a same-origin opened window allows them to instantiate"); + +</script> diff --git a/testing/web-platform/tests/wasm/webapi/META.yml b/testing/web-platform/tests/wasm/webapi/META.yml new file mode 100644 index 0000000000..69715cd7c8 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/META.yml @@ -0,0 +1 @@ +spec: https://webassembly.github.io/spec/web-api/ diff --git a/testing/web-platform/tests/wasm/webapi/abort.any.js b/testing/web-platform/tests/wasm/webapi/abort.any.js new file mode 100644 index 0000000000..f5ddd353aa --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/abort.any.js @@ -0,0 +1,37 @@ +const methods = [ + "compileStreaming", + "instantiateStreaming", +]; + +for (const method of methods) { + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + const request = fetch('../incrementer.wasm', { signal }); + return promise_rejects_dom(t, 'AbortError', WebAssembly[method](request), + `${method} should reject`); + }, `${method}() on an already-aborted request should reject with AbortError`); + + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + const request = fetch('../incrementer.wasm', { signal }); + const promise = WebAssembly[method](request); + controller.abort(); + return promise_rejects_dom(t, 'AbortError', promise, `${method} should reject`); + }, `${method}() synchronously followed by abort should reject with AbortError`); + + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + return fetch('../incrementer.wasm', { signal }) + .then(response => { + Promise.resolve().then(() => controller.abort()); + return WebAssembly[method](response); + }) + .catch(err => { + assert_equals(err.name, "AbortError"); + }); + }, `${method}() asynchronously racing with abort should succeed or reject with AbortError`); +} diff --git a/testing/web-platform/tests/wasm/webapi/body.any.js b/testing/web-platform/tests/wasm/webapi/body.any.js new file mode 100644 index 0000000000..4db7e8d123 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/body.any.js @@ -0,0 +1,19 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + argument.arrayBuffer(); + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method} after consumption`); + + promise_test(t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + const promise = WebAssembly[method](argument); + argument.arrayBuffer(); + return promise_rejects_js(t, TypeError, promise); + }, `${method} before consumption`); +} diff --git a/testing/web-platform/tests/wasm/webapi/contenttype.any.js b/testing/web-platform/tests/wasm/webapi/contenttype.any.js new file mode 100644 index 0000000000..0a2f5f1122 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/contenttype.any.js @@ -0,0 +1,64 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/assertions.js + +promise_test(t => { + const response = fetch("/wasm/incrementer.wasm").then(res => new Response(res.body)); + return promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(response)); +}, "Response with no Content-Type: compileStreaming"); + +promise_test(t => { + const response = fetch("/wasm/incrementer.wasm").then(res => new Response(res.body)); + return promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(response)); +}, "Response with no Content-Type: instantiateStreaming"); + +const invalidContentTypes = [ + "", + "application/javascript", + "application/octet-stream", + "text/wasm", + "application/wasm;", + "application/wasm;x", + "application/wasm;charset=UTF-8", +]; + +for (const contenttype of invalidContentTypes) { + promise_test(t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + return promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(response)); + }, `Response with Content-Type ${format_value(contenttype)}: compileStreaming`); + + promise_test(t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + return promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(response)); + }, `Response with Content-Type ${format_value(contenttype)}: instantiateStreaming`); +} + +const validContentTypes = [ + "application/wasm", + "APPLICATION/wasm", + "APPLICATION/WASM", +]; + +for (const contenttype of validContentTypes) { + promise_test(async t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + const module = await WebAssembly.compileStreaming(response); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype, + "prototype"); + }, `Response with Content-Type ${format_value(contenttype)}: compileStreaming`); + + promise_test(async t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + const result = await WebAssembly.instantiateStreaming(response); + assert_WebAssemblyInstantiatedSource( + result, + { + "increment": { + "kind": "function", + "name": "0", + "length": 1 + } + } + ); + }, `Response with Content-Type ${format_value(contenttype)}: instantiateStreaming`); +} diff --git a/testing/web-platform/tests/wasm/webapi/empty-body.any.js b/testing/web-platform/tests/wasm/webapi/empty-body.any.js new file mode 100644 index 0000000000..0771647b70 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/empty-body.any.js @@ -0,0 +1,20 @@ +// META: global=window,worker + +const invalidArguments = [ + [() => new Response(undefined, { headers: { "Content-Type": "application/wasm" } }), "no body"], + [() => new Response("", { headers: { "Content-Type": "application/wasm" } }), "empty body"], +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const [argumentFactory, name] of invalidArguments) { + promise_test(t => { + const argument = argumentFactory(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](argument)); + }, `${method}: ${name}`); + + promise_test(t => { + const argument = Promise.resolve(argumentFactory()); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](argument)); + }, `${method}: ${name} in a promise`); + } +} diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/execute-start.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/execute-start.tentative.html new file mode 100644 index 0000000000..a35adbe8eb --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/execute-start.tentative.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>Check execution of WebAssembly start function</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + const test_load = async_test( + "Importing a WebAssembly module should execute the start function."); + + window.log = []; + window.addEventListener("error", ev => { + log.push(ev.message); + }); + + window.addEventListener("load", test_load.step_func_done(ev => { + assert_array_equals(log, ["executed"]); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/execute-start.wasm" onerror="unreachable()""></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/exported-names.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/exported-names.tentative.html new file mode 100644 index 0000000000..16a9c59787 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/exported-names.tentative.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>Exported names from a WebAssembly module</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +import * as mod from "./resources/exported-names.wasm"; +assert_array_equals(Object.getOwnPropertyNames(mod).sort(), + ["func", "glob", "mem", "tab"]); +assert_true(mod.func instanceof Function); +assert_true(mod.mem instanceof WebAssembly.Memory); +assert_true(mod.glob instanceof WebAssembly.Global); +assert_true(mod.tab instanceof WebAssembly.Table); +assert_throws_js(TypeError, () => { mod.func = 2; }); +done(); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/invalid-bytecode.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/invalid-bytecode.tentative.html new file mode 100644 index 0000000000..0e447dbee5 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/invalid-bytecode.tentative.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Handling of importing invalid WebAssembly modules</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + window.log = []; + + window.addEventListener("error", ev => log.push(ev.error)); + + const test_load = async_test( + "Test that imports of invalid WebAssembly modules leads to WebAssembly.CompileError on window."); + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log.length, 2); + assert_equals(log[0].constructor, WebAssembly.CompileError); + assert_equals(log[1].constructor, WebAssembly.CompileError); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/invalid-bytecode.wasm" onerror="unreachable()"></script> +<script type="module" src="./resources/invalid-module.wasm" onerror="unreachable()"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html new file mode 100644 index 0000000000..f45e06ece5 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html @@ -0,0 +1,38 @@ +<!doctype html> +<title>Cyclic linking between JavaScript and WebAssembly (JS higher)</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + const test_load = async_test( + "Check cyclic linking between JavaScript and WebAssembly where JavaScript is higher in the module graph."); + + window.log = []; + window.addEventListener("error", ev => { + test_load.step(() => assert_equals(ev.error.constructor, WebAssembly.LinkError)); + log.push(ev.message); + }); + + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log.length, 10); + assert_equals(log[1], 1); + assert_equals(log[3], 2); + assert_equals(log[5], 3); + assert_equals(log[7], 4); + assert_equals(log[9], 5); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/js-wasm-cycle-value.js" + onerror="unreachable()" onload="log.push(1)"></script> +<script type="module" src="./resources/js-wasm-cycle-global.js" + onerror="unreachable()" onload="log.push(2)"></script> +<script type="module" src="./resources/js-wasm-cycle-memory.js" + onerror="unreachable()" onload="log.push(3)"></script> +<script type="module" src="./resources/js-wasm-cycle-table.js" + onerror="unreachable()" onload="log.push(4)"></script> +<script type="module" src="./resources/js-wasm-cycle-function-error.js" + onerror="unreachable()" onload="log.push(5)"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html new file mode 100644 index 0000000000..38b0d3203c --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>Check bindings in JavaScript and WebAssembly cycle (JS higher)</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +import { f } from "./resources/js-wasm-cycle.js"; +assert_equals(f(), 24); +done(); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/module-parse-error.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/module-parse-error.tentative.html new file mode 100644 index 0000000000..0e447dbee5 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/module-parse-error.tentative.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Handling of importing invalid WebAssembly modules</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + window.log = []; + + window.addEventListener("error", ev => log.push(ev.error)); + + const test_load = async_test( + "Test that imports of invalid WebAssembly modules leads to WebAssembly.CompileError on window."); + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log.length, 2); + assert_equals(log[0].constructor, WebAssembly.CompileError); + assert_equals(log[1].constructor, WebAssembly.CompileError); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/invalid-bytecode.wasm" onerror="unreachable()"></script> +<script type="module" src="./resources/invalid-module.wasm" onerror="unreachable()"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.js new file mode 100644 index 0000000000..e0dcf493f8 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.js @@ -0,0 +1 @@ +export { f } from "./resources/resolve-export.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.tentative.html new file mode 100644 index 0000000000..1468822102 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resolve-export.tentative.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Check ResolveExport on invalid re-export from WebAssembly</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + const test_load = async_test( + "Re-export of missing Wasm export should result in SyntaxError."); + + window.log = []; + window.addEventListener("error", ev => { + test_load.step(() => assert_equals(ev.error.constructor, SyntaxError)); + log.push(ev.message); + }); + + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log[1], 1); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resolve-export.js" + onerror="unreachable()" onload="log.push(1)"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/execute-start.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/execute-start.wasm Binary files differnew file mode 100644 index 0000000000..ecfdda1f9a --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/execute-start.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/exported-names.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/exported-names.wasm Binary files differnew file mode 100644 index 0000000000..ebffad193c --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/exported-names.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm Binary files differnew file mode 100644 index 0000000000..1ae8b721f3 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-module.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-module.wasm Binary files differnew file mode 100644 index 0000000000..dd711f0953 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/invalid-module.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js new file mode 100644 index 0000000000..06cb8a0ad9 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js @@ -0,0 +1,2 @@ +export const func = 42; +import { f } from "./js-wasm-cycle-function-error.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm Binary files differnew file mode 100644 index 0000000000..b89d94dde7 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js new file mode 100644 index 0000000000..1f375b8ce1 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js @@ -0,0 +1,2 @@ +export const glob = new WebAssembly.Global({ value: "i32" }, 42); +import { f } from "./js-wasm-cycle-global.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm Binary files differnew file mode 100644 index 0000000000..2a9017f87b --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js new file mode 100644 index 0000000000..92e37a86ac --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js @@ -0,0 +1,2 @@ +export const mem = new WebAssembly.Memory({ initial: 10 }); +import { f } from "./js-wasm-cycle-memory.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm Binary files differnew file mode 100644 index 0000000000..e699a9b3c4 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js new file mode 100644 index 0000000000..5d6794489f --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js @@ -0,0 +1,2 @@ +export const tab = new WebAssembly.Table({ element: "anyfunc" }); +import { f } from "./js-wasm-cycle-table.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm Binary files differnew file mode 100644 index 0000000000..ec4883e652 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js new file mode 100644 index 0000000000..f7b0d62080 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js @@ -0,0 +1,2 @@ +export const val = 42; +import { f } from "./js-wasm-cycle-value.wasm"; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm Binary files differnew file mode 100644 index 0000000000..083409e260 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.js new file mode 100644 index 0000000000..8ee579e2ad --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.js @@ -0,0 +1,13 @@ +function f() { return 42; } +export { f }; + +import { mem, tab, glob, func } from "./js-wasm-cycle.wasm"; +assert_true(glob instanceof WebAssembly.Global); +assert_equals(glob.valueOf(), 1); +assert_true(mem instanceof WebAssembly.Memory); +assert_true(tab instanceof WebAssembly.Table); +assert_true(func instanceof Function); + +f = () => { return 24 }; + +assert_equals(func(), 42); diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm Binary files differnew file mode 100644 index 0000000000..77a3b86ab6 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/log.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/log.js new file mode 100644 index 0000000000..0c4f5ed519 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/log.js @@ -0,0 +1 @@ +export function logExec() { log.push("executed"); } diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/resolve-export.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/resolve-export.wasm Binary files differnew file mode 100644 index 0000000000..d8fc92d022 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/resolve-export.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm Binary files differnew file mode 100644 index 0000000000..f9f0cf2799 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm Binary files differnew file mode 100644 index 0000000000..0ee948f96f --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm Binary files differnew file mode 100644 index 0000000000..c27bcb068d --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm Binary files differnew file mode 100644 index 0000000000..652ff14310 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.js new file mode 100644 index 0000000000..78982c32dc --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.js @@ -0,0 +1 @@ +export let f = 5; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.wasm Binary files differnew file mode 100644 index 0000000000..2f23c58520 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-func.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.js new file mode 100644 index 0000000000..4258cd2d7d --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.js @@ -0,0 +1 @@ +export let g = 5; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.wasm Binary files differnew file mode 100644 index 0000000000..2f8bd77940 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-global.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.js new file mode 100644 index 0000000000..4cee889838 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.js @@ -0,0 +1 @@ +export let m = 5; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm Binary files differnew file mode 100644 index 0000000000..d9474047cd --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.js new file mode 100644 index 0000000000..ca823646cb --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.js @@ -0,0 +1 @@ +export let t = 5; diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.wasm Binary files differnew file mode 100644 index 0000000000..8ccc8be7f2 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-import-table.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.js new file mode 100644 index 0000000000..161edab4f6 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.js @@ -0,0 +1,15 @@ +import * as mod from "./wasm-js-cycle.wasm"; + +let jsGlob = new WebAssembly.Global({ value: "i32", mutable: true }, 42); +let jsMem = new WebAssembly.Memory({ initial: 10 }); +let jsTab = new WebAssembly.Table({ initial: 10, element: "anyfunc" }); +let jsFunc = () => { return 42; }; + +export { jsGlob, jsMem, jsTab, jsFunc }; + +export function mutateBindings() { + jsGlob = 0; + jsMem = 0; + jsTab = 0; + jsFunc = 0; +} diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm Binary files differnew file mode 100644 index 0000000000..b700377b27 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-helper.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-helper.js new file mode 100644 index 0000000000..277bb4c1ea --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-helper.js @@ -0,0 +1 @@ +export function pm(x) { postMessage(x); } diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-source-phase.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-source-phase.js new file mode 100644 index 0000000000..c7a4f0d437 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker-source-phase.js @@ -0,0 +1,3 @@ +import source modSource from "./worker.wasm"; +assert_true(modSource instanceof WebAssembly.Module); +assert_true(await import.source("./worker.wasm") === modSource); diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.js b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.js new file mode 100644 index 0000000000..1163e477fd --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.js @@ -0,0 +1,2 @@ +import * as mod from "./worker.wasm"; +assert_true(await import("./worker.wasm") === mod); diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.wasm b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.wasm Binary files differnew file mode 100644 index 0000000000..e942dc54ac --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/resources/worker.wasm diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/source-phase.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/source-phase.tentative.html new file mode 100644 index 0000000000..eb415a4c6f --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/source-phase.tentative.html @@ -0,0 +1,35 @@ +<!doctype html> +<title>Source phase imports</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); + +import source exportedNamesSource from "./resources/exported-names.wasm"; + +assert_true(exportedNamesSource instanceof WebAssembly.Module); +const AbstractModuleSource = Object.getPrototypeOf(WebAssembly.Module); +assert_equals(AbstractModuleSource.name, "AbstractModuleSource"); +assert_true(exportedNamesSource instanceof AbstractModuleSource); + +assert_array_equals(WebAssembly.Module.exports(exportedNamesSource).sort(), + ["func", "glob", "mem", "tab"]); + +const wasmImportFromWasmSource = await import.source("./resources/wasm-import-from-wasm.wasm"); + +assert_true(wasmImportFromWasmSource instanceof WebAssembly.Module); + +let logged = false; +const instance = await WebAssembly.instantiate(wasmImportFromWasmSource, { + "./wasm-export-to-wasm.wasm": { + log () { + logged = true; + } + } +}); +instance.exports.logExec(); +assert_true(logged); + +done(); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html new file mode 100644 index 0000000000..3761a22f21 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>Check import and export between WebAssembly modules</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +window.log = []; +import { logExec } from "./resources/wasm-import-from-wasm.wasm"; +logExec(); +assert_equals(log.length, 1); +assert_equals(log[0], "executed"); +done(); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import.tentative.html new file mode 100644 index 0000000000..243cfd46e4 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-import.tentative.html @@ -0,0 +1,34 @@ +<!doctype html> +<title>Errors for imports of WebAssembly modules</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + const test_load = async_test( + "Invalid imports for WebAssembly modules should error."); + + window.log = []; + window.addEventListener("error", ev => { + test_load.step(() => assert_equals(ev.error.constructor, WebAssembly.LinkError)); + log.push(ev.message); + }); + + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log[1], 1); + assert_equals(log[3], 2); + assert_equals(log[5], 3); + assert_equals(log[7], 4); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/wasm-import-func.wasm" + onerror="unreachable()" onload="log.push(1)"></script> +<script type="module" src="./resources/wasm-import-memory.wasm" + onerror="unreachable()" onload="log.push(2)"></script> +<script type="module" src="./resources/wasm-import-table.wasm" + onerror="unreachable()" onload="log.push(3)"></script> +<script type="module" src="./resources/wasm-import-global.wasm" + onerror="unreachable()" onload="log.push(4)"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html new file mode 100644 index 0000000000..298d4d40b0 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html @@ -0,0 +1,32 @@ +<!doctype html> +<title>Check bindings in JavaScript and WebAssembly cycle (Wasm higher)</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +import * as wasm from "./resources/wasm-js-cycle.wasm"; +import * as js from "./resources/wasm-js-cycle.js"; + +js.mutateBindings(); + +assert_true(wasm.wasmGlob instanceof WebAssembly.Global); +assert_equals(wasm.wasmGlob.valueOf(), 24); + +assert_true(wasm.wasmFunc instanceof Function); +assert_equals(wasm.wasmFunc(), 43); + +assert_equals(wasm.incrementGlob(), 43); + +const buf = new Int32Array(wasm.wasmMem.buffer); +assert_equals(buf[0], 0); +assert_equals(wasm.mutateMem(), 42); +assert_equals(buf[0], 42); + +assert_equals(wasm.wasmTab.get(0), null); +const ref = wasm.mutateTab(); +assert_true(ref instanceof Function); +assert_equals(wasm.wasmTab.get(0), ref); + +done(); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html new file mode 100644 index 0000000000..6c43e72b09 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Errors for linking WebAssembly module scripts</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({allow_uncaught_exception: true}); + + const test_load = async_test( + "Link errors for imports between WebAssembly modules should be reported."); + + window.log = []; + window.addEventListener("error", ev => { + test_load.step(() => assert_equals(ev.error.constructor, WebAssembly.LinkError)); + log.push(ev.message); + }); + + window.addEventListener("load", test_load.step_func_done(ev => { + assert_equals(log.length, 2); + assert_equals(log[1], 1); + })); + + function unreachable() { log.push("unexpected"); } +</script> +<script type="module" src="./resources/wasm-import-error-from-wasm.wasm" + onerror="unreachable()" onload="log.push(1)"></script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/worker-import.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/worker-import.tentative.html new file mode 100644 index 0000000000..739f2d3f28 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/worker-import.tentative.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<title>Testing import of WebAssembly from JavaScript worker</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +const worker = new Worker("resources/worker.js", { type: "module" }); +worker.onmessage = (msg) => { + assert_equals(msg.data, 42); + done(); +} +</script> diff --git a/testing/web-platform/tests/wasm/webapi/esm-integration/worker.tentative.html b/testing/web-platform/tests/wasm/webapi/esm-integration/worker.tentative.html new file mode 100644 index 0000000000..6145dd04ff --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/esm-integration/worker.tentative.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<title>Testing WebAssembly worker</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type=module> +setup({ single_test: true }); +const worker = new Worker("resources/worker.wasm", { type: "module" }); +worker.onmessage = (msg) => { + assert_equals(msg, 42); + done(); +} +worker.onerror = () => { + assert_unreached("worker got an error"); +} +</script> diff --git a/testing/web-platform/tests/wasm/webapi/historical.any.js b/testing/web-platform/tests/wasm/webapi/historical.any.js new file mode 100644 index 0000000000..257112c416 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/historical.any.js @@ -0,0 +1,29 @@ +// META: global=window,worker + +promise_test(async t => { + const db_name = "WebAssembly"; + const obj_store = "store"; + const module_key = "module"; + + await new Promise((resolve, reject) => { + const delete_request = indexedDB.deleteDatabase(db_name); + delete_request.onsuccess = resolve; + delete_request.onerror = reject; + }); + + const db = await new Promise((resolve, reject) => { + const open_request = indexedDB.open(db_name); + open_request.onupgradeneeded = function() { + open_request.result.createObjectStore(obj_store); + }; + open_request.onsuccess = function() { + resolve(open_request.result); + }; + open_request.onerror = reject; + }); + + const mod = await WebAssembly.compileStreaming(fetch('../incrementer.wasm')); + const tx = db.transaction(obj_store, 'readwrite'); + const store = tx.objectStore(obj_store); + assert_throws_dom("DataCloneError", () => store.put(mod, module_key)); +}); diff --git a/testing/web-platform/tests/wasm/webapi/idlharness.any.js b/testing/web-platform/tests/wasm/webapi/idlharness.any.js new file mode 100644 index 0000000000..0c4669e6ca --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/idlharness.any.js @@ -0,0 +1,10 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +"use strict"; + +idl_test( + ["wasm-web-api"], + ["wasm-js-api"], + idl_array => {} +); diff --git a/testing/web-platform/tests/wasm/webapi/instantiateStreaming-bad-imports.any.js b/testing/web-platform/tests/wasm/webapi/instantiateStreaming-bad-imports.any.js new file mode 100644 index 0000000000..38ecc40252 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/instantiateStreaming-bad-imports.any.js @@ -0,0 +1,13 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...args) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, error, WebAssembly.instantiateStreaming(response, ...args)); + }, name); +}); diff --git a/testing/web-platform/tests/wasm/webapi/instantiateStreaming.any.js b/testing/web-platform/tests/wasm/webapi/instantiateStreaming.any.js new file mode 100644 index 0000000000..cf3a5e7331 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/instantiateStreaming.any.js @@ -0,0 +1,49 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +for (const [name, fn] of instanceTestFactory) { + promise_test(async () => { + const { buffer, args, exports, verify } = fn(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + const result = await WebAssembly.instantiateStreaming(response, ...args); + assert_WebAssemblyInstantiatedSource(result, exports); + verify(result.instance); + }, name); +} + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiateStreaming(response, imports); + assert_array_equals(order, []); + const result = await p; + assert_WebAssemblyInstantiatedSource(result, {}); + assert_array_equals(order, expected); +}, "Synchronous options handling"); diff --git a/testing/web-platform/tests/wasm/webapi/invalid-args.any.js b/testing/web-platform/tests/wasm/webapi/invalid-args.any.js new file mode 100644 index 0000000000..b27e018a98 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/invalid-args.any.js @@ -0,0 +1,28 @@ +// META: global=window,worker + +const invalidArguments = [ + [undefined], + [null], + [true], + ["test"], + [Symbol()], + [0], + [0.1], + [NaN], + [{}, "Empty object"], + [Response, "Response interface object"], + [Response.prototype, "Response interface prototype object"], +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const [argument, name = format_value(argument)] of invalidArguments) { + promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method}: ${name}`); + + promise_test(t => { + const promise = Promise.resolve(argument); + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method}: ${name} in a promise`); + } +} diff --git a/testing/web-platform/tests/wasm/webapi/invalid-code.any.js b/testing/web-platform/tests/wasm/webapi/invalid-code.any.js new file mode 100644 index 0000000000..37373d4997 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/invalid-code.any.js @@ -0,0 +1,21 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + const response = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](response)); + }, `Invalid code (0x0000): ${method}`); + + promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0xCA, 0xFE])); + const response = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](response)); + }, `Invalid code (0xCAFE): ${method}`); +} diff --git a/testing/web-platform/tests/wasm/webapi/modified-contenttype.any.js b/testing/web-platform/tests/wasm/webapi/modified-contenttype.any.js new file mode 100644 index 0000000000..354930517c --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/modified-contenttype.any.js @@ -0,0 +1,24 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +["compileStreaming", "instantiateStreaming"].forEach(method => { + promise_test(async t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "test/test" } }); + argument.headers.set("Content-Type", "application/wasm"); + // This should resolve successfully + await WebAssembly[method](argument); + // Ensure body can only be read once + return promise_rejects_js(t, TypeError, argument.blob()); + }, `${method} with Content-Type set late`); + + promise_test(async t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + argument.headers.delete("Content-Type"); + // Ensure Wasm cannot be created + await promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + // This should resolve successfully + await argument.arrayBuffer(); + }, `${method} with Content-Type removed late`); +}); diff --git a/testing/web-platform/tests/wasm/webapi/origin.sub.any.js b/testing/web-platform/tests/wasm/webapi/origin.sub.any.js new file mode 100644 index 0000000000..bf7901eedd --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/origin.sub.any.js @@ -0,0 +1,15 @@ +// META: global=window,worker + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const url = "http://{{domains[www]}}:{{ports[http][0]}}/wasm/incrementer.wasm"; + const response = fetch(url, { "mode": "no-cors" }); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Opaque response: ${method}`); + + promise_test(t => { + const url = "/fetch/api/resources/redirect.py?redirect_status=301&location=/wasm/incrementer.wasm"; + const response = fetch(url, { "mode": "no-cors", "redirect": "manual" }); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Opaque redirect response: ${method}`); +} diff --git a/testing/web-platform/tests/wasm/webapi/rejected-arg.any.js b/testing/web-platform/tests/wasm/webapi/rejected-arg.any.js new file mode 100644 index 0000000000..49018db5e8 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/rejected-arg.any.js @@ -0,0 +1,9 @@ +// META: global=window,worker + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const error = { "name": "custom error" }; + const promise = Promise.reject(error); + return promise_rejects_exactly(t, error, WebAssembly[method](promise)); + }, `${method}`); +} diff --git a/testing/web-platform/tests/wasm/webapi/resources/incrementer.no_mime_type.wasm b/testing/web-platform/tests/wasm/webapi/resources/incrementer.no_mime_type.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/resources/incrementer.no_mime_type.wasm diff --git a/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm diff --git a/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm.headers b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm.headers new file mode 100644 index 0000000000..76b9c163b6 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wasm.headers @@ -0,0 +1,2 @@ +Content-Type: application/wasm +Cache-Control: max-age=3600 diff --git a/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm Binary files differnew file mode 100644 index 0000000000..47afcdef2a --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm diff --git a/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers new file mode 100644 index 0000000000..833ee71634 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers @@ -0,0 +1,2 @@ +Content-Type: text/css +Cache-Control: max-age=3600 diff --git a/testing/web-platform/tests/wasm/webapi/status.any.js b/testing/web-platform/tests/wasm/webapi/status.any.js new file mode 100644 index 0000000000..f3859646cc --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/status.any.js @@ -0,0 +1,21 @@ +// META: global=window,worker + +const statuses = [ + 0, + 300, + 400, + 404, + 500, + 600, + 700, + 999, +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const status of statuses) { + promise_test(t => { + const response = fetch(`status.py?status=${status}`); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Response with status ${status}: ${method}`); + } +} diff --git a/testing/web-platform/tests/wasm/webapi/status.py b/testing/web-platform/tests/wasm/webapi/status.py new file mode 100644 index 0000000000..9f056678c1 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/status.py @@ -0,0 +1,4 @@ +def main(request, response): + status = int(request.GET[b"status"]) + module = b"\0asm\1\0\0\0" + return status, [(b"Content-Type", b"application/wasm")], module diff --git a/testing/web-platform/tests/wasm/webapi/wasm_stream_compile_test.html b/testing/web-platform/tests/wasm/webapi/wasm_stream_compile_test.html new file mode 100644 index 0000000000..790410e425 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/wasm_stream_compile_test.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>WebAssembly.compileStreaming</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const module = await WebAssembly.compileStreaming(response); + assert_true(module instanceof WebAssembly.Module); + }, "compileStreaming using resolved response"); + + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const module = await WebAssembly.compileStreaming(response); + const instance = new WebAssembly.Instance(module); + assert_true(instance instanceof WebAssembly.Instance); + }, "compileStreaming using resolved response and check instantiate"); + + promise_test(async function() { + const result = fetch('resources/incrementer.wasm'); + const module = await WebAssembly.compileStreaming(result); + const instance = new WebAssembly.Instance(module); + assert_true(instance instanceof WebAssembly.Instance); + }, "compileStreaming using promise response from fetch and check instantiate"); + + promise_test(async function(t) { + const result = fetch('resources/incrementer.wrong_mime_type.wasm'); + await promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(result)); + }, "compileStreaming raise error if wrong mime type"); + + promise_test(async function(t) { + const result = fetch('resources/incrementer.no_mime_type.wasm?pipe=header(Content-Type,)'); + await promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(result)); + }, "compileStreaming raise error if no mime type"); + + promise_test(async function(t) { + const result = fetch('webapi/status.py?status=404'); + await promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(result)); + }, "compileStreaming raise error if 404 status"); + + const getWasmUrl = fileName => { + const host_info = get_host_info(); + const url = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + '/wasm/webapi/'; + return url + fileName + "?pipe=header(Access-Control-Allow-Origin,*)"; + }; + + promise_test(async function() { + const result = fetch(getWasmUrl('resources/incrementer.wasm'), {"mode": "cors"} ); + const module = await WebAssembly.compileStreaming(result); + assert_true(module instanceof WebAssembly.Module); + }, "compileStreaming check CORS"); + + promise_test(async function(t) { + const result = fetch(getWasmUrl('resources/incrementer.wasm'), {"mode": "no-cors"} ); + await promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(result)); + }, "compileStreaming raise error if no-cors"); + + promise_test(async function() { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const response = new Response(buffer, { headers: { "Content-Type" : "application/wasm" }}); + const module = await WebAssembly.compileStreaming(response); + assert_true(module instanceof WebAssembly.Module); + }, "compileStreaming receive promise with response created from ArrayBuffer"); + + promise_test(async function() { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const stream = new ReadableStream({ + start(controller) { + (async () => { + await Promise.resolve().then(() => controller.enqueue(new Uint8Array(buffer.slice(0, 20)))); + await Promise.resolve().then(() => controller.enqueue(new Uint8Array(buffer.slice(20, buffer.byteLength)))); + await Promise.resolve().then(() => controller.close()); + })(); + } + }); + const response = new Response(stream, { headers: { "Content-Type" : "application/wasm" }}); + const module = await WebAssembly.compileStreaming(response); + assert_true(module instanceof WebAssembly.Module); + }, "compileStreaming using ReadableStream with Uint8Array chunks"); + + promise_test(async function(t) { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const stream = new ReadableStream({ + start(controller) { + // Enqueuing an ArrayBuffer rather a Uint8Array per + // https://streams.spec.whatwg.org/#read-loop + controller.enqueue(buffer); + controller.close(); + } + }); + const response = new Response(stream, { headers: { "Content-Type" : "application/wasm" }}); + await promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(response)); + }, "compileStreaming using ReadableStream with ArrayBuffer chunk"); + + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const blob = await response.blob(); + const module = await WebAssembly.compileStreaming(new Response(blob, { headers: { "Content-Type" : "application/wasm" }})); + assert_true(module instanceof WebAssembly.Module); + }, "compileStreaming using blob"); + + promise_test(async function(t) { + const response = await fetch('resources/incrementer.wasm'); + const blob = await response.blob(); + const formData = new FormData; + formData.append('blob', blob); + formData.append('blob2', "Hello"); + await promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.compileStreaming(new Response(formData, { headers: { "Content-Type" : "application/wasm" }}))); + }, "compileStreaming using FormData"); +</script> diff --git a/testing/web-platform/tests/wasm/webapi/wasm_stream_instantiate_test.html b/testing/web-platform/tests/wasm/webapi/wasm_stream_instantiate_test.html new file mode 100644 index 0000000000..f39f650495 --- /dev/null +++ b/testing/web-platform/tests/wasm/webapi/wasm_stream_instantiate_test.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>WebAssembly.instantiateStreaming</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const { instance, module } = await WebAssembly.instantiateStreaming(response); + assert_true(instance instanceof WebAssembly.Instance); + assert_true(module instanceof WebAssembly.Module); + }, "instantiateStreaming using resolved response"); + + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const { instance } = await WebAssembly.instantiateStreaming(response); + assert_true(instance instanceof WebAssembly.Instance); + }, "instantiateStreaming using resolved response and check instantiate"); + + promise_test(async function() { + const result = fetch('resources/incrementer.wasm'); + const { instance } = await WebAssembly.instantiateStreaming(result); + assert_true(instance instanceof WebAssembly.Instance); + }, "instantiateStreaming using promise response from fetch and check instantiate"); + + promise_test(async function(t) { + const result = fetch('resources/incrementer.wrong_mime_type.wasm'); + await promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(result)); + }, "instantiateStreaming raise error if wrong mime type"); + + promise_test(async function(t) { + const result = fetch('resources/incrementer.no_mime_type.wasm?pipe=header(Content-Type,)'); + await promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(result)); + }, "instantiateStreaming raise error if no mime type"); + + promise_test(async function(t) { + const result = fetch('webapi/status.py?status=404'); + await promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(result)); + }, "instantiateStreaming raise error if 404 status"); + + const getWasmUrl = fileName => { + const host_info = get_host_info(); + const url = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + '/wasm/webapi/'; + return url + fileName + "?pipe=header(Access-Control-Allow-Origin,*)"; + }; + + promise_test(async function() { + const result = fetch(getWasmUrl('resources/incrementer.wasm'), {"mode": "cors"} ); + const { instance } = await WebAssembly.instantiateStreaming(result); + assert_true(instance instanceof WebAssembly.Instance); + }, "instantiateStreaming check CORS"); + + promise_test(async function(t) { + const result = fetch(getWasmUrl('resources/incrementer.wasm'), {"mode": "no-cors"} ); + await promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(result)); + }, "instantiateStreaming raise error if no-cors"); + + promise_test(async function() { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const response = new Response(buffer, { headers: { "Content-Type" : "application/wasm" }}); + const { instance } = await WebAssembly.instantiateStreaming(response); + assert_true(instance instanceof WebAssembly.Instance); + }, "instantiateStreaming receive promise with response created from ArrayBuffer"); + + promise_test(async function() { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const stream = new ReadableStream({ + start(controller) { + (async () => { + await Promise.resolve().then(() => controller.enqueue(new Uint8Array(buffer.slice(0, 20)))); + await Promise.resolve().then(() => controller.enqueue(new Uint8Array(buffer.slice(20, buffer.byteLength)))); + await Promise.resolve().then(() => controller.close()); + })(); + } + }); + const response = new Response(stream, { headers: { "Content-Type" : "application/wasm" }}); + const { instance } = await WebAssembly.instantiateStreaming(response); + assert_true(instance instanceof WebAssembly.Instance); + }, "instantiateStreaming using ReadableStream with Uint8Array chunks"); + + promise_test(async function(t) { + const v = await fetch('resources/incrementer.wasm'); + const buffer = await v.arrayBuffer(); + const stream = new ReadableStream({ + start(controller) { + // Enqueuing an ArrayBuffer rather a Uint8Array per + // https://streams.spec.whatwg.org/#read-loop + controller.enqueue(buffer); + controller.close(); + } + }); + const response = new Response(stream, { headers: { "Content-Type" : "application/wasm" }}); + await promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(response)); + }, "instantiateStreaming using ReadableStream with ArrayBuffer chunk"); + + promise_test(async function() { + const response = await fetch('resources/incrementer.wasm'); + const blob = await response.blob(); + const { instance, module } = await WebAssembly.instantiateStreaming(new Response(blob, { headers: { "Content-Type" : "application/wasm" }})); + assert_true(instance instanceof WebAssembly.Instance); + assert_true(module instanceof WebAssembly.Module); + }, "instantiateStreaming using blob"); + + promise_test(async function(t) { + const response = await fetch('resources/incrementer.wasm'); + const blob = await response.blob(); + const formData = new FormData; + formData.append('blob', blob); + formData.append('blob2', "Hello"); + await promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.instantiateStreaming(new Response(formData, { headers: { "Content-Type" : "application/wasm" }}))); + }, "instantiateStreaming using FormData"); +</script> |