Disallow use of OpCompositeExtract/OpCompositeInsert with no indices (#2980)

diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index d2d029d..e54a0cd 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -1407,20 +1407,6 @@
   };
 }
 
-// Folds an OpCompositeExtract instruction with no indexes into an OpCopyObject.
-bool ExtractWithNoIndexes(IRContext*, Instruction* inst,
-                          const std::vector<const analysis::Constant*>&) {
-  assert(inst->opcode() == SpvOpCompositeExtract &&
-         "Wrong opcode.  Should be OpCompositeExtract.");
-
-  if (inst->NumInOperands() > 1) {
-    return false;
-  }
-
-  inst->SetOpcode(SpvOpCopyObject);
-  return true;
-}
-
 FoldingRule InsertFeedingExtract() {
   return [](IRContext* context, Instruction* inst,
             const std::vector<const analysis::Constant*>&) {
@@ -2227,7 +2213,6 @@
   // Take that into consideration.
   rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct());
 
-  rules_[SpvOpCompositeExtract].push_back(ExtractWithNoIndexes);
   rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract());
   rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract());
   rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract());
diff --git a/source/val/validate_composites.cpp b/source/val/validate_composites.cpp
index 9b7f592..eb8a324 100644
--- a/source/val/validate_composites.cpp
+++ b/source/val/validate_composites.cpp
@@ -30,8 +30,8 @@
 // OpCompositeInsert instruction. The function traverses the hierarchy of
 // nested data structures (structs, arrays, vectors, matrices) as directed by
 // the sequence of indices in the instruction. May return error if traversal
-// fails (encountered non-composite, out of bounds, nesting too deep).
-// Returns the type of Composite operand if the instruction has no indices.
+// fails (encountered non-composite, out of bounds, no indices, nesting too
+// deep).
 spv_result_t GetExtractInsertValueType(ValidationState_t& _,
                                        const Instruction* inst,
                                        uint32_t* member_type) {
@@ -40,10 +40,15 @@
   uint32_t word_index = opcode == SpvOpCompositeExtract ? 4 : 5;
   const uint32_t num_words = static_cast<uint32_t>(inst->words().size());
   const uint32_t composite_id_index = word_index - 1;
-
   const uint32_t num_indices = num_words - word_index;
   const uint32_t kCompositeExtractInsertMaxNumIndices = 255;
-  if (num_indices > kCompositeExtractInsertMaxNumIndices) {
+
+  if (num_indices == 0) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected at least one index to Op"
+           << spvOpcodeString(inst->opcode()) << ", zero found";
+
+  } else if (num_indices > kCompositeExtractInsertMaxNumIndices) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "The number of indexes in Op" << spvOpcodeString(opcode)
            << " may not exceed " << kCompositeExtractInsertMaxNumIndices
@@ -386,20 +391,20 @@
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Cannot extract from a composite of 8- or 16-bit types";
   }
+
   return SPV_SUCCESS;
 }
 
 spv_result_t ValidateCompositeInsert(ValidationState_t& _,
                                      const Instruction* inst) {
-  const SpvOp opcode = inst->opcode();
   const uint32_t object_type = _.GetOperandTypeId(inst, 2);
   const uint32_t composite_type = _.GetOperandTypeId(inst, 3);
   const uint32_t result_type = inst->type_id();
   if (result_type != composite_type) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "The Result Type must be the same as Composite type in Op"
-           << spvOpcodeString(opcode) << " yielding Result Id " << result_type
-           << ".";
+           << spvOpcodeString(inst->opcode()) << " yielding Result Id "
+           << result_type << ".";
   }
 
   uint32_t member_type = 0;
@@ -421,6 +426,7 @@
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Cannot insert into a composite of 8- or 16-bit types";
   }
+
   return SPV_SUCCESS;
 }
 
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 5c2adec..cb81f1c 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -3333,16 +3333,7 @@
             "%3 = OpCompositeExtract %float %2 4\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        3, 0),
-    // Test case 14: Fold OpCompositeExtract with no indexes.
-    InstructionFoldingCase<uint32_t>(
-        Header() + "%main = OpFunction %void None %void_func\n" +
-            "%main_lab = OpLabel\n" +
-            "%2 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1\n" +
-            "%3 = OpCompositeExtract %v4float %2\n" +
-            "OpReturn\n" +
-            "OpFunctionEnd",
-        3, 2)
+        3, 0)
 ));
 
 INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
index 7b850e3..50dc501 100644
--- a/test/opt/reduce_load_size_test.cpp
+++ b/test/opt/reduce_load_size_test.cpp
@@ -321,34 +321,6 @@
   SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
 }
 
-TEST_F(ReduceLoadSizeTest, extract_with_no_index) {
-  const std::string test =
-      R"(
-               OpCapability ImageGatherExtended
-               OpExtension ""
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "P�Ma'" %12 %17
-               OpExecutionMode %4 OriginUpperLeft
-       %void = OpTypeVoid
-          %3 = OpTypeFunction %void
-      %float = OpTypeFloat 32
-  %_struct_7 = OpTypeStruct %float %float
-%_ptr_Input__struct_7 = OpTypePointer Input %_struct_7
-%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7
-         %12 = OpVariable %_ptr_Input__struct_7 Input
-         %17 = OpVariable %_ptr_Output__struct_7 Output
-          %4 = OpFunction %void DontInline|Pure|Const %3
-        %245 = OpLabel
-         %13 = OpLoad %_struct_7 %12
-         %33 = OpCompositeExtract %_struct_7 %13
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  auto result = SinglePassRunAndDisassemble<ReduceLoadSize>(test, true, true);
-  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
-}
-
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 1228076..3cf46ca 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1899,37 +1899,6 @@
   SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
 }
 
-TEST_F(ScalarReplacementTest, ExtractWithNoIndex) {
-  const std::string text = R"(
-; CHECK: [[var1:%\w+]] = OpVariable %_ptr_Function_float Function
-; CHECK: [[var2:%\w+]] = OpVariable %_ptr_Function_float Function
-; CHECK: [[ld1:%\w+]] = OpLoad %float [[var1]]
-; CHECK: [[ld2:%\w+]] = OpLoad %float [[var2]]
-; CHECK: [[constr:%\w+]] = OpCompositeConstruct {{%\w+}} [[ld2]] [[ld1]]
-; CHECK: OpCompositeExtract {{%\w+}} [[constr]]
-OpCapability Shader
-OpExtension ""
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %1 "main"
-OpExecutionMode %1 OriginUpperLeft
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_struct_5 = OpTypeStruct %float %float
-%_ptr_Private__struct_5 = OpTypePointer Private %_struct_5
-%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
-%1 = OpFunction %void Inline %3
-%8 = OpLabel
-%9 = OpVariable %_ptr_Function__struct_5 Function
-%10 = OpLoad %_struct_5 %9
-%11 = OpCompositeExtract %_struct_5 %10
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
-}
-
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/vector_dce_test.cpp b/test/opt/vector_dce_test.cpp
index a978a07..594995c 100644
--- a/test/opt/vector_dce_test.cpp
+++ b/test/opt/vector_dce_test.cpp
@@ -1158,74 +1158,6 @@
   SinglePassRunAndCheck<VectorDCE>(text, text, true, true);
 }
 
-TEST_F(VectorDCETest, InsertWithNoIndices) {
-  const std::string text = R"(
-; CHECK: OpEntryPoint Fragment {{%\w+}} "PSMain" [[in1:%\w+]] [[in2:%\w+]] [[out:%\w+]]
-; CHECK: OpFunction
-; CHECK: [[ld:%\w+]] = OpLoad %v4float [[in2]]
-; CHECK: OpStore [[out]] [[ld]]
-               OpCapability Shader
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
-               OpExecutionMode %1 OriginUpperLeft
-       %void = OpTypeVoid
-          %5 = OpTypeFunction %void
-      %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-          %2 = OpVariable %_ptr_Input_v4float Input
-          %14 = OpVariable %_ptr_Input_v4float Input
-          %3 = OpVariable %_ptr_Output_v4float Output
-          %1 = OpFunction %void None %5
-         %10 = OpLabel
-         %13 = OpLoad %v4float %14
-         %11 = OpLoad %v4float %2
-         %12 = OpCompositeInsert %v4float %13 %11
-               OpStore %3 %12
-               OpReturn
-               OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<VectorDCE>(text, true);
-}
-
-TEST_F(VectorDCETest, ExtractWithNoIndices) {
-  const std::string text = R"(
-; CHECK: OpLoad %float
-; CHECK: [[ld:%\w+]] = OpLoad %v4float
-; CHECK: [[ex1:%\w+]] = OpCompositeExtract %v4float [[ld]]
-; CHECK: [[ex2:%\w+]] = OpCompositeExtract %float [[ex1]] 1
-; CHECK: OpStore {{%\w+}} [[ex2]]
-               OpCapability Shader
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
-               OpExecutionMode %1 OriginUpperLeft
-       %void = OpTypeVoid
-          %5 = OpTypeFunction %void
-      %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-%_ptr_Input_float = OpTypePointer Input %float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%_ptr_Output_float = OpTypePointer Output %float
-          %2 = OpVariable %_ptr_Input_v4float Input
-          %14 = OpVariable %_ptr_Input_float Input
-          %3 = OpVariable %_ptr_Output_float Output
-          %1 = OpFunction %void None %5
-         %10 = OpLabel
-         %13 = OpLoad %float %14
-         %11 = OpLoad %v4float %2
-         %12 = OpCompositeInsert %v4float %13 %11 0
-         %20 = OpCompositeExtract %v4float %12
-         %21 = OpCompositeExtract %float %20 1
-               OpStore %3 %21
-               OpReturn
-               OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<VectorDCE>(text, true);
-}
-
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index 74b6f20..e970562 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -648,7 +648,6 @@
 %val16 = OpCompositeExtract %f32 %struct 4 1000 1
 %val17 = OpCompositeExtract %f32 %struct 5 0
 %val18 = OpCompositeExtract %u32 %struct 5 1
-%val19 = OpCompositeExtract %big_struct %struct
 )";
 
   CompileSuccessfully(GenerateShaderCode(body));
@@ -767,6 +766,18 @@
                         "indexes still remain to be traversed."));
 }
 
+TEST_F(ValidateComposites, CompositeExtractNoIndices) {
+  const std::string body = R"(
+%struct = OpLoad %big_struct %var_big_struct
+%val1 = OpCompositeExtract %big_struct %struct
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected at least one index to OpCompositeExtract"));
+}
+
 TEST_F(ValidateComposites, CompositeExtractWrongType1) {
   const std::string body = R"(
 %struct = OpLoad %big_struct %var_big_struct
@@ -861,7 +872,6 @@
 %val16 = OpCompositeInsert %big_struct %f32_3 %struct 4 1000 1
 %val17 = OpCompositeInsert %big_struct %f32_3 %struct 5 0
 %val18 = OpCompositeInsert %big_struct %u32_3 %struct 5 1
-%val19 = OpCompositeInsert %big_struct %struct %struct
 )";
 
   CompileSuccessfully(GenerateShaderCode(body));
@@ -1157,9 +1167,8 @@
               HasSubstr("The Result Type must be the same as Composite type"));
 }
 
-// Valid: No Indexes were passed to OpCompositeExtract, and the Result Type is
-// the same as the Base Composite type.
-TEST_F(ValidateComposites, CompositeExtractNoIndexesGood) {
+// Invalid: No Indexes were passed to OpCompositeExtract.
+TEST_F(ValidateComposites, CompositeExtractNoIndices2) {
   std::ostringstream spirv;
   spirv << GetHeaderForTestsFromValId() << std::endl;
   spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl;
@@ -1167,29 +1176,32 @@
   spirv << R"(OpReturn
               OpFunctionEnd)";
   CompileSuccessfully(spirv.str());
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected at least one index to OpCompositeExtract, zero found"));
 }
 
-// Invalid: No Indexes were passed to OpCompositeExtract, but the Result Type is
-// different from the Base Composite type.
-TEST_F(ValidateComposites, CompositeExtractNoIndexesBad) {
+// Invalid: No Indexes were passed to OpCompositeExtract.
+TEST_F(ValidateComposites, CompositeExtractNoIndicesWrongResultType) {
   std::ostringstream spirv;
   spirv << GetHeaderForTestsFromValId() << std::endl;
   spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl;
-  spirv << "%float_entry = OpCompositeExtract  %float %matrix" << std::endl;
+  spirv << "%float_entry = OpCompositeExtract %float %matrix" << std::endl;
   spirv << R"(OpReturn
               OpFunctionEnd)";
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Result type (OpTypeFloat) does not match the type "
-                        "that results from indexing into the composite "
-                        "(OpTypeMatrix)."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected at least one index to OpCompositeExtract, zero found"));
 }
 
-// Valid: No Indexes were passed to OpCompositeInsert, and the type of the
+// Invalid: No Indices were passed to OpCompositeInsert, and the type of the
 // Object<id> argument matches the Composite type.
-TEST_F(ValidateComposites, CompositeInsertMissingIndexesGood) {
+TEST_F(ValidateComposites, CompositeInsertMissingIndices) {
   std::ostringstream spirv;
   spirv << GetHeaderForTestsFromValId() << std::endl;
   spirv << "%matrix   = OpLoad %mat4x3 %my_matrix" << std::endl;
@@ -1199,12 +1211,16 @@
               OpReturn
               OpFunctionEnd)";
   CompileSuccessfully(spirv.str());
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected at least one index to OpCompositeInsert, zero found"));
 }
 
-// Invalid: No Indexes were passed to OpCompositeInsert, but the type of the
+// Invalid: No Indices were passed to OpCompositeInsert, but the type of the
 // Object<id> argument does not match the Composite type.
-TEST_F(ValidateComposites, CompositeInsertMissingIndexesBad) {
+TEST_F(ValidateComposites, CompositeInsertMissingIndices2) {
   std::ostringstream spirv;
   spirv << GetHeaderForTestsFromValId() << std::endl;
   spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl;
@@ -1214,10 +1230,10 @@
               OpFunctionEnd)";
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The Object type (OpTypeInt) does not match the type "
-                        "that results from indexing into the Composite "
-                        "(OpTypeMatrix)."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected at least one index to OpCompositeInsert, zero found"));
 }
 
 // Valid: Tests that we can index into Struct, Array, Matrix, and Vector!