[EH] Make rethrow's target a try label#3568
Conversation
I was previously mistaken about `rethrow`'s argument rule and thought it only counted `catch`'s depth. But it turns out it follows the same rule with `delegate`'s label: the immediate argument is comptuted in the same way as branch labels, but it only can target `try` labels (semantically it targets that `try`'s corresponding `catch`); otherwise it will be a validation failure. Unlike `delegate`, `rethrow`'s label denotes not where to rethrow, but which exception to rethrow. For example, ```wasm try $l0 catch ($l0) try $l1 catch ($l1) rethrow $l0 ;; rethrow the exception caught by 'catch ($l0)' end end ``` Refer to this comment for the more detailed informal semantics: WebAssembly/exception-handling#146 (comment) This also reverses some of `delegateTarget` -> `exceptionTarget` changes done in WebAssembly#3562 in the validator. Label validation rules apply differently for `delegate` and `rethrow` for try-catch. For example, this is valid: ```wasm try $l0 try delegate $l0 catch ($l0) end ``` But this is NOT valid: ```wasm try $l0 catch ($l0) try delegate $l0 end ``` So `try`'s label should be used within try-catch range (not catch-end range) for `delegate`s. But for the `rethrow` the rule is different. For example, this is valid: ```wasm try $l0 catch ($l0) rethrow $l0 end ``` But this is NOT valid: ```wasm try $l0 rethrow $l0 catch ($l0) end ``` So the `try`'s label should be used within catch-end range instead.
| Expression* | ||
| SExpressionWasmBuilder::makeTryOrCatchBody(Element& s, Type type, bool isTry) { | ||
| if (isTry && !elementStartsWith(s, "do")) { | ||
| throw ParseException("invalid try do clause", s.line, s.col); | ||
| } | ||
| if (!isTry && !elementStartsWith(s, "catch") && | ||
| !elementStartsWith(s, "catch_all")) { | ||
| throw ParseException("invalid catch clause", s.line, s.col); | ||
| } | ||
| if (s.size() == 1) { // (do) / (catch) / (catch_all) without instructions | ||
| return makeNop(); | ||
| } | ||
| auto ret = allocator.alloc<Block>(); | ||
| for (size_t i = 1; i < s.size(); i++) { | ||
| ret->list.push_back(parseExpression(s[i])); | ||
| } | ||
| if (ret->list.size() == 1) { | ||
| return ret->list[0]; | ||
| } | ||
| ret->finalize(type); | ||
| return ret; | ||
| } | ||
|
|
There was a problem hiding this comment.
This method was already unused; it became unused in #3487 but wasn't deleted there.
| ;; This i32.add will be dce'd | ||
| (i32.add | ||
| (i32.const 0) | ||
| (rethrow $l0) |
There was a problem hiding this comment.
This function had to change because now rethrow should be within a catch.
src/wasm/wasm-validator.cpp
Outdated
| shouldBeTrue(delegateTargetNames.empty(), | ||
| curr->body, | ||
| "all named delegate targets must exist"); | ||
| shouldBeTrue(rethrowTargetNames.empty(), | ||
| curr->body, | ||
| "all named rethrow targets must exist"); |
There was a problem hiding this comment.
Couldn't these only fail if there was a bug in the validator itself? If so, should these be asserts instead?
There was a problem hiding this comment.
Isn't that the case basically for all checking points within the validator? We already have similar lines:
binaryen/src/wasm/wasm-validator.cpp
Lines 2534 to 2538 in 1d3f578
There was a problem hiding this comment.
In other words, is it possible to construct an invalid Binaryen IR module that would trigger this error? (I would ask the same questions of any of the other checks we have)
There was a problem hiding this comment.
Yeah I think so. Some flawed pass can assign a block label to delegate, or even a random string (unlikely), but anyway I don't think it's impossible.
There was a problem hiding this comment.
But that would be caught by https://github.com/WebAssembly/binaryen/pull/3568/files#diff-2149be365b66a82038f1d3fa8f9fb1b4dcd40ab535c986f4bf0a4c37e669a1ceR2079-R2092, right? These checks are just checking that the target names were properly removed from their sets at the end of the corresponding scopes AFAICT, and it shouldn't be possible to create an invalid Binaryen module in which a try scope or catch scope never ends.
There was a problem hiding this comment.
Hmm, I think you're right. Then maybe I'll remove all those checks..?
There was a problem hiding this comment.
Oh no, you suggested to make them assertions... Will change them to assertions.
tlively
left a comment
There was a problem hiding this comment.
LGTM besides those few comments.
Co-authored-by: Thomas Lively <7121787+tlively@users.noreply.github.com>
I was previously mistaken about
rethrow's argument rule and thoughtit only counted
catch's depth. But it turns out it follows the samerule
delegate's label: the immediate argument follows the same rule aswhen computing branch labels, but it only can target
trylabels(semantically it targets that
try's correspondingcatch); otherwiseit will be a validation failure. Unlike
delegate,rethrow's labeldenotes not where to rethrow, but which exception to rethrow. For
example,
Refer to this comment for the more detailed informal semantics:
WebAssembly/exception-handling#146 (comment)
This also reverts some of
delegateTarget->exceptionTargetchangesdone in #3562 in the validator. Label validation rules apply differently
for
delegateandrethrowfor try-catch. For example, this is valid:But this is NOT valid:
So
try's label should be used within try-catch range (not catch-endrange) for
delegates.But for the
rethrowthe rule is different. For example, this is valid:But this is NOT valid:
So the
try's label should be used within catch-end range instead.