summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/CMakeLists.txt1
-rw-r--r--src/ir/eh-utils.cpp71
-rw-r--r--src/ir/eh-utils.h38
-rw-r--r--src/ir/iteration.h2
-rw-r--r--src/wasm/wasm-validator.cpp65
-rw-r--r--test/exception-handling.wast26
-rw-r--r--test/exception-handling.wast.from-wast28
-rw-r--r--test/exception-handling.wast.fromBinary32
-rw-r--r--test/exception-handling.wast.fromBinary.noDebugInfo32
-rw-r--r--test/lit/passes/inlining-eh.wast8
-rw-r--r--test/lit/passes/remove-unused-names-eh.wast4
-rw-r--r--test/spec/exception-handling.wast200
12 files changed, 499 insertions, 8 deletions
diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt
index f598f3d72..b625cebe7 100644
--- a/src/ir/CMakeLists.txt
+++ b/src/ir/CMakeLists.txt
@@ -2,6 +2,7 @@ FILE(GLOB ir_HEADERS *.h)
set(ir_SOURCES
ExpressionAnalyzer.cpp
ExpressionManipulator.cpp
+ eh-utils.cpp
intrinsics.cpp
names.cpp
properties.cpp
diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp
new file mode 100644
index 000000000..de85478a2
--- /dev/null
+++ b/src/ir/eh-utils.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ir/eh-utils.h"
+#include "ir/branch-utils.h"
+
+namespace wasm {
+
+namespace EHUtils {
+
+bool isPopValid(Expression* catchBody) {
+ Expression* firstChild = nullptr;
+ auto* block = catchBody->dynCast<Block>();
+ if (!block) {
+ firstChild = catchBody;
+ } else {
+ // When there are multiple expressions within a catch body, an implicit
+ // block is created within it for convenience purposes, and if there are no
+ // branches that targets the block, it will be omitted when written back.
+ // But if there is a branch targetting this block, this block cannot be
+ // removed, and 'pop''s location will be like
+ // (catch $e
+ // (block $l0
+ // (pop i32) ;; within a block!
+ // (br $l0)
+ // ...
+ // )
+ // )
+ // which is invalid.
+ if (BranchUtils::BranchSeeker::has(block, block->name)) {
+ return false;
+ }
+ // There should be a pop somewhere
+ if (block->list.empty()) {
+ return false;
+ }
+ firstChild = *block->list.begin();
+ }
+
+ // Go down the line for the first child until we reach a leaf. A pop should be
+ // in that first-decendent line.
+ while (true) {
+ if (firstChild->is<Pop>()) {
+ return true;
+ }
+ // We use ValueChildIterator in order not to go into block/loop/try/if
+ // bodies, because a pop cannot be in those control flow expressions.
+ ValueChildIterator it(firstChild);
+ if (it.begin() == it.end()) {
+ return false;
+ }
+ firstChild = *it.begin();
+ }
+}
+
+} // namespace EHUtils
+
+} // namespace wasm
diff --git a/src/ir/eh-utils.h b/src/ir/eh-utils.h
new file mode 100644
index 000000000..844fda448
--- /dev/null
+++ b/src/ir/eh-utils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ir_eh_h
+#define wasm_ir_eh_h
+
+namespace wasm {
+
+class Expression;
+
+namespace EHUtils {
+
+// Returns true if a 'pop' instruction exists in a valid location, which means
+// right after a 'catch' instruction in binary writing order.
+// - This assumes there should be at least a single pop. So given a catch body
+// whose tag type is void or a catch_all's body, this returns false.
+// - This returns true even if there are more pops after the first one within a
+// catch body, which is invalid. That will be taken care of in validation.
+bool isPopValid(Expression* catchBody);
+
+} // namespace EHUtils
+
+} // namespace wasm
+
+#endif // wasm_ir_eh_h
diff --git a/src/ir/iteration.h b/src/ir/iteration.h
index b3553f1ec..29d183678 100644
--- a/src/ir/iteration.h
+++ b/src/ir/iteration.h
@@ -53,6 +53,8 @@ template<class Specific> class AbstractChildIterator {
return index != other.index || &parent != &(other.parent);
}
+ bool operator==(const Iterator& other) const { return !(*this != other); }
+
void operator++() { index++; }
Expression*& operator*() {
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index bb0e99cc2..86b537bf5 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -19,6 +19,7 @@
#include <sstream>
#include <unordered_set>
+#include "ir/eh-utils.h"
#include "ir/features.h"
#include "ir/global-utils.h"
#include "ir/intrinsics.h"
@@ -2143,6 +2144,70 @@ void FunctionValidator::visitTry(Try* curr) {
curr,
"try cannot have both catch and delegate at the same time");
+ // Given a catch body, find pops corresponding to the catch
+ auto findPops = [](Expression* expr) {
+ SmallVector<Pop*, 1> pops;
+ SmallVector<Expression*, 8> work;
+ work.push_back(expr);
+ while (!work.empty()) {
+ auto* curr = work.back();
+ work.pop_back();
+ if (auto* pop = curr->dynCast<Pop>()) {
+ pops.push_back(pop);
+ } else if (auto* try_ = curr->dynCast<Try>()) {
+ // We don't go into inner catch bodies; pops in inner catch bodies
+ // belong to the inner catches
+ work.push_back(try_->body);
+ } else {
+ for (auto* child : ChildIterator(curr)) {
+ work.push_back(child);
+ }
+ }
+ }
+ return pops;
+ };
+
+ for (Index i = 0; i < curr->catchTags.size(); i++) {
+ Name tagName = curr->catchTags[i];
+ auto* tag = getModule()->getTagOrNull(tagName);
+ if (!shouldBeTrue(tag != nullptr, curr, "")) {
+ getStream() << "tag name is invalid: " << tagName << "\n";
+ }
+
+ auto* catchBody = curr->catchBodies[i];
+ SmallVector<Pop*, 1> pops = findPops(catchBody);
+ if (tag->sig.params == Type::none) {
+ if (!shouldBeTrue(pops.empty(), curr, "")) {
+ getStream() << "catch's tag (" << tagName
+ << ") doesn't have any params, but there are pops";
+ }
+ } else {
+ if (shouldBeTrue(pops.size() == 1, curr, "")) {
+ auto* pop = *pops.begin();
+ if (!shouldBeSubType(pop->type, tag->sig.params, curr, "")) {
+ getStream()
+ << "catch's tag (" << tagName
+ << ")'s pop doesn't have the same type as the tag's params";
+ }
+ if (!shouldBeTrue(EHUtils::isPopValid(catchBody), curr, "")) {
+ getStream() << "catch's body (" << tagName
+ << ")'s pop's location is not valid";
+ }
+ } else {
+ getStream() << "catch's tag (" << tagName
+ << ") has params, so there should be a single pop within "
+ "the catch body";
+ }
+ }
+ }
+
+ if (curr->hasCatchAll()) {
+ auto* catchAllBody = curr->catchBodies.back();
+ shouldBeTrue(findPops(catchAllBody).empty(),
+ curr,
+ "catch_all's body should not have pops");
+ }
+
if (curr->isDelegate()) {
noteDelegate(curr->delegateTarget, curr);
}
diff --git a/test/exception-handling.wast b/test/exception-handling.wast
index c608e860a..cd436296b 100644
--- a/test/exception-handling.wast
+++ b/test/exception-handling.wast
@@ -2,6 +2,7 @@
(tag $e-i32 (param i32))
(tag $e-i64 (param i64))
(tag $e-i32-i64 (param i32 i64))
+ (tag $e-anyref (param anyref))
(tag $e-empty)
(func $foo)
@@ -311,4 +312,29 @@
)
)
)
+
+ (func $pop_test
+ (try
+ (do)
+ (catch $e-i32
+ (throw $e-i32
+ (if (result i32)
+ ;; pop is within an if condition, so this is OK.
+ (pop i32)
+ (i32.const 0)
+ (i32.const 3)
+ )
+ )
+ )
+ )
+
+ (try
+ (do)
+ (catch $e-anyref
+ (drop
+ (pop funcref) ;; pop can be subtype
+ )
+ )
+ )
+ )
)
diff --git a/test/exception-handling.wast.from-wast b/test/exception-handling.wast.from-wast
index 1ce7a84b7..ee409652b 100644
--- a/test/exception-handling.wast.from-wast
+++ b/test/exception-handling.wast.from-wast
@@ -3,9 +3,11 @@
(type $i32_=>_none (func (param i32)))
(type $i64_=>_none (func (param i64)))
(type $i32_i64_=>_none (func (param i32 i64)))
+ (type $anyref_=>_none (func (param anyref)))
(tag $e-i32 (param i32))
(tag $e-i64 (param i64))
(tag $e-i32-i64 (param i32 i64))
+ (tag $e-anyref (param anyref))
(tag $e-empty (param))
(func $foo
(nop)
@@ -351,4 +353,30 @@
)
)
)
+ (func $pop_test
+ (try $try
+ (do
+ (nop)
+ )
+ (catch $e-i32
+ (throw $e-i32
+ (if (result i32)
+ (pop i32)
+ (i32.const 0)
+ (i32.const 3)
+ )
+ )
+ )
+ )
+ (try $try28
+ (do
+ (nop)
+ )
+ (catch $e-anyref
+ (drop
+ (pop funcref)
+ )
+ )
+ )
+ )
)
diff --git a/test/exception-handling.wast.fromBinary b/test/exception-handling.wast.fromBinary
index d37f48a23..9a89a0beb 100644
--- a/test/exception-handling.wast.fromBinary
+++ b/test/exception-handling.wast.fromBinary
@@ -3,10 +3,12 @@
(type $i32_=>_none (func (param i32)))
(type $i64_=>_none (func (param i64)))
(type $i32_i64_=>_none (func (param i32 i64)))
+ (type $anyref_=>_none (func (param anyref)))
(tag $tag$0 (param i32))
(tag $tag$1 (param i64))
(tag $tag$2 (param i32 i64))
- (tag $tag$3 (param))
+ (tag $tag$3 (param anyref))
+ (tag $tag$4 (param))
(func $foo
(nop)
)
@@ -269,7 +271,7 @@
(do
(nop)
)
- (catch $tag$3
+ (catch $tag$4
(nop)
)
)
@@ -380,5 +382,31 @@
)
)
)
+ (func $pop_test
+ (try $label$5
+ (do
+ (nop)
+ )
+ (catch $tag$0
+ (throw $tag$0
+ (if (result i32)
+ (pop i32)
+ (i32.const 0)
+ (i32.const 3)
+ )
+ )
+ )
+ )
+ (try $label$8
+ (do
+ (nop)
+ )
+ (catch $tag$3
+ (drop
+ (pop anyref)
+ )
+ )
+ )
+ )
)
diff --git a/test/exception-handling.wast.fromBinary.noDebugInfo b/test/exception-handling.wast.fromBinary.noDebugInfo
index 1a36aaa66..eb6b98bd8 100644
--- a/test/exception-handling.wast.fromBinary.noDebugInfo
+++ b/test/exception-handling.wast.fromBinary.noDebugInfo
@@ -3,10 +3,12 @@
(type $i32_=>_none (func (param i32)))
(type $i64_=>_none (func (param i64)))
(type $i32_i64_=>_none (func (param i32 i64)))
+ (type $anyref_=>_none (func (param anyref)))
(tag $tag$0 (param i32))
(tag $tag$1 (param i64))
(tag $tag$2 (param i32 i64))
- (tag $tag$3 (param))
+ (tag $tag$3 (param anyref))
+ (tag $tag$4 (param))
(func $0
(nop)
)
@@ -269,7 +271,7 @@
(do
(nop)
)
- (catch $tag$3
+ (catch $tag$4
(nop)
)
)
@@ -380,5 +382,31 @@
)
)
)
+ (func $5
+ (try $label$5
+ (do
+ (nop)
+ )
+ (catch $tag$0
+ (throw $tag$0
+ (if (result i32)
+ (pop i32)
+ (i32.const 0)
+ (i32.const 3)
+ )
+ )
+ )
+ )
+ (try $label$8
+ (do
+ (nop)
+ )
+ (catch $tag$3
+ (drop
+ (pop anyref)
+ )
+ )
+ )
+ )
)
diff --git a/test/lit/passes/inlining-eh.wast b/test/lit/passes/inlining-eh.wast
index 5ec3c55dc..ea49c9550 100644
--- a/test/lit/passes/inlining-eh.wast
+++ b/test/lit/passes/inlining-eh.wast
@@ -11,7 +11,9 @@
(try $label
(do)
(catch $tag$0
- (nop)
+ (drop
+ (pop i32)
+ )
)
)
)
@@ -27,7 +29,9 @@
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag$0
- ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/remove-unused-names-eh.wast b/test/lit/passes/remove-unused-names-eh.wast
index fcdbf0f07..e7a26cbae 100644
--- a/test/lit/passes/remove-unused-names-eh.wast
+++ b/test/lit/passes/remove-unused-names-eh.wast
@@ -10,7 +10,7 @@
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (catch $tag$0
+ ;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (try $label$8
;; CHECK-NEXT: (do
;; CHECK-NEXT: (try
@@ -32,7 +32,7 @@
(func $func0
(try $label$9 ;; needed due to a rethrow
(do)
- (catch $tag$0
+ (catch_all
(try $label$8 ;; needed due to a delegate
(do
(try $label$6 ;; this one is not needed
diff --git a/test/spec/exception-handling.wast b/test/spec/exception-handling.wast
index 1b7907474..33454f1f9 100644
--- a/test/spec/exception-handling.wast
+++ b/test/spec/exception-handling.wast
@@ -191,6 +191,7 @@
)
)
(catch $e-i32
+ (drop (pop i32))
(rethrow $l0) ;; rethrow (i32.const 1) again
)
)
@@ -232,6 +233,7 @@
(assert_invalid
(module
+ (tag $e-i32 (param i32))
(func $f0
(try
(do (i32.const 0))
@@ -329,3 +331,201 @@
)
"all rethrow targets must be valid"
)
+
+(assert_invalid
+ (module
+ (func $f0
+ (try
+ (do)
+ (catch $e)
+ )
+ )
+ )
+ "catch's tag name is invalid: e"
+)
+
+(assert_invalid
+ (module
+ (tag $e-none (param))
+ (func $f0 (result i32)
+ (try (result i32)
+ (do
+ (i32.const 0)
+ )
+ (catch $e-none
+ (pop i32)
+ )
+ )
+ )
+ )
+ "catch's tag (e-none) doesn't have any params, but there are pops"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32)
+ )
+ )
+ )
+ "catch's tag (e-i32) has params, so there should be a single pop within the catch body"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ (drop
+ (pop i32)
+ )
+ )
+ )
+ )
+ )
+ "catch's tag (e-i32) has params, so there should be a single pop within the catch body"
+)
+
+(assert_invalid
+ (module
+ (func $f0 (result i32)
+ (try (result i32)
+ (do
+ (i32.const 0)
+ )
+ (catch_all
+ (pop i32)
+ )
+ )
+ )
+ )
+ "catch_all's body should not have pops"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0 (result f32)
+ (try (result f32)
+ (do
+ (f32.const 0)
+ )
+ (catch $e-i32
+ (pop f32)
+ )
+ )
+ )
+ )
+ "catch's tag (e-i32)'s pop doesn't have the same type as the tag's params"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0 (result i32)
+ (try (result i32)
+ (do
+ (i32.const 0)
+ )
+ (catch $e-i32
+ (drop
+ (i32.const 0)
+ )
+ (pop i32) ;; Not the first children within 'catch'
+ )
+ )
+ )
+ )
+ "catch's body (e-i32)'s pop's location is not valid"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32
+ (throw $e-i32
+ (block (result i32)
+ (pop i32) ;; pop is within a block
+ )
+ )
+ )
+ )
+ )
+ )
+ "catch's body (e-i32)'s pop's location is not valid"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32
+ (throw $e-i32
+ (loop (result i32)
+ (pop i32) ;; pop is within a loop
+ )
+ )
+ )
+ )
+ )
+ )
+ "catch's body (e-i32)'s pop's location is not valid"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32
+ (throw $e-i32
+ (try (result i32)
+ (do
+ (pop i32) ;; pop is within a try
+ )
+ (catch_all
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ "catch's body (e-i32)'s pop's location is not valid"
+)
+
+(assert_invalid
+ (module
+ (tag $e-i32 (param i32))
+ (func $f0
+ (try
+ (do)
+ (catch $e-i32
+ (throw $e-i32
+ (if (result i32)
+ (i32.const 0)
+ (pop i32) ;; pop is within an if true body
+ (i32.const 3)
+ )
+ )
+ )
+ )
+ )
+ )
+ "catch's body (e-i32)'s pop's location is not valid"
+)