resolver: Reject non-storage runtime-sized arrays

There were several cases where we were not rejecting these which were
leading to ICEs or bad codegen.

Fixed: tint:1248
Change-Id: I7cdf3b74d92b81b1067ad908af423ea0b5442328
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/76161
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index e018a56..e346ed3 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -2650,6 +2650,14 @@
   }
 
   if (auto* arr = ty->As<sem::Array>()) {
+    if (arr->IsRuntimeSized() && sc != ast::StorageClass::kStorage) {
+      AddError(
+          "runtime-sized arrays can only be used in the <storage> storage "
+          "class",
+          usage);
+      return false;
+    }
+
     return ApplyStorageClassUsageToType(
         sc, const_cast<sem::Type*>(arr->ElemType()), usage);
   }
diff --git a/src/resolver/resolver_validation.cc b/src/resolver/resolver_validation.cc
index 5a375aa..12e3b34 100644
--- a/src/resolver/resolver_validation.cc
+++ b/src/resolver/resolver_validation.cc
@@ -571,19 +571,6 @@
             decl->source);
         return false;
       }
-      for (auto* member : str->Members()) {
-        if (auto* arr = member->Type()->As<sem::Array>()) {
-          if (arr->IsRuntimeSized()) {
-            AddError(
-                "structure containing a runtime sized array "
-                "cannot be used as a uniform buffer",
-                decl->source);
-            AddNote("structure is declared here", str->Declaration()->source);
-            return false;
-          }
-        }
-      }
-
       break;
     }
     default:
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 4c6799f..f836088 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -45,6 +45,52 @@
             "the function storage class");
 }
 
+TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) {
+  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
+         ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArrayInStruct) {
+  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kPrivate);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArray) {
+  Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
+         ast::StorageClass::kWorkgroup);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable v)");
+}
+
+TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArrayInStruct) {
+  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
+  Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kWorkgroup);
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
+}
+
 TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
   // var<storage> g : i32;
   Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
@@ -170,9 +216,11 @@
          });
 
   ASSERT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "56:78 error: structure containing a runtime sized array cannot be "
-            "used as a uniform buffer\n12:34 note: structure is declared here");
+  EXPECT_EQ(
+      r()->error(),
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+note: while analysing structure member S.m
+56:78 note: while instantiating variable svar)");
 }
 
 TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 2592246..08f37be 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -411,9 +411,10 @@
        });
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "12:34 error: runtime arrays may only appear as the last member of "
-            "a struct");
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating variable a)");
 }
 
 TEST_F(ResolverTypeValidationTest, Struct_Member_VectorNoType) {
@@ -575,7 +576,8 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while instantiating variable g)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
@@ -586,7 +588,8 @@
 
   EXPECT_EQ(
       r()->error(),
-      R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
+      R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while instantiating variable g)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
@@ -610,9 +613,30 @@
        });
 
   EXPECT_FALSE(r()->Resolve()) << r()->error();
-  EXPECT_EQ(r()->error(),
-            "12:34 error: runtime arrays may only appear as the last member of "
-            "a struct");
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating parameter a)");
+}
+
+TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsParameter_Fail) {
+  // fn func(a : ptr<workgroup, array<u32>>) {}
+
+  auto* param =
+      Param(Source{{12, 34}}, "a",
+            ty.pointer(ty.array<i32>(), ast::StorageClass::kWorkgroup));
+
+  Func("func", ast::VariableList{param}, ty.void_(),
+       ast::StatementList{
+           Return(),
+       },
+       ast::DecorationList{});
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+12:34 note: while instantiating parameter a)");
 }
 
 TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
diff --git a/src/resolver/var_let_validation_test.cc b/src/resolver/var_let_validation_test.cc
index bc0377a..9ee07b0 100644
--- a/src/resolver/var_let_validation_test.cc
+++ b/src/resolver/var_let_validation_test.cc
@@ -262,13 +262,17 @@
 }
 
 TEST_F(ResolverVarLetValidationTest, NonConstructibleType_RuntimeArray) {
-  auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
-  auto* v = Var("v", ty.Of(s));
+  auto* s = Structure("S", {Member(Source{{56, 78}}, "m", ty.array(ty.i32()))},
+                      {StructBlock()});
+  auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
   WrapInFunction(v);
 
   EXPECT_FALSE(r()->Resolve());
-  EXPECT_EQ(r()->error(),
-            "error: function variable must have a constructible type");
+  EXPECT_EQ(
+      r()->error(),
+      R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
+56:78 note: while analysing structure member S.m
+12:34 note: while instantiating variable v)");
 }
 
 TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Struct_WithAtomic) {