resolver: Validate unreachable stmts when terminator is in block(s)

Will requires updating the WGSL spec, which currently has rules looser than SPIR-V.

Fixed: tint:1167
Bug: chromium:1246163
Change-Id: Ie8fcfabc0bb89c7fb69c345475ff99c07fa04172
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/63560
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/control_block_validation_test.cc b/src/resolver/control_block_validation_test.cc
index bc8fc39..6ee6c55 100644
--- a/src/resolver/control_block_validation_test.cc
+++ b/src/resolver/control_block_validation_test.cc
@@ -111,21 +111,39 @@
 
 TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
   // loop {
+  //   var z: i32;
   //   continue;
-  //   var z : i32;
+  //   z = 1;
   // }
-  WrapInFunction(Loop(Block(
-      create<ast::ContinueStatement>(),
-      Decl(Source{{12, 34}}, Var("z", ty.i32(), ast::StorageClass::kNone)))));
+  WrapInFunction(Loop(Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                            create<ast::ContinueStatement>(),
+                            Assign(Source{{12, 34}}, "z", 1))));
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
+TEST_F(ResolverControlBlockValidationTest,
+       UnreachableCode_Loop_continue_InBlocks) {
+  // loop {
+  //   var z: i32;
+  //   {{{continue;}}}
+  //   z = 1;
+  // }
+  WrapInFunction(
+      Loop(Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                 Block(Block(Block(create<ast::ContinueStatement>()))),
+                 Assign(Source{{12, 34}}, "z", 1))));
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
 }
 
 TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
-  // for (;;;) {
+  // for (;;) {
+  //   var z: i32;
   //   continue;
-  //   var z : i32;
+  //   z = 1;
   // }
   WrapInFunction(
       For(nullptr, nullptr, nullptr,
@@ -136,6 +154,24 @@
   EXPECT_FALSE(r()->Resolve()) << r()->error();
   EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
 }
+
+TEST_F(ResolverControlBlockValidationTest,
+       UnreachableCode_ForLoop_continue_InBlocks) {
+  // for (;;) {
+  //   var z: i32;
+  //   {{{continue;}}}
+  //   z = 1;
+  // }
+  WrapInFunction(
+      For(nullptr, nullptr, nullptr,
+          Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+                Block(Block(Block(create<ast::ContinueStatement>()))),
+                Assign(Source{{12, 34}}, "z", 1))));
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
 TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
   // switch (a) {
   //   case 1: { break; var a : u32 = 2;}
@@ -152,6 +188,24 @@
   EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
 }
 
+TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
+  // switch (a) {
+  //   case 1: { {{{break;}}} var a : u32 = 2;}
+  //   default: {}
+  // }
+  auto* decl = Decl(Source{{12, 34}},
+                    Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2)));
+
+  WrapInFunction(Loop(Block(Switch(
+      Expr(1),
+      Case(Literal(1),
+           Block(Block(Block(Block(create<ast::BreakStatement>()))), decl)),
+      DefaultCase()))));
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
 TEST_F(ResolverControlBlockValidationTest,
        SwitchConditionTypeMustMatchSelectorType2_Fail) {
   // var a : u32 = 2;
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index 4e87a8c..50a8e21 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/ast/discard_statement.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
 #include "src/resolver/resolver.h"
@@ -172,18 +173,68 @@
 
 TEST_F(ResolverFunctionValidationTest, UnreachableCode_return) {
   // fn func() -> {
+  //  var a : i32;
   //  return;
-  //  var a: i32 = 2;
+  //  a = 2;
   //}
-  auto* decl = Decl(Source{{12, 34}},
-                    Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2)));
 
   Func("func", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
+       {
+           Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
            Return(),
-           decl,
-       },
-       ast::DecorationList{});
+           Assign(Source{{12, 34}}, "a", 2),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
+  // fn func() -> {
+  //  var a : i32;
+  //  {{{return;}}}
+  //  a = 2;
+  //}
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
+           Block(Block(Block(Return()))),
+           Assign(Source{{12, 34}}, "a", 2),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
+  // fn func() -> {
+  //  var a : i32;
+  //  discard;
+  //  a = 2;
+  //}
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
+           create<ast::DiscardStatement>(),
+           Assign(Source{{12, 34}}, "a", 2),
+       });
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
+TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
+  // fn func() -> {
+  //  var a : i32;
+  //  {{{discard;}}}
+  //  a = 2;
+  //}
+
+  Func("func", ast::VariableList{}, ty.void_(),
+       {
+           Decl(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2))),
+           Block(Block(Block(create<ast::DiscardStatement>()))),
+           Assign(Source{{12, 34}}, "a", 2),
+       });
   EXPECT_FALSE(r()->Resolve());
   EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
 }
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index f7c958b..1a1143f 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1964,15 +1964,23 @@
 }
 
 bool Resolver::ValidateStatements(const ast::StatementList& stmts) {
-  auto next_stmt = stmts.begin();
+  bool unreachable = false;
   for (auto* stmt : stmts) {
-    next_stmt++;
-    if (stmt->IsAnyOf<ast::ReturnStatement, ast::BreakStatement,
-                      ast::ContinueStatement>()) {
-      if (stmt != stmts.back()) {
-        AddError("code is unreachable", (*next_stmt)->source());
-        return false;
+    if (unreachable) {
+      AddError("code is unreachable", stmt->source());
+      return false;
+    }
+
+    auto* nested_stmt = stmt;
+    while (auto* block = nested_stmt->As<ast::BlockStatement>()) {
+      if (block->empty()) {
+        break;
       }
+      nested_stmt = block->statements().back();
+    }
+    if (nested_stmt->IsAnyOf<ast::ReturnStatement, ast::BreakStatement,
+                             ast::ContinueStatement, ast::DiscardStatement>()) {
+      unreachable = true;
     }
   }
   return true;
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index bad2a8e..aea2321 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -476,6 +476,32 @@
   EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
 }
 
+TEST_F(
+    ResolverValidationTest,
+    Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing_InBlocks) {  // NOLINT - line length
+  // loop  {
+  //     var z : i32;
+  //     {{{continue;}}} // Bypasses z decl
+  //     z = 1;
+  //     continue; // Ok
+  //
+  //     continuing {
+  //         z = 2;
+  //     }
+  // }
+
+  auto* body =
+      Block(Decl(Var("z", ty.i32(), ast::StorageClass::kNone)),
+            Block(Block(Block(create<ast::ContinueStatement>()))),
+            Assign(Source{{12, 34}}, "z", 2), create<ast::ContinueStatement>());
+  auto* continuing = Block(Assign(Expr("z"), 2));
+  auto* loop_stmt = Loop(body, continuing);
+  WrapInFunction(loop_stmt);
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(r()->error(), "12:34 error: code is unreachable");
+}
+
 TEST_F(ResolverValidationTest,
        Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
   // loop  {
diff --git a/src/writer/wgsl/generator_impl_function_test.cc b/src/writer/wgsl/generator_impl_function_test.cc
index 62e0606..89dd681 100644
--- a/src/writer/wgsl/generator_impl_function_test.cc
+++ b/src/writer/wgsl/generator_impl_function_test.cc
@@ -28,7 +28,6 @@
 TEST_F(WgslGeneratorImplTest, Emit_Function) {
   auto* func = Func("my_func", ast::VariableList{}, ty.void_(),
                     ast::StatementList{
-                        create<ast::DiscardStatement>(),
                         Return(),
                     },
                     ast::DecorationList{});
@@ -39,7 +38,6 @@
 
   ASSERT_TRUE(gen.EmitFunction(func));
   EXPECT_EQ(gen.result(), R"(  fn my_func() {
-    discard;
     return;
   }
 )");
@@ -50,7 +48,6 @@
       "my_func", ast::VariableList{Param("a", ty.f32()), Param("b", ty.i32())},
       ty.void_(),
       ast::StatementList{
-          create<ast::DiscardStatement>(),
           Return(),
       },
       ast::DecorationList{});
@@ -61,7 +58,6 @@
 
   ASSERT_TRUE(gen.EmitFunction(func));
   EXPECT_EQ(gen.result(), R"(  fn my_func(a : f32, b : i32) {
-    discard;
     return;
   }
 )");