Source code
Revision control
Copy as Markdown
Other Tools
// 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)
(v128.const f64x2 1 2)
(throw $exn)
catch $exn
end))`;
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();