spirv-fuzz: Don't replace irrelevant indices in OpAccessChain (#3988)

Part of #3980.
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
index ae9a754..38553d2 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
@@ -76,7 +76,8 @@
       // the index of the operand restricted to input operands only.
       uint32_t use_in_operand_index =
           fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
-      if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst,
+      if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(),
+                                          *GetTransformationContext(), use_inst,
                                           use_in_operand_index)) {
         continue;
       }
diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
index f35ee50..432addb 100644
--- a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
+++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
@@ -115,8 +115,9 @@
               fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
 
           // Only go ahead if this id use can be replaced in principle.
-          if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst,
-                                              in_index)) {
+          if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(),
+                                              *GetTransformationContext(),
+                                              use_inst, in_index)) {
             return;
           }
 
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index aa186c2..8c14db4 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1530,10 +1530,18 @@
 }
 
 bool IdUseCanBeReplaced(opt::IRContext* ir_context,
+                        const TransformationContext& transformation_context,
                         opt::Instruction* use_instruction,
                         uint32_t use_in_operand_index) {
   if (spvOpcodeIsAccessChain(use_instruction->opcode()) &&
       use_in_operand_index > 0) {
+    // A replacement for an irrelevant index in OpAccessChain must be clamped
+    // first.
+    if (transformation_context.GetFactManager()->IdIsIrrelevant(
+            use_instruction->GetSingleWordInOperand(use_in_operand_index))) {
+      return false;
+    }
+
     // This is an access chain index.  If the (sub-)object being accessed by the
     // given index has struct type then we cannot replace the use, as it needs
     // to be an OpConstant.
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index f23826a..4e6ec36 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -545,13 +545,16 @@
 // replacing the id use at |use_in_operand_index| of |use_instruction| with a
 // synonym or another id of appropriate type if the original id is irrelevant.
 // In particular, this checks that:
-// - the id use is not an index into a struct field in an OpAccessChain - such
+// - If id use is an index of an irrelevant id (|use_in_operand_index > 0|)
+//   in OpAccessChain - it can't be replaced.
+// - The id use is not an index into a struct field in an OpAccessChain - such
 //   indices must be constants, so it is dangerous to replace them.
-// - the id use is not a pointer function call argument, on which there are
+// - The id use is not a pointer function call argument, on which there are
 //   restrictions that make replacement problematic.
-// - the id use is not the Sample parameter of an OpImageTexelPointer
+// - The id use is not the Sample parameter of an OpImageTexelPointer
 //   instruction, as this must satisfy particular requirements.
 bool IdUseCanBeReplaced(opt::IRContext* ir_context,
+                        const TransformationContext& transformation_context,
                         opt::Instruction* use_instruction,
                         uint32_t use_in_operand_index);
 
diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp
index 0b5cf8e..24e079f 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.cpp
+++ b/source/fuzz/transformation_replace_id_with_synonym.cpp
@@ -74,7 +74,7 @@
 
   // Is the use suitable for being replaced in principle?
   if (!fuzzerutil::IdUseCanBeReplaced(
-          ir_context, use_instruction,
+          ir_context, transformation_context, use_instruction,
           message_.id_use_descriptor().in_operand_index())) {
     return false;
   }
diff --git a/source/fuzz/transformation_replace_irrelevant_id.cpp b/source/fuzz/transformation_replace_irrelevant_id.cpp
index 5cea04d..27f56eb 100644
--- a/source/fuzz/transformation_replace_irrelevant_id.cpp
+++ b/source/fuzz/transformation_replace_irrelevant_id.cpp
@@ -78,8 +78,8 @@
       message_.id_use_descriptor().in_operand_index();
 
   // The id use must be replaceable with any other id of the same type.
-  if (!fuzzerutil::IdUseCanBeReplaced(ir_context, use_instruction,
-                                      use_in_operand_index)) {
+  if (!fuzzerutil::IdUseCanBeReplaced(ir_context, transformation_context,
+                                      use_instruction, use_in_operand_index)) {
     return false;
   }
 
diff --git a/test/fuzz/transformation_replace_irrelevant_id_test.cpp b/test/fuzz/transformation_replace_irrelevant_id_test.cpp
index 828fd82..c04a091 100644
--- a/test/fuzz/transformation_replace_irrelevant_id_test.cpp
+++ b/test/fuzz/transformation_replace_irrelevant_id_test.cpp
@@ -285,6 +285,52 @@
           .IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationReplaceIrrelevantIdTest, OpAccessChainIrrelevantIndex) {
+  // Checks that we can't replace irrelevant index operands in OpAccessChain.
+  const std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 0
+         %11 = OpConstant %6 2
+         %13 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+         %12 = OpAccessChain %13 %9 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(10);
+
+  // We cannot replace the use of %10 in %12 with %11 because %10 is an
+  // irrelevant id.
+  ASSERT_FALSE(
+      TransformationReplaceIrrelevantId(
+          MakeIdUseDescriptor(
+              10, MakeInstructionDescriptor(12, SpvOpAccessChain, 0), 1),
+          11)
+          .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools