Skip to content

Commit

Permalink
[js-api] Re-add old tests to test/js-api/exception (#315)
Browse files Browse the repository at this point in the history
These two tests used to be in
https://github.com/WebAssembly/exception-handling/tree/main/test/js-api/exception
and were moved into
https://github.com/WebAssembly/exception-handling/tree/main/test/legacy/exceptions/js-api
in #305.

I'm planning new version of these tests that use `try_table` and
`throw_ref` in
https://github.com/WebAssembly/exception-handling/tree/main/test/js-api/exception,
but it wouldn't require to rewrite the whole tests, but copying these
tests into the this directory and modify them in a single PR makes
Github think these are brand-new files, resulting in a large diff
containing the whole files that is difficult to review (and which has
been reviewed and in the repo for a long time already).

So I'm making a PR that only re-adds these file here so that I can add
changes to these files in another PR.
  • Loading branch information
aheejin committed Jun 25, 2024
1 parent 2f5a7c5 commit e2c8d90
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 0 deletions.
120 changes: 120 additions & 0 deletions test/js-api/exception/basic.tentative.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// META: global=window,dedicatedworker,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, kWasmVoid,
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, kWasmVoid,
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");
170 changes: 170 additions & 0 deletions test/js-api/exception/identity.tentative.any.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// META: global=window,dedicatedworker,jsshell,shadowrealm
// META: script=/wasm/jsapi/assertions.js
// META: script=/wasm/jsapi/wasm-module-builder.js

test(() => {
const builder = new WasmModuleBuilder();

// Tag defined in JavaScript and imported into Wasm
const jsTag = new WebAssembly.Tag({ parameters: ["i32"] });
const jsTagIndex = builder.addImportedTag("module", "jsTag", kSig_v_i);
const jsTagExn = new WebAssembly.Exception(jsTag, [42]);
const jsTagExnSamePayload = new WebAssembly.Exception(jsTag, [42]);
const jsTagExnDiffPayload = new WebAssembly.Exception(jsTag, [53]);
const throwJSTagExnIndex = builder.addImport("module", "throwJSTagExn", kSig_v_v);

// Tag defined in Wasm and exported to JS
const wasmTagIndex = builder.addTag(kSig_v_i);
builder.addExportOfKind("wasmTag", kExternalTag, wasmTagIndex);
const throwWasmTagExnIndex = builder.addImport("module", "throwWasmTagExn", kSig_v_v);
// Will be assigned after an instance is created
let wasmTagExn = null;
let wasmTagExnSamePayload = null;
let wasmTagExnDiffPayload = null;

const imports = {
module: {
throwJSTagExn: function() { throw jsTagExn; },
throwWasmTagExn: function() { throw wasmTagExn; },
jsTag: jsTag
}
};

// Call a JS function that throws an exception using a JS-defined tag, catches
// it with a 'catch' instruction, and rethrows it.
builder
.addFunction("catch_js_tag_rethrow", kSig_v_v)
.addBody([
kExprTry, kWasmVoid,
kExprCallFunction, throwJSTagExnIndex,
kExprCatch, jsTagIndex,
kExprDrop,
kExprRethrow, 0x00,
kExprEnd
])
.exportFunc();

// Call a JS function that throws an exception using a Wasm-defined tag,
// catches it with a 'catch' instruction, and rethrows it.
builder
.addFunction("catch_wasm_tag_rethrow", kSig_v_v)
.addBody([
kExprTry, kWasmVoid,
kExprCallFunction, throwWasmTagExnIndex,
kExprCatch, wasmTagIndex,
kExprDrop,
kExprRethrow, 0x00,
kExprEnd
])
.exportFunc();

// Call a JS function that throws an exception using a JS-defined tag, catches
// it with a 'catch_all' instruction, and rethrows it.
builder
.addFunction("catch_all_js_tag_rethrow", kSig_v_v)
.addBody([
kExprTry, kWasmVoid,
kExprCallFunction, throwJSTagExnIndex,
kExprCatchAll,
kExprRethrow, 0x00,
kExprEnd
])
.exportFunc();

// Call a JS function that throws an exception using a Wasm-defined tag,
// catches it with a 'catch_all' instruction, and rethrows it.
builder
.addFunction("catch_all_wasm_tag_rethrow", kSig_v_v)
.addBody([
kExprTry, kWasmVoid,
kExprCallFunction, throwWasmTagExnIndex,
kExprCatchAll,
kExprRethrow, 0x00,
kExprEnd
])
.exportFunc();

// Call a JS function that throws an exception, catches it with a 'catch'
// instruction, and returns its i32 payload.
builder
.addFunction("catch_js_tag_return_payload", kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprCallFunction, throwJSTagExnIndex,
kExprI32Const, 0x00,
kExprCatch, jsTagIndex,
kExprReturn,
kExprEnd
])
.exportFunc();

// Call a JS function that throws an exception, catches it with a 'catch'
// instruction, and throws a new exception using that payload.
builder
.addFunction("catch_js_tag_throw_payload", kSig_v_v)
.addBody([
kExprTry, kWasmVoid,
kExprCallFunction, throwJSTagExnIndex,
kExprCatch, jsTagIndex,
kExprThrow, jsTagIndex,
kExprEnd
])
.exportFunc();

const buffer = builder.toBuffer();

WebAssembly.instantiate(buffer, imports).then(result => {
// The exception object's identity should be preserved across 'rethrow's in
// Wasm code. Do tests with a tag defined in JS.
try {
result.instance.exports.catch_js_tag_rethrow();
} catch (e) {
assert_equals(e, jsTagExn);
// Even if they have the same payload, they are different objects, so they
// shouldn't compare equal.
assert_not_equals(e, jsTagExnSamePayload);
assert_not_equals(e, jsTagExnDiffPayload);
}
try {
result.instance.exports.catch_all_js_tag_rethrow();
} catch (e) {
assert_equals(e, jsTagExn);
assert_not_equals(e, jsTagExnSamePayload);
assert_not_equals(e, jsTagExnDiffPayload);
}

// Do the same tests with a tag defined in Wasm.
const wasmTag = result.instance.exports.wasmTag;
wasmTagExn = new WebAssembly.Exception(wasmTag, [42]);
wasmTagExnSamePayload = new WebAssembly.Exception(wasmTag, [42]);
wasmTagExnDiffPayload = new WebAssembly.Exception(wasmTag, [53]);
try {
result.instance.exports.catch_wasm_tag_rethrow();
} catch (e) {
assert_equals(e, wasmTagExn);
assert_not_equals(e, wasmTagExnSamePayload);
assert_not_equals(e, wasmTagExnDiffPayload);
}
try {
result.instance.exports.catch_all_wasm_tag_rethrow();
} catch (e) {
assert_equals(e, wasmTagExn);
assert_not_equals(e, wasmTagExnSamePayload);
assert_not_equals(e, wasmTagExnDiffPayload);
}

// This function catches the exception and returns its i32 payload, which
// should match the original payload.
assert_equals(result.instance.exports.catch_js_tag_return_payload(), 42);

// This function catches the exception and throws a new exception using the
// its payload. Even if the payload is reused, the exception objects should
// not compare equal.
try {
result.instance.exports.catch_js_tag_throw_payload();
} catch (e) {
assert_equals(e.getArg(jsTag, 0), 42);
assert_not_equals(e, jsTagExn);
}
});
}, "Identity check");

0 comments on commit e2c8d90

Please sign in to comment.