diff options
author | Alon Zakai <azakai@google.com> | 2022-05-25 10:09:57 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-25 17:09:57 +0000 |
commit | 808be8a27f7adf00f2410219507b649cdab4aa99 (patch) | |
tree | 504dc7d13929ad22137e7716043afdb4331e7599 | |
parent | 3542bd9429529bfd9fbe6a85f8f97a9388ef3c2a (diff) | |
download | binaryen-808be8a27f7adf00f2410219507b649cdab4aa99.tar.gz binaryen-808be8a27f7adf00f2410219507b649cdab4aa99.tar.bz2 binaryen-808be8a27f7adf00f2410219507b649cdab4aa99.zip |
[Wasm GC] Fix CFG traversal of call_ref and add missing validation check (#4690)
We were missing CallRef in the CFG traversal code in a place where we
note possible exceptions. As a result we thought CallRef cannot throw, and
were missing some control flow edges.
To actually detect the problem, we need to validate non-nullable locals
properly, which we were not doing. This adds that as well.
-rw-r--r-- | src/cfg/cfg-traversal.h | 3 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 32 | ||||
-rw-r--r-- | test/lit/validation/nn-locals-bad-call_ref.wast | 31 | ||||
-rw-r--r-- | test/lit/validation/nn-locals-bad.wast | 15 | ||||
-rw-r--r-- | test/lit/validation/nn-locals-ok.wast | 14 |
5 files changed, 94 insertions, 1 deletions
diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h index faaa3c830..478b4780e 100644 --- a/src/cfg/cfg-traversal.h +++ b/src/cfg/cfg-traversal.h @@ -392,7 +392,8 @@ struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> { break; } case Expression::Id::CallId: - case Expression::Id::CallIndirectId: { + case Expression::Id::CallIndirectId: + case Expression::Id::CallRefId: { self->pushTask(SubType::doEndCall, currp); break; } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index c74d95fdd..9845da72c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -23,6 +23,7 @@ #include "ir/features.h" #include "ir/global-utils.h" #include "ir/intrinsics.h" +#include "ir/local-graph.h" #include "ir/module-utils.h" #include "ir/stack-utils.h" #include "ir/utils.h" @@ -2779,6 +2780,37 @@ void FunctionValidator::visitFunction(Function* curr) { Name name = pair.second; shouldBeTrue(seen.insert(name).second, name, "local names must be unique"); } + + if (getModule()->features.hasGCNNLocals()) { + // If we have non-nullable locals, verify that no local.get can read a null + // default value. + // TODO: this can be fairly slow due to the LocalGraph. writing more code to + // do a more specific analysis (we don't need to know all sets, just + // if there is a set of a null default value that is read) could be a + // lot faster. + bool hasNNLocals = false; + for (const auto& var : curr->vars) { + if (var.isNonNullable()) { + hasNNLocals = true; + break; + } + } + if (hasNNLocals) { + LocalGraph graph(curr); + for (auto& [get, sets] : graph.getSetses) { + auto index = get->index; + // It is always ok to read nullable locals, and it is always ok to read + // params even if they are non-nullable. + if (!curr->getLocalType(index).isNonNullable() || + curr->isParam(index)) { + continue; + } + for (auto* set : sets) { + shouldBeTrue(!!set, index, "non-nullable local must not read null"); + } + } + } + } } static bool checkSegmentOffset(Expression* curr, diff --git a/test/lit/validation/nn-locals-bad-call_ref.wast b/test/lit/validation/nn-locals-bad-call_ref.wast new file mode 100644 index 000000000..357409eef --- /dev/null +++ b/test/lit/validation/nn-locals-bad-call_ref.wast @@ -0,0 +1,31 @@ +;; Test for validation of non-nullable locals + +;; RUN: not wasm-opt -all --enable-gc-nn-locals %s 2>&1 | filecheck %s + +;; CHECK: non-nullable local must not read null + +(module + (tag $tag (param i32)) + (func $func + (local $0 (ref any)) + (try + (do + (call_ref + (ref.func $func) + ) + ) + (catch $tag + (drop + (pop (i32)) + ) + ;; The path to here is from a possible exception thrown in the call_ref. + ;; This is a regression test for call_ref not being seen as possibly + ;; throwing. We should see a validation error here, as the local.get is + ;; of a null default, and it *is* reachable thanks to the call_ref. + (drop + (local.get $0) + ) + ) + ) + ) +) diff --git a/test/lit/validation/nn-locals-bad.wast b/test/lit/validation/nn-locals-bad.wast new file mode 100644 index 000000000..17a5c1404 --- /dev/null +++ b/test/lit/validation/nn-locals-bad.wast @@ -0,0 +1,15 @@ +;; Test for validation of non-nullable locals + +;; RUN: not wasm-opt -all --enable-gc-nn-locals %s 2>&1 | filecheck %s + +;; CHECK: non-nullable local must not read null + +(module + (func $foo + (local $nn (ref any)) + ;; It is not ok to read a non-nullable local. + (drop + (local.get $nn) + ) + ) +) diff --git a/test/lit/validation/nn-locals-ok.wast b/test/lit/validation/nn-locals-ok.wast new file mode 100644 index 000000000..1dfd20962 --- /dev/null +++ b/test/lit/validation/nn-locals-ok.wast @@ -0,0 +1,14 @@ +;; Test for validation of non-nullable locals + +;; RUN: wasm-opt -all --enable-gc-nn-locals %s --print | filecheck %s + +;; CHECK: (module + +(module + (func $foo (param $nn (ref any)) + ;; It is ok to read a non-nullable param. + (drop + (local.get $nn) + ) + ) +) |