Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EH] Make rethrow's target a try label #3568

Merged
merged 5 commits into from
Feb 17, 2021
Merged

Conversation

aheejin
Copy link
Member

@aheejin aheejin commented Feb 14, 2021

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 delegate's label: the immediate argument follows the same rule as
when computing 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,

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 reverts some of delegateTarget -> exceptionTarget changes
done in #3562 in the validator. Label validation rules apply differently
for delegate and rethrow for try-catch. For example, this is valid:

try $l0
  try
  delegate $l0
catch ($l0)
end

But this is NOT valid:

try $l0
catch ($l0)
  try
  delegate $l0
end

So try's label should be used within try-catch range (not catch-end
range) for delegates.

But for the rethrow the rule is different. For example, this is valid:

try $l0
catch ($l0)
  rethrow $l0
end

But this is NOT valid:

try $l0
  rethrow $l0
catch ($l0)
end

So the try's label should be used within catch-end range instead.

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.
Comment on lines -2081 to -2103
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;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function had to change because now rethrow should be within a catch.

src/wasm/wasm-binary.cpp Outdated Show resolved Hide resolved
src/wasm/wasm-validator.cpp Outdated Show resolved Hide resolved
Comment on lines 2546 to 2551
shouldBeTrue(delegateTargetNames.empty(),
curr->body,
"all named delegate targets must exist");
shouldBeTrue(rethrowTargetNames.empty(),
curr->body,
"all named rethrow targets must exist");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't these only fail if there was a bug in the validator itself? If so, should these be asserts instead?

Copy link
Member Author

@aheejin aheejin Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that the case basically for all checking points within the validator? We already have similar lines:

shouldBeTrue(
breakInfos.empty(), curr->body, "all named break targets must exist");
shouldBeTrue(exceptionTargetNames.empty(),
curr->body,
"all named delegate targets must exist");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think you're right. Then maybe I'll remove all those checks..?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, you suggested to make them assertions... Will change them to assertions.

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM besides those few comments.

@aheejin aheejin merged commit 1254b56 into WebAssembly:main Feb 17, 2021
@aheejin aheejin deleted the rethrow_fix branch February 17, 2021 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants