[tint][ir] Check result and operand Types for nullness

Fixes: 355409306

Change-Id: I03b13eb1cf2aea0e4f41b58b9d5bd59344c4a0bc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/200059
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 01e6a56..a932edc 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -297,26 +297,27 @@
     /// @returns the styled  name for the given block
     StyledText NameOf(const Block* block);
 
-    /// Checks the given result is not null
+    /// Checks the given result is not null and its type is not null
     /// @param inst the instruction
     /// @param idx the result index
     /// @returns true if the result is not null
-    bool CheckResultNotNull(const ir::Instruction* inst, size_t idx);
+    bool CheckResult(const Instruction* inst, size_t idx);
 
     /// Checks the number of results for @p inst are exactly equal to @p count and that none of
-    /// them are null.
+    /// them are null. Also checks that the types for the results are not null
     /// @param inst the instruction
     /// @param count the number of results to check
     /// @returns true if the results count is as expected and none are null
     bool CheckResults(const ir::Instruction* inst, size_t count);
 
-    /// Checks the given operand is not null
+    /// Checks the given operand is not null and its type is not null
     /// @param inst the instruction
     /// @param idx the operand index
     /// @returns true if the operand is not null
-    bool CheckOperandNotNull(const ir::Instruction* inst, size_t idx);
+    bool CheckOperand(const Instruction* inst, size_t idx);
 
-    /// Checks the number of operands provided to @p inst and that none of them are null.
+    /// Checks the number of operands provided to @p inst and that none of them are null. Also
+    /// checks that the types for the operands are not null
     /// @param inst the instruction
     /// @param min_count the minimum number of operands to expect
     /// @param max_count the maximum number of operands to expect, if not set, than only the minimum
@@ -327,7 +328,7 @@
                        std::optional<size_t> max_count);
 
     /// Checks the number of operands for @p inst are exactly equal to @p count and that none of
-    /// them are null.
+    /// them are null. Also checks that the types for the operands are not null
     /// @param inst the instruction
     /// @param count the number of operands to check
     /// @returns true if the operands count is as expected and none are null
@@ -791,12 +792,18 @@
                         << Disassemble().NameOf(block);
 }
 
-bool Validator::CheckResultNotNull(const Instruction* inst, size_t idx) {
+bool Validator::CheckResult(const Instruction* inst, size_t idx) {
     auto* result = inst->Result(idx);
     if (TINT_UNLIKELY(result == nullptr)) {
         AddResultError(inst, idx) << "result is undefined";
         return false;
     }
+
+    if (TINT_UNLIKELY(result->Type() == nullptr)) {
+        AddResultError(inst, idx) << "result type is undefined";
+        return false;
+    }
+
     return true;
 }
 
@@ -809,19 +816,27 @@
 
     bool passed = true;
     for (size_t i = 0; i < count; i++) {
-        if (TINT_UNLIKELY(!CheckResultNotNull(inst, i))) {
+        if (TINT_UNLIKELY(!CheckResult(inst, i))) {
             passed = false;
         }
     }
     return passed;
 }
 
-bool Validator::CheckOperandNotNull(const Instruction* inst, size_t idx) {
+bool Validator::CheckOperand(const Instruction* inst, size_t idx) {
     auto* operand = inst->Operand(idx);
     if (TINT_UNLIKELY(operand == nullptr)) {
         AddError(inst, idx) << "operand is undefined";
         return false;
     }
+
+    // ir::Function does not have a meaningful type, so does not override the default Type()
+    // behaviour.
+    if (TINT_UNLIKELY(!operand->Is<ir::Function>() && operand->Type() == nullptr)) {
+        AddError(inst, idx) << "operand type is undefined";
+        return false;
+    }
+
     return true;
 }
 
@@ -847,7 +862,7 @@
 
     bool passed = true;
     for (size_t i = 0; i < inst->Operands().Length(); i++) {
-        if (TINT_UNLIKELY(!CheckOperandNotNull(inst, i))) {
+        if (TINT_UNLIKELY(!CheckOperand(inst, i))) {
             passed = false;
         }
     }
@@ -863,7 +878,7 @@
 
     bool passed = true;
     for (size_t i = 0; i < count; i++) {
-        if (TINT_UNLIKELY(!CheckOperandNotNull(inst, i))) {
+        if (TINT_UNLIKELY(!CheckOperand(inst, i))) {
             passed = false;
         }
     }
@@ -1139,16 +1154,15 @@
 
     // Check that initializer and result type match
     if (var->Initializer()) {
+        if (!CheckOperand(var, ir::Var::kInitializerOperandOffset)) {
+            return;
+        }
+
         if (var->Initializer()->Type() != var->Result(0)->Type()->UnwrapPtrOrRef()) {
             AddError(var) << "initializer type "
-                          << style::Type(var->Initializer()->Type()
-                                             ? var->Initializer()->Type()->FriendlyName()
-                                             : "undef")
+                          << style::Type(var->Initializer()->Type()->FriendlyName())
                           << " does not match store type "
-                          << style::Type(
-                                 var->Result(0)->Type()
-                                     ? var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName()
-                                     : "undef");
+                          << style::Type(var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName());
             return;
         }
     }
@@ -1830,14 +1844,7 @@
             }
             auto* value_type = from->Type();
             auto* store_type = mv->StoreType();
-
-            if (!store_type) {
-                AddError(s, Store::kToOperandOffset) << "store type must not be null";
-            }
-            if (!value_type) {
-                AddError(s, Store::kFromOperandOffset) << "value type must not be null";
-            }
-            if (store_type && value_type && value_type != store_type) {
+            if (value_type != store_type) {
                 AddError(s, Store::kFromOperandOffset)
                     << "value type " << style::Type(value_type->FriendlyName())
                     << " does not match store type " << style::Type(store_type->FriendlyName());
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 0e92fb2..538738d 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -2302,6 +2302,51 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Var_Init_NullType) {
+    auto* f = b.Function("my_func", ty.void_());
+
+    b.Append(f->Block(), [&] {
+        auto* i = b.Var<function, f32>("i");
+        i->SetInitializer(b.Constant(0_f));
+        auto* load = b.Load(i);
+        auto* load_ret = load->Result(0);
+        auto* j = b.Var<function, f32>("j");
+        j->SetInitializer(load_ret);
+        load_ret->SetType(nullptr);
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:4:5 error: load: result type is undefined
+    %3:null = load %i
+    ^^^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+:5:46 error: var: operand type is undefined
+    %j:ptr<function, f32, read_write> = var, %3
+                                             ^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%my_func = func():void {
+  $B1: {
+    %i:ptr<function, f32, read_write> = var, 0.0f
+    %3:null = load %i
+    %j:ptr<function, f32, read_write> = var, %3
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Var_HandleMissingBindingPoint) {
     auto* v = b.Var(ty.ptr<handle, i32>());
     mod.root_block->Append(v);
@@ -5691,7 +5736,7 @@
   $B1: {
   ^^^
 
-:3:11 error: store: store target operand is not a memory view
+:3:11 error: store: operand type is undefined
     store %2, 42u
           ^^
 
@@ -5724,7 +5769,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:5:15 error: store: value type must not be null
+              R"(:5:15 error: store: operand type is undefined
     store %2, %3
               ^^