summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/exceptions/validation.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/exceptions/validation.js')
-rw-r--r--js/src/jit-test/tests/wasm/exceptions/validation.js662
1 files changed, 662 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/exceptions/validation.js b/js/src/jit-test/tests/wasm/exceptions/validation.js
new file mode 100644
index 0000000000..98a37042f1
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/exceptions/validation.js
@@ -0,0 +1,662 @@
+
+// Test wasm type validation for exception handling instructions.
+
+load(libdir + "wasm-binary.js");
+
+function wasmValid(mod) {
+ assertEq(WebAssembly.validate(mod), true);
+}
+
+function wasmInvalid(mod, pattern) {
+ assertEq(WebAssembly.validate(mod), false);
+ assertErrorMessage(
+ () => new WebAssembly.Module(mod),
+ WebAssembly.CompileError,
+ pattern
+ );
+}
+
+const emptyType = { args: [], ret: VoidCode };
+const i32Type = { args: [I32Code], ret: VoidCode };
+const toi32Type = { args: [], ret: I32Code };
+const i32Toi32Type = { args: [I32Code], ret: I32Code };
+const i32Toi64Type = { args: [I32Code], ret: I64Code };
+const i32i32Toi32Type = { args: [I32Code, I32Code], ret: I32Code };
+
+function testValidateDecode() {
+ // Try blocks must have a block type code.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: [
+ TryCode,
+ // Missing type code.
+ I32ConstCode,
+ 0x01,
+ CatchCode,
+ 0x00,
+ EndCode,
+ DropCode,
+ ReturnCode,
+ ],
+ }),
+ ]),
+ ]),
+ /bad type/
+ );
+
+ // Catch must have a tag index.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ 0x01,
+ CatchCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /expected tag index/
+ );
+
+ // Rethrow must have a depth argument.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ RethrowCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /unable to read rethrow depth/
+ );
+
+ // Delegate must have a depth argument.
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ tagSection([{ type: 0 }]),
+ bodySection([
+ funcBody(
+ {
+ locals: [],
+ body: [
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ 0x01,
+ DelegateCode,
+ // Index missing.
+ ],
+ },
+ (withEndCode = false)
+ ),
+ ]),
+ ]),
+ /unable to read delegate depth/
+ );
+}
+
+function testValidateThrow() {
+ valid = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i32.const 0
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+
+ validSimd = `(module
+ (tag $exn (param v128))
+ (func (export "f") (param v128) (result v128)
+ (try (result v128)
+ (do (v128.const f64x2 1 2)
+ (throw $exn))
+ (catch $exn))))`;
+
+ invalid0 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+ error0 = /popping value from empty stack/;
+
+ invalid1 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i64.const 0
+ throw $exn1)
+ (tag $exn1 (type 0)))`;
+ error1 = /expression has type i64 but expected i32/;
+
+ invalid2 = `(module
+ (type (func (param i32)))
+ (func $exn-zero
+ i32.const 0
+ throw 1)
+ (tag $exn1 (type 0)))`;
+ error2 = /tag index out of range/;
+
+ wasmValidateText(valid);
+ if (wasmSimdEnabled()) {
+ wasmValidateText(validSimd);
+ }
+ wasmFailValidateText(invalid0, error0);
+ wasmFailValidateText(invalid1, error1);
+ wasmFailValidateText(invalid2, error2);
+}
+
+function testValidateTryCatch() {
+ function mod_with(fbody) {
+ return moduleWithSections([
+ sigSection([emptyType, i32Type, i32i32Toi32Type]),
+ declSection([0]),
+ tagSection([{ type: 0 }, { type: 1 }]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: fbody,
+ }),
+ ]),
+ ]);
+ }
+
+ const body1 = [
+ // try (result i32)
+ TryCode,
+ I32Code,
+ // (i32.const 1)
+ I32ConstCode,
+ varU32(1),
+ // catch 1
+ CatchCode,
+ varU32(1),
+ ];
+
+ const valid1 = mod_with(body1.concat([EndCode, DropCode, ReturnCode]));
+ const invalid1 = mod_with(
+ body1.concat([I32ConstCode, varU32(2), EndCode, DropCode, ReturnCode])
+ );
+
+ const valid2 = mod_with([
+ // (i32.const 0) (i32.const 0)
+ I32ConstCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(0),
+ // try (param i32 i32) (result i32) drop drop (i32.const 1)
+ TryCode,
+ varS32(2),
+ DropCode,
+ DropCode,
+ I32ConstCode,
+ varU32(1),
+ // catch 0 (i32.const 2) end drop return
+ CatchCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(2),
+ EndCode,
+ DropCode,
+ ReturnCode,
+ ]);
+
+ wasmValid(valid1);
+ wasmInvalid(invalid1, /unused values not explicitly dropped/);
+ wasmValid(valid2);
+
+ // Test handler-less try blocks.
+ wasmValidateText(
+ `(module (func try end))`
+ );
+
+ wasmValidateText(
+ `(module (func (result i32) try (result i32) (i32.const 1) end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32) (i32.const 1) (br 0) end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (func try (result i32) end))`,
+ /popping value from empty stack/
+ );
+}
+
+function testValidateCatch() {
+ wasmInvalid(
+ moduleWithSections([
+ sigSection([emptyType]),
+ declSection([0]),
+ bodySection([
+ funcBody({
+ locals: [],
+ body: [TryCode, VoidCode, CatchCode, varU32(0), EndCode],
+ }),
+ ]),
+ ]),
+ /tag index out of range/
+ );
+}
+
+function testValidateCatchAll() {
+ wasmValidateText(
+ `(module
+ (tag $exn)
+ (func try catch $exn catch_all end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32)
+ (i32.const 0)
+ catch_all
+ (i32.const 1)
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try catch_all catch 0 end))`,
+ /catch cannot follow a catch_all/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try (result i32) (i32.const 1) catch_all end drop))`,
+ /popping value from empty stack/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param i32))
+ (func try catch $exn drop catch_all drop end))`,
+ /popping value from empty stack/
+ );
+
+ // We can't distinguish `else` and `catch_all` in error messages since they
+ // share the binary opcode.
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func try catch_all catch_all end))`,
+ /catch_all can only be used within a try/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn)
+ (func catch_all))`,
+ /catch_all can only be used within a try/
+ );
+}
+
+function testValidateExnPayload() {
+ valid0 = moduleWithSections([
+ sigSection([i32Type, i32Toi32Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (param i32) (result i32) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i32) (local.get 0) (throw $exn) (i32.const 1)
+ TryCode,
+ I32Code,
+ LocalGetCode,
+ varU32(0),
+ ThrowCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ // catch $exn (i32.const 1) (i32.add) end
+ CatchCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ I32AddCode,
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ // This is to ensure the following sentence from the spec overview holds:
+ // > "the operand stack is popped back to the size the operand stack had
+ // > when the try block was entered"
+ valid1 = moduleWithSections([
+ sigSection([i32Type, toi32Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (result i32) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i32) (i32.const 0) (i32.const 1) (throw $exn) drop
+ TryCode,
+ I32Code,
+ I32ConstCode,
+ varU32(0),
+ I32ConstCode,
+ varU32(1),
+ ThrowCode,
+ varU32(0),
+ DropCode,
+ // catch $exn drop (i32.const 2) end
+ CatchCode,
+ varU32(0),
+ DropCode,
+ I32ConstCode,
+ varU32(2),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ invalid0 = moduleWithSections([
+ sigSection([i32Type, i32Toi64Type]),
+ declSection([1]),
+ // (tag $exn (param i32))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func (param i32) (result i64) ...
+ funcBody({
+ locals: [],
+ body: [
+ // try (result i64) (local.get 0) (throw $exn) (i64.const 0)
+ TryCode,
+ I64Code,
+ LocalGetCode,
+ varU32(0),
+ ThrowCode,
+ varU32(0),
+ I64ConstCode,
+ varU32(0),
+ // catch $exn end
+ CatchCode,
+ varU32(0),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ invalid1 = moduleWithSections([
+ // (type (func))
+ sigSection([emptyType]),
+ declSection([0]),
+ // (tag $exn (type 0))
+ tagSection([{ type: 0 }]),
+ bodySection([
+ // (func ...
+ funcBody({
+ locals: [],
+ body: [
+ // try catch 1 end
+ TryCode,
+ VoidCode,
+ CatchCode,
+ varU32(1),
+ EndCode,
+ ],
+ }),
+ ]),
+ ]);
+
+ wasmValid(valid0);
+ wasmValid(valid1);
+ wasmInvalid(invalid0, /has type i32 but expected i64/);
+ wasmInvalid(invalid1, /tag index out of range/);
+}
+
+function testValidateRethrow() {
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch_all
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (func (result i32)
+ try (result i32)
+ (i32.const 1)
+ catch_all
+ rethrow 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 0
+ end
+ end
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 2
+ end
+ end
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 1
+ end
+ end
+ end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func rethrow 0))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func try rethrow 0 end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module (func try rethrow 0 catch_all end))`,
+ /rethrow target was not a catch block/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ nop
+ catch $exn
+ block
+ try
+ catch $exn
+ rethrow 4
+ end
+ end
+ end))`,
+ /rethrow depth exceeds current nesting level/
+ );
+}
+
+function testValidateDelegate() {
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 0
+ catch $exn
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 1
+ catch $exn
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ block
+ try
+ throw $exn
+ delegate 0
+ end))`
+ );
+
+ wasmValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ catch $exn
+ try
+ throw $exn
+ delegate 0
+ end))`
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func (result i32)
+ try
+ throw $exn
+ delegate 0
+ (i64.const 0)
+ end))`,
+ /type mismatch: expression has type i64 but expected i32/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try (result i32)
+ (i64.const 0)
+ delegate 0))`,
+ /type mismatch: expression has type i64 but expected i32/
+ );
+
+ wasmFailValidateText(
+ `(module
+ (tag $exn (param))
+ (func
+ try
+ try
+ throw $exn
+ delegate 2
+ catch $exn
+ end))`,
+ /delegate depth exceeds current nesting level/
+ );
+
+ wasmFailValidateText(
+ `(module (func delegate 0))`,
+ /delegate can only be used within a try/
+ );
+}
+
+testValidateDecode();
+testValidateThrow();
+testValidateTryCatch();
+testValidateCatch();
+testValidateCatchAll();
+testValidateExnPayload();
+testValidateRethrow();
+testValidateDelegate();