Expand description
Pre-flight wasm value-stack underflow detector.
Real wasm input is validated by the decoder (wasmparser). This module is
a safety net for direct callers of the lowering pipeline that feed in
raw Vec<WasmOp> without going through the validator — most notably the
fuzz harnesses, which intentionally generate malformed sequences to
prove the contract that lowering returns Err, not panics.
The check is best-effort: control-flow ops (Block, Loop, If/Else,
End, Br/BrIf/BrTable, Return, Call) have stack effects that
depend on block types and function signatures we don’t have here. When
the input contains any such op, validation gracefully bails out with
Ok(()) rather than reporting a spurious underflow. This keeps the
check conservative — it never rejects valid input — at the cost of
catching only the underflow cases that don’t involve control flow.
The bug this was written for ([PR #113 fuzz harness wasm_ops_lower_or_error,
input [I32DivS] with empty initial stack]) sits squarely inside the
modeled subset, which is the common case.
§Scope
The validator does not enforce wasm type checking — it only tracks
stack depth. So i32.const ; i64.add will pass even though it’s
type-invalid. Type errors fall to the lowering pipeline, which now
raises them as Err (per PR #117 — the same audit pass).
§Why not just call wasmparser?
Two reasons:
- The lowering pipeline accepts
Vec<WasmOp>(its own enum), not raw wasm bytes. Threading wasmparser back would require a re-encoder. - The harnesses want to feed malformed input. We want a cheap local check that returns Err rather than panics, not full re-validation.
See PR #117 for the original fuzz crash that motivated this module.
Note: Select is modeled as pop 3, push 1 — wasm’s select consumes
two values and a condition. MemoryGrow pops a page count and pushes
the previous size (or -1). MemorySize is a pure push.
Functions§
- check_
no_ underflow - Pre-flight check: returns
Err(Error::validation(...))if any modeled op would underflow the wasm value stack. If the sequence contains control-flow ops we don’t model, returnsOk(())(bails conservatively).