diff options
author | Heejin Ahn <aheejin@gmail.com> | 2020-09-15 23:46:32 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-15 23:46:32 +0900 |
commit | bced89d2986bde990bf9bf52306e55772b02707e (patch) | |
tree | 5fe2cf3bbaed1f19e1ddd87aa0e4d2d63b0c3f65 | |
parent | bcc6f2994e8f71b633b1d0257547aeed691f6ceb (diff) | |
download | binaryen-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.h | 4 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 102 | ||||
-rw-r--r-- | test/break-within-catch.wasm | bin | 0 -> 32 bytes | |||
-rw-r--r-- | test/break-within-catch.wasm.fromBinary | 19 |
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 Binary files differnew file mode 100644 index 000000000..90b08f9a9 --- /dev/null +++ b/test/break-within-catch.wasm 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) + ) + ) + ) + ) +) + |