diff options
Diffstat (limited to 'js/src/jit-test/tests/wasm/exceptions/validation.js')
-rw-r--r-- | js/src/jit-test/tests/wasm/exceptions/validation.js | 662 |
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(); |