From 685b3ad1bac46018910bc31fabc6a591db0b30ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 10 Jul 2026 20:41:19 +0200 Subject: [PATCH] wasmparser: require result equivalence for `(on $t switch)` handler The `resume`/`resume_throw` handler clause `(on $tag switch)` was accepted when the switch tag's result types were merely a *subtype* of the resumed continuation's results. This is unsound, and does not follow the spec: the handler judgment has no subsumption rule, so `(on tu switch) : t*` together with the required `hdl : t2*` forces `t* = t2*`. The two result vectors must be equivalent, not just related by subtyping. V8 and Binaryen already check for equivalence. --- crates/wasmparser/src/validator/operators.rs | 15 ++++++++++-- .../validate-switch-label.wast | 23 ++++++++++++++++++- .../validate-switch-label.wast.json | 11 +++++++-- .../{5.print => 6.print} | 4 ++-- 4 files changed, 46 insertions(+), 7 deletions(-) rename tests/snapshots/cli/stack-switching/validate-switch-label.wast/{5.print => 6.print} (68%) diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 5dc7ce054e..3d7ad79e7b 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -1911,10 +1911,21 @@ where } Handle::OnSwitch { tag } => { let tag_ty = self.tag_at(tag)?; - if !self.is_func_subtype( + // The tag's type must be *equivalent* to (not merely a + // subtype of) `[] -> [old results]`: the handler judgment + // has no subsumption rule, so `(on tu switch) : t*` together + // with the required `hdl : t2*` forces `t* = t2*`. Checking + // subtyping in both directions gives equivalence, and also + // pins the tag to zero parameters (via the param-length + // check inside `is_func_subtype`). + let tag_matches = self.is_func_subtype( (tag_ty.params(), tag_ty.results()), (&[], old_func_ty.results()), - ) { + ) && self.is_func_subtype( + (&[], old_func_ty.results()), + (tag_ty.params(), tag_ty.results()), + ); + if !tag_matches { bail!( self.offset, "type mismatch: switch tag does not match continuation" diff --git a/tests/cli/stack-switching/validate-switch-label.wast b/tests/cli/stack-switching/validate-switch-label.wast index 74917b7512..3ee47c7b80 100644 --- a/tests/cli/stack-switching/validate-switch-label.wast +++ b/tests/cli/stack-switching/validate-switch-label.wast @@ -83,11 +83,32 @@ ) "type mismatch: switch tag does not match continuation") +;; The switch tag's results must be *equivalent* to the continuation's results, +;; not merely a subtype. Here the tag results `[nullfuncref]` are a strict +;; subtype of the continuation results `[funcref]`, so this must be rejected. +(assert_invalid + (module + (type $ft (func (result funcref))) + (type $ct (cont $ft)) + + (type $tag_ft (func (result nullfuncref))) + (tag $t (type $tag_ft)) + + (func + block $label + (resume $ct (on $t switch) (ref.null $ct)) + unreachable + end + ) + ) + "type mismatch: switch tag does not match continuation") + +;; Equivalent reference-type results (both `[funcref]`) are accepted. (module (type $ft (func (result funcref))) (type $ct (cont $ft)) - (type $tag_ft (func (result nullfuncref))) + (type $tag_ft (func (result funcref))) (tag $t (type $tag_ft)) (func diff --git a/tests/snapshots/cli/stack-switching/validate-switch-label.wast.json b/tests/snapshots/cli/stack-switching/validate-switch-label.wast.json index ed496e7cfe..db59137f2c 100644 --- a/tests/snapshots/cli/stack-switching/validate-switch-label.wast.json +++ b/tests/snapshots/cli/stack-switching/validate-switch-label.wast.json @@ -36,9 +36,16 @@ "text": "type mismatch: switch tag does not match continuation" }, { - "type": "module", - "line": 86, + "type": "assert_invalid", + "line": 90, "filename": "validate-switch-label.5.wasm", + "module_type": "binary", + "text": "type mismatch: switch tag does not match continuation" + }, + { + "type": "module", + "line": 107, + "filename": "validate-switch-label.6.wasm", "module_type": "binary" } ] diff --git a/tests/snapshots/cli/stack-switching/validate-switch-label.wast/5.print b/tests/snapshots/cli/stack-switching/validate-switch-label.wast/6.print similarity index 68% rename from tests/snapshots/cli/stack-switching/validate-switch-label.wast/5.print rename to tests/snapshots/cli/stack-switching/validate-switch-label.wast/6.print index 3a71bb73e7..210c1a057a 100644 --- a/tests/snapshots/cli/stack-switching/validate-switch-label.wast/5.print +++ b/tests/snapshots/cli/stack-switching/validate-switch-label.wast/6.print @@ -1,9 +1,9 @@ (module (type $ft (;0;) (func (result funcref))) (type $ct (;1;) (cont $ft)) - (type $tag_ft (;2;) (func (result nullfuncref))) + (type $tag_ft (;2;) (func (result funcref))) (type (;3;) (func)) - (tag $t (;0;) (type $tag_ft) (result nullfuncref)) + (tag $t (;0;) (type $tag_ft) (result funcref)) (func (;0;) (type 3) block $label ref.null $ct