summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeejin Ahn <aheejin@gmail.com>2020-09-15 23:46:32 +0900
committerGitHub <noreply@github.com>2020-09-15 23:46:32 +0900
commitbced89d2986bde990bf9bf52306e55772b02707e (patch)
tree5fe2cf3bbaed1f19e1ddd87aa0e4d2d63b0c3f65
parentbcc6f2994e8f71b633b1d0257547aeed691f6ceb (diff)
downloadbinaryen-bced89d2986bde990bf9bf52306e55772b02707e.tar.gz
binaryen-bced89d2986bde990bf9bf52306e55772b02707e.tar.bz2
binaryen-bced89d2986bde990bf9bf52306e55772b02707e.zip
Fix inner block problem with 'catch' (#3129)
Fixes #3114.
-rw-r--r--src/wasm-binary.h4
-rw-r--r--src/wasm/wasm-binary.cpp102
-rw-r--r--test/break-within-catch.wasmbin0 -> 32 bytes
-rw-r--r--test/break-within-catch.wasm.fromBinary19
4 files changed, 104 insertions, 21 deletions
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 8a067e3fd..b979be853 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1394,7 +1394,7 @@ public:
void visitBlock(Block* curr);
// Gets a block of expressions. If it's just one, return that singleton.
- Expression* getBlockOrSingleton(Type type, unsigned numPops = 0);
+ Expression* getBlockOrSingleton(Type type);
void visitIf(If* curr);
void visitLoop(Loop* curr);
@@ -1444,7 +1444,7 @@ public:
void visitRefNull(RefNull* curr);
void visitRefIsNull(RefIsNull* curr);
void visitRefFunc(RefFunc* curr);
- void visitTry(Try* curr);
+ void visitTryOrTryInBlock(Expression*& out);
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
void visitBrOnExn(BrOnExn* curr);
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index fb8d97cb5..5942a979f 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -2441,7 +2441,7 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
visitRefFunc((curr = allocator.alloc<RefFunc>())->cast<RefFunc>());
break;
case BinaryConsts::Try:
- visitTry((curr = allocator.alloc<Try>())->cast<Try>());
+ visitTryOrTryInBlock(curr);
break;
case BinaryConsts::Throw:
visitThrow((curr = allocator.alloc<Throw>())->cast<Throw>());
@@ -2692,21 +2692,11 @@ void WasmBinaryBuilder::visitBlock(Block* curr) {
}
// Gets a block of expressions. If it's just one, return that singleton.
-// numPops is the number of pop instructions we add before starting to parse the
-// block. Can be used when we need to assume certain number of values are on top
-// of the stack in the beginning.
-Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type,
- unsigned numPops) {
+Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type) {
Name label = getNextLabel();
breakStack.push_back({label, type});
auto start = expressionStack.size();
- Builder builder(wasm);
- for (unsigned i = 0; i < numPops; i++) {
- auto* pop = builder.makePop(Type::exnref);
- pushExpression(pop);
- }
-
processExpressions();
size_t end = expressionStack.size();
if (end < start) {
@@ -2757,12 +2747,12 @@ void WasmBinaryBuilder::visitLoop(Loop* curr) {
auto start = expressionStack.size();
processExpressions();
size_t end = expressionStack.size();
+ if (start > end) {
+ throwError("block cannot pop from outside");
+ }
if (end - start == 1) {
curr->body = popExpression();
} else {
- if (start > end) {
- throwError("block cannot pop from outside");
- }
auto* block = allocator.alloc<Block>();
pushBlockElements(block, curr->type, start);
block->finalize(curr->type);
@@ -4834,8 +4824,9 @@ void WasmBinaryBuilder::visitRefFunc(RefFunc* curr) {
curr->finalize();
}
-void WasmBinaryBuilder::visitTry(Try* curr) {
+void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
BYN_TRACE("zz node: Try\n");
+ auto* curr = allocator.alloc<Try>();
startControlFlow(curr);
// For simplicity of implementation, like if scopes, we create a hidden block
// within each try-body and catch-body, and let branches target those inner
@@ -4845,11 +4836,84 @@ void WasmBinaryBuilder::visitTry(Try* curr) {
if (lastSeparator != BinaryConsts::Catch) {
throwError("No catch instruction within a try scope");
}
- curr->catchBody = getBlockOrSingleton(curr->type, 1);
+
+ // For simplicity, we create an inner block within the catch body too, but the
+ // one within the 'catch' *must* be omitted when we write out the binary back
+ // later, because the 'catch' instruction pushes a value onto the stack and
+ // the inner block does not support block input parameters without multivalue
+ // support.
+ // try
+ // ...
+ // catch ;; Pushes a value onto the stack
+ // block ;; Inner block. Should be deleted when writing binary!
+ // use the pushed value
+ // end
+ // end
+ //
+ // But when input binary code is like
+ // try
+ // ...
+ // catch
+ // br 0
+ // end
+ //
+ // 'br 0' accidentally happens to target the inner block, creating code like
+ // this in Binaryen IR, making the inner block not deletable, resulting in a
+ // validation error:
+ // (try
+ // ...
+ // (catch
+ // (block $label0 ;; Cannot be deleted, because there's a branch to this
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ //
+ // When this happens, we fix this by creating a block that wraps the whole
+ // try-catch, and making the branches target that block instead, like this:
+ // (block $label ;; New enclosing block, new target for the branch
+ // (try
+ // ...
+ // (catch
+ // (block ;; Now this can be deleted when writing binary
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ // )
+ Name catchLabel = getNextLabel();
+ breakStack.push_back({catchLabel, curr->type});
+ auto start = expressionStack.size();
+
+ Builder builder(wasm);
+ pushExpression(builder.makePop(Type::exnref));
+
+ processExpressions();
+ size_t end = expressionStack.size();
+ if (start > end) {
+ throwError("block cannot pop from outside");
+ }
+ if (end - start == 1) {
+ curr->catchBody = popExpression();
+ } else {
+ auto* block = allocator.alloc<Block>();
+ pushBlockElements(block, curr->type, start);
+ block->finalize(curr->type);
+ curr->catchBody = block;
+ }
curr->finalize(curr->type);
- if (lastSeparator != BinaryConsts::End) {
- throwError("try should end with end");
+
+ if (breakTargetNames.find(catchLabel) == breakTargetNames.end()) {
+ out = curr;
+ } else {
+ // Create a new block that encloses the whole try-catch
+ auto* block = builder.makeBlock(catchLabel, curr);
+ out = block;
}
+ breakStack.pop_back();
+ breakTargetNames.erase(catchLabel);
}
void WasmBinaryBuilder::visitThrow(Throw* curr) {
diff --git a/test/break-within-catch.wasm b/test/break-within-catch.wasm
new file mode 100644
index 000000000..90b08f9a9
--- /dev/null
+++ b/test/break-within-catch.wasm
Binary files differ
diff --git a/test/break-within-catch.wasm.fromBinary b/test/break-within-catch.wasm.fromBinary
new file mode 100644
index 000000000..82ab6e717
--- /dev/null
+++ b/test/break-within-catch.wasm.fromBinary
@@ -0,0 +1,19 @@
+(module
+ (type $none_=>_none (func))
+ (func $0
+ (block $label$2
+ (try
+ (do
+ (nop)
+ )
+ (catch
+ (drop
+ (pop exnref)
+ )
+ (br $label$2)
+ )
+ )
+ )
+ )
+)
+