[ir] Validate terminator operands
If they are not nullptr, we need to make sure they are alive and in
scope.
Fixed: 420684375
Change-Id: Ie1216a1f216d385c71e844600a7555d6d04208c0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/243994
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index b3ed46a..3e9cea6 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -1698,12 +1698,13 @@
bool Validator::CheckOperand(const Instruction* inst, size_t idx) {
auto* operand = inst->Operand(idx);
- // var instructions are allowed to have a nullptr operands
- if (inst->Is<Var>() && operand == nullptr) {
- return true;
- }
-
if (DAWN_UNLIKELY(operand == nullptr)) {
+ // var instructions are allowed to have a nullptr initializers.
+ // terminator instructions use nullptr operands to signal 'undef'.
+ if (inst->IsAnyOf<Terminator, Var>()) {
+ return true;
+ }
+
AddError(inst, idx) << "operand is undefined";
return false;
}
@@ -3462,8 +3463,10 @@
return;
}
- // Note, transforms create `undef` terminator arguments (this is done in MergeReturn and
- // DemoteToHelper) so we can't add validation.
+ // Operands must be alive and in scope if they are not nullptr.
+ if (!CheckOperands(b)) {
+ return;
+ }
tint::Switch(
b, //
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 08bc348..20ce4db 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -1208,6 +1208,34 @@
)")) << res.Failure();
}
+TEST_F(IR_ValidatorTest, Scoping_UseAfterNestedDecl_InTerminator) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* outer = b.If(true);
+ outer->AddResult(b.InstructionResult<u32>());
+
+ b.Append(outer->True(), [&] {
+ Let* decl = nullptr;
+ auto* inner = b.If(true);
+ b.Append(inner->True(), [&] {
+ decl = b.Let("decl", 42_u);
+ b.ExitIf(inner);
+ });
+ b.ExitIf(outer, decl);
+ });
+
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_THAT(res.Failure().reason,
+ testing::HasSubstr(R"(:11:17 error: exit_if: %decl is not in scope
+ exit_if %decl # if_1
+ ^^^^^
+)")) << res.Failure();
+}
+
TEST_F(IR_ValidatorTest, OverrideWithoutCapability) {
b.Append(mod.root_block, [&] { b.Override("a", 1_u); });