diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg index 074deca492fc..0d5d7c21c231 100644 --- a/js/public/friend/ErrorNumbers.msg +++ b/js/public/friend/ErrorNumbers.msg @@ -465,6 +465,9 @@ MSG_DEF(JSMSG_WASM_BAD_EQREF_VALUE, 0, JSEXN_TYPEERR, "can only pass a Type MSG_DEF(JSMSG_WASM_BAD_VAL_TYPE, 0, JSEXN_TYPEERR, "cannot pass v128 to or from JS") MSG_DEF(JSMSG_WASM_BAD_STRING_VAL_TYPE, 0, JSEXN_TYPEERR, "bad value type") MSG_DEF(JSMSG_WASM_BAD_STRING_IDX_TYPE, 0, JSEXN_TYPEERR, "bad index type") +MSG_DEF(JSMSG_WASM_BAD_EXN_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Tag") +MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD, 0, JSEXN_TYPEERR, "second argument must be an object") +MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, 2, JSEXN_TYPEERR, "expected {0} values but got {1}") MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer") MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}") MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified") diff --git a/js/src/jit-test/tests/wasm/exceptions/import-export.js b/js/src/jit-test/tests/wasm/exceptions/import-export.js index c3d5d2d756b1..dcfdf09799d0 100644 --- a/js/src/jit-test/tests/wasm/exceptions/import-export.js +++ b/js/src/jit-test/tests/wasm/exceptions/import-export.js @@ -8,12 +8,6 @@ function testException() { WebAssembly.RuntimeError, /cannot call WebAssembly.Tag/ ); - - assertErrorMessage( - () => new WebAssembly.Exception(), - WebAssembly.RuntimeError, - /cannot call WebAssembly.Exception/ - ); } function testImports() { diff --git a/js/src/jit-test/tests/wasm/exceptions/js-api.js b/js/src/jit-test/tests/wasm/exceptions/js-api.js new file mode 100644 index 000000000000..c669e75c003e --- /dev/null +++ b/js/src/jit-test/tests/wasm/exceptions/js-api.js @@ -0,0 +1,173 @@ +// Tests for wasm exception proposal JS API features. + +load(libdir + "eqArrayHelper.js"); + +assertErrorMessage( + () => WebAssembly.Exception(), + TypeError, + /calling a builtin Exception constructor without new is forbidden/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(), + TypeError, + /At least 2 arguments required/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(3, []), + TypeError, + /first argument must be a WebAssembly.Tag/ +); + +const { tag1, tag2, tag3, tag4, tag5, tag6, tag7 } = wasmEvalText( + `(module + (event (export "tag1") (param)) + (event (export "tag2") (param i32)) + (event (export "tag3") (param i32 f32)) + (event (export "tag4") (param i32 externref i32)) + (event (export "tag5") (param i32 externref i32 externref)) + (event (export "tag6") (param funcref)) + (event (export "tag7") (param i64)))` +).exports; + +new WebAssembly.Exception(tag1, []); +new WebAssembly.Exception(tag2, [3]); +new WebAssembly.Exception(tag3, [3, 5.5]); +new WebAssembly.Exception(tag4, [3, "foo", 4]); +new WebAssembly.Exception(tag5, [3, "foo", 4, "bar"]); + +assertErrorMessage( + () => new WebAssembly.Exception(tag2, []), + TypeError, + /expected 1 values but got 0/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(tag2, [3n]), + TypeError, + /can't convert BigInt to number/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(tag6, [undefined]), + TypeError, + /can only pass WebAssembly exported functions to funcref/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(tag7, [undefined]), + TypeError, + /can't convert undefined to BigInt/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(tag7, {}), + TypeError, + /\({}\) is not iterable/ +); + +assertErrorMessage( + () => new WebAssembly.Exception(tag7, 1), + TypeError, + /second argument must be an object/ +); + +// Test throwing a JS constructed exception to Wasm. +assertEq( + wasmEvalText( + `(module + (import "m" "exn" (event $exn (param i32))) + (import "m" "f" (func $f)) + (func (export "f") (result i32) + try (result i32) + call $f + (i32.const 0) + catch $exn + end))`, + { + m: { + exn: tag2, + f: () => { + throw new WebAssembly.Exception(tag2, [42]); + }, + }, + } + ).exports.f(), + 42 +); + +assertEqArray( + wasmEvalText( + `(module + (import "m" "exn" (event $exn (param i32 f32))) + (import "m" "f" (func $f)) + (func (export "f") (result i32 f32) + try (result i32 f32) + call $f + (i32.const 0) + (f32.const 0) + catch $exn + end))`, + { + m: { + exn: tag3, + f: () => { + throw new WebAssembly.Exception(tag3, [42, 5.5]); + }, + }, + } + ).exports.f(), + [42, 5.5] +); + +assertEqArray( + wasmEvalText( + `(module + (import "m" "exn" (event $exn (param i32 externref i32))) + (import "m" "f" (func $f)) + (func (export "f") (result i32 externref i32) + try (result i32 externref i32) + call $f + (i32.const 0) + (ref.null extern) + (i32.const 0) + catch $exn + end))`, + { + m: { + exn: tag4, + f: () => { + throw new WebAssembly.Exception(tag4, [42, "foo", 42]); + }, + }, + } + ).exports.f(), + [42, "foo", 42] +); + +assertEq( + wasmEvalText( + `(module + (import "m" "exn" (event $exn)) + (import "m" "f" (func $f)) + (func (export "f") (result i32) + try (result i32) + call $f + (i32.const 0) + catch $exn + (i32.const 0) + catch_all + (i32.const 1) + end))`, + { + m: { + exn: tag1, + f: () => { + throw new WebAssembly.Exception(tag2, [42]); + }, + }, + } + ).exports.f(), + 1 +); diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index 75eb5252873d..e3ef565c17c4 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -3584,6 +3584,8 @@ void WasmTagObject::finalize(JSFreeOp* fop, JSObject* obj) { } } +static bool IsTagObject(JSObject* obj) { return obj->is(); } + bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -3710,12 +3712,88 @@ bool WasmExceptionObject::construct(JSContext* cx, unsigned argc, Value* vp) { return false; } - // FIXME: When the JS API is finalized, it may be possible to construct - // WebAssembly.Exception instances from JS, but not for now. - JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, - JSMSG_WASM_EXN_CONSTRUCTOR, "WebAssembly.Exception"); + if (!args.requireAtLeast(cx, "WebAssembly.Exception", 2)) { + return false; + } - return false; + if (!args[0].isObject() || !IsTagObject(&args[0].toObject())) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_ARG); + return false; + } + + RootedWasmTagObject exnTag(cx, &args[0].toObject().as()); + + if (!args.get(1).isObject()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_PAYLOAD); + return false; + } + + JS::ForOfIterator iterator(cx); + if (!iterator.init(args.get(1), JS::ForOfIterator::ThrowOnNonIterable)) { + return false; + } + + wasm::ValTypeVector& params = exnTag->valueTypes(); + + // This is pre-sizing the data buffer for the exception object. + size_t nbytes = 0; + for (const ValType param : params) { + if (!param.isReference()) { + nbytes += SizeOf(param); + } + } + + RootedArrayBufferObject buf(cx, ArrayBufferObject::createZeroed(cx, nbytes)); + if (!buf) { + return false; + } + + RootedArrayObject refs(cx, NewDenseEmptyArray(cx)); + if (!refs) { + return false; + } + + uint8_t* bufPtr = buf->dataPointer(); + RootedValue nextArg(cx); + for (size_t i = 0; i < params.length(); i++) { + bool done; + if (!iterator.next(&nextArg, &done)) { + return false; + } + if (done) { + UniqueChars expected(JS_smprintf("%zu", params.length())); + UniqueChars got(JS_smprintf("%zu", i)); + + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, expected.get(), + got.get()); + return false; + } + + if (params[i].isReference()) { + RootedObject objPtr(cx); + if (!ToWebAssemblyValue(cx, nextArg, params[i], objPtr.address(), true)) { + return false; + } + if (!NewbornArrayPush(cx, refs, ObjectValue(*objPtr))) { + return false; + } + } else { + if (!ToWebAssemblyValue(cx, nextArg, params[i], bufPtr, true)) { + return false; + } + bufPtr += SizeOf(params[i]); + } + } + + RootedWasmExceptionObject exnObj( + cx, WasmExceptionObject::create(cx, SharedExceptionTag(&exnTag->tag()), + buf, refs)); + + args.rval().setObject(*exnObj); + return true; } /* static */