Source code

Revision control

Copy as Markdown

Other Tools

// |jit-test| skip-if: !wasmStackSwitchingEnabled()
// Regression test for a return_call_indirect signature mismatch in the *entry*
// function of a continuation stack.
//
// The bad-signature trap tears down the entry function's frame, leaving the
// unwound PC inside the ContBaseFrame stub (not a Function CodeRange). Frame
// iteration must switch to the resume target on the parent stack instead of
// dereferencing a null CodeRange, and exception unwinding must free the
// continuation stack even though no frame is reported on it.
const PRELUDE = `
(type $ft (func))
(type $ct (cont $ft))
`;
// Basic case: the continuation's entry function tail-calls through a table
// entry whose signature does not match. The trap must surface to the JS caller
// as a RuntimeError rather than crashing.
{
let run = wasmEvalText(`(module
${PRELUDE}
(type $bad (func (param i32)))
(table 1 funcref)
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func $f (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
(elem declare func $f)
(func (export "run")
ref.func $f
cont.new $ct
resume $ct
)
)`).exports.run;
assertErrorMessage(() => run(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}
// Repeat many times: each trap allocates and must free its continuation stack
// during exception unwinding. A leak or use-after-free of the unwound stack
// would manifest here (especially under ASAN / debug assertions).
{
let run = wasmEvalText(`(module
${PRELUDE}
(type $bad (func (param i32)))
(table 1 funcref)
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func $f (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
(elem declare func $f)
(func (export "run")
ref.func $f
cont.new $ct
resume $ct
)
)`).exports.run;
for (let i = 0; i < 1000; i++) {
assertErrorMessage(() => run(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}
}
// The mismatch happens after a suspend/resume round-trip, so the bad
// return_call_indirect tears down the entry frame only after the continuation
// stack has already been switched away from and back to.
{
let { start, step } = wasmEvalText(`(module
${PRELUDE}
(type $bad (func (param i32)))
(tag $tag)
(table 1 funcref)
(global $k (mut (ref null $ct)) (ref.null $ct))
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func $f (type $ft)
suspend $tag
i32.const 0
return_call_indirect (type $ft)
)
(elem declare func $f)
(func (export "start")
ref.func $f
cont.new $ct
global.set $k
)
(func (export "step")
(block (result (ref $ct))
global.get $k
resume $ct (on $tag 0)
return
)
global.set $k
)
)`).exports;
start();
step();
assertErrorMessage(() => step(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}
// Nested continuations: an inner continuation's entry function does the bad
// return_call_indirect while running on top of an outer continuation. The
// RuntimeError must propagate out through both stacks to the JS caller.
{
let run = wasmEvalText(`(module
${PRELUDE}
(type $bad (func (param i32)))
(table 1 funcref)
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func $inner (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
(elem declare func $inner)
(func $outer (type $ft)
ref.func $inner
cont.new $ct
resume $ct
)
(elem declare func $outer)
(func (export "run")
ref.func $outer
cont.new $ct
resume $ct
)
)`).exports.run;
assertErrorMessage(() => run(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}
// After the trap is handled, the engine must remain in a consistent state:
// a subsequent, well-behaved continuation runs to completion.
{
let { bad, good, result } = wasmEvalText(`(module
${PRELUDE}
(type $bad_ty (func (param i32)))
(table 1 funcref)
(global $r (mut i32) (i32.const 0))
(func $g (type $bad_ty))
(elem (i32.const 0) func $g)
(func $f_bad (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
(elem declare func $f_bad)
(func $f_good (type $ft)
i32.const 42
global.set $r
)
(elem declare func $f_good)
(func (export "bad")
ref.func $f_bad
cont.new $ct
resume $ct
)
(func (export "good")
ref.func $f_good
cont.new $ct
resume $ct
)
(func (export "result") (result i32) global.get $r)
)`).exports;
assertErrorMessage(() => bad(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
good();
assertEq(result(), 42);
}
// Cross-instance: instance A does cont.new on a function imported from instance
// B, so the ContBaseFrame stub belongs to A while the first (and only) real
// frame to run on the stack belongs to B. When B's entry function does the bad
// return_call_indirect, the unwound PC sits in A's stub, but the frame's
// effective instance is B. Frame iteration and trap setup must not assume the
// stub and the entry frame share a Code/instance.
{
let b = wasmEvalText(`(module
(type $ft (func))
(type $bad (func (param i32)))
(table 1 funcref)
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func (export "f") (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
)`).exports;
let run = wasmEvalText(`(module
${PRELUDE}
(import "b" "f" (func $f (type $ft)))
(elem declare func $f)
(func (export "run")
ref.func $f
cont.new $ct
resume $ct
)
)`, { b: { f: b.f } }).exports.run;
assertErrorMessage(() => run(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}
// Same cross-instance setup, but instance B performs the resume: A does
// cont.new on B's imported function and hands the continuation to B, which
// resumes it. The stub is still A's while the running frame is B's.
{
let b = wasmEvalText(`(module
${PRELUDE}
(type $bad (func (param i32)))
(table 1 funcref)
(func $g (type $bad))
(elem (i32.const 0) func $g)
(func (export "f") (type $ft)
i32.const 0
return_call_indirect (type $ft)
)
(func (export "resumeIt") (param $k (ref $ct))
local.get $k
resume $ct
)
)`).exports;
let run = wasmEvalText(`(module
${PRELUDE}
(import "b" "f" (func $f (type $ft)))
(import "b" "resumeIt" (func $resumeIt (param (ref $ct))))
(elem declare func $f)
(func (export "run")
ref.func $f
cont.new $ct
call $resumeIt
)
)`, { b: { f: b.f, resumeIt: b.resumeIt } }).exports.run;
assertErrorMessage(() => run(), WebAssembly.RuntimeError,
/indirect call signature mismatch/);
}