spirv-fuzz: Fix mismatch with shrinker step limit (#3985)

Fixes #3984.
diff --git a/source/fuzz/added_function_reducer.cpp b/source/fuzz/added_function_reducer.cpp
index 0ea88c8..e7cb027 100644
--- a/source/fuzz/added_function_reducer.cpp
+++ b/source/fuzz/added_function_reducer.cpp
@@ -45,7 +45,7 @@
       validator_options_(validator_options),
       shrinker_step_limit_(shrinker_step_limit),
       num_existing_shrink_attempts_(num_existing_shrink_attempts),
-      num_reduction_attempts_(0) {}
+      num_reducer_interestingness_function_invocations_(0) {}
 
 AddedFunctionReducer::~AddedFunctionReducer() = default;
 
@@ -99,16 +99,25 @@
   protobufs::TransformationSequence transformation_sequence_out;
   ReplayAdaptedTransformations(reduced_binary, &binary_out,
                                &transformation_sequence_out);
+  // We subtract 1 from |num_reducer_interestingness_function_invocations_| to
+  // account for the fact that spirv-reduce invokes its interestingness test
+  // once before reduction commences in order to check that the initial module
+  // is interesting.
+  assert(num_reducer_interestingness_function_invocations_ > 0 &&
+         "At a minimum spirv-reduce should have invoked its interestingness "
+         "test once.");
   return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
-          std::move(transformation_sequence_out), num_reduction_attempts_};
+          std::move(transformation_sequence_out),
+          num_reducer_interestingness_function_invocations_ - 1};
 }
 
 bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
     const std::vector<uint32_t>& binary_under_reduction,
     const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
   uint32_t counter_for_shrinker_interestingness_function =
-      num_existing_shrink_attempts_ + num_reduction_attempts_;
-  num_reduction_attempts_++;
+      num_existing_shrink_attempts_ +
+      num_reducer_interestingness_function_invocations_;
+  num_reducer_interestingness_function_invocations_++;
 
   // The reduced version of the added function must be limited to accessing
   // global variables appearing in |irrelevant_pointee_global_variables|.  This
diff --git a/source/fuzz/added_function_reducer.h b/source/fuzz/added_function_reducer.h
index 9dcc632..3efd268 100644
--- a/source/fuzz/added_function_reducer.h
+++ b/source/fuzz/added_function_reducer.h
@@ -181,9 +181,10 @@
   // AddedFunctionReducer instance.
   const uint32_t num_existing_shrink_attempts_;
 
-  // Tracks the number of attempts that spirv-reduce has made in reducing the
-  // added function.
-  uint32_t num_reduction_attempts_;
+  // Tracks the number of attempts that spirv-reduce has invoked its
+  // interestingness function, which it does once at the start of reduction,
+  // and then once more each time it makes a reduction step.
+  uint32_t num_reducer_interestingness_function_invocations_;
 };
 
 }  // namespace fuzz
diff --git a/test/fuzz/shrinker_test.cpp b/test/fuzz/shrinker_test.cpp
index 1ea2c16..42cd182 100644
--- a/test/fuzz/shrinker_test.cpp
+++ b/test/fuzz/shrinker_test.cpp
@@ -144,7 +144,7 @@
   // compilers are kept happy.  See:
   // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html
   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = kConsoleMessageConsumer;
+  const auto consumer = fuzzerutil::kSilentMessageConsumer;
 
   SpirvTools tools(env);
   std::vector<uint32_t> reference_binary;
@@ -266,6 +266,118 @@
   }
 }
 
+TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) {
+  const std::string kReferenceModule = 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 = OpTypePointer Private %6
+          %8 = OpVariable %7 Private
+          %9 = OpConstant %6 2
+         %10 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %8 %9
+         %12 = OpLoad %6 %8
+               OpStore %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string kDonorModule = 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
+         %48 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %52 = OpCopyObject %6 %48
+         %53 = OpCopyObject %6 %52
+         %54 = OpCopyObject %6 %53
+         %55 = OpCopyObject %6 %54
+         %56 = OpCopyObject %6 %55
+         %57 = OpCopyObject %6 %56
+         %58 = OpCopyObject %6 %48
+         %59 = OpCopyObject %6 %58
+         %60 = OpCopyObject %6 %59
+         %61 = OpCopyObject %6 %60
+         %62 = OpCopyObject %6 %61
+         %63 = OpCopyObject %6 %62
+         %64 = OpCopyObject %6 %48
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = fuzzerutil::kSilentMessageConsumer;
+
+  SpirvTools tools(env);
+  std::vector<uint32_t> reference_binary;
+  ASSERT_TRUE(
+      tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
+
+  spvtools::ValidatorOptions validator_options;
+
+  const auto variant_ir_context =
+      BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      variant_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  const auto donor_ir_context =
+      BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  PseudoRandomGenerator random_generator(0);
+  FuzzerContext fuzzer_context(&random_generator, 100);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
+
+  protobufs::TransformationSequence transformations;
+  FuzzerPassDonateModules pass(variant_ir_context.get(),
+                               &transformation_context, &fuzzer_context,
+                               &transformations, {});
+  pass.DonateSingleModule(donor_ir_context.get(), true);
+
+  protobufs::FactSequence no_facts;
+
+  Shrinker::InterestingnessFunction interestingness_function =
+      [consumer, env](const std::vector<uint32_t>& binary,
+                      uint32_t /*unused*/) -> bool {
+    auto temp_ir_context =
+        BuildModule(env, consumer, binary.data(), binary.size());
+    uint32_t copy_object_count = 0;
+    temp_ir_context->module()->ForEachInst(
+        [&copy_object_count](opt::Instruction* inst) {
+          if (inst->opcode() == SpvOpCopyObject) {
+            copy_object_count++;
+          }
+
+        });
+    return copy_object_count >= 8;
+  };
+
+  auto shrinker_result =
+      Shrinker(env, consumer, reference_binary, no_facts, transformations,
+               interestingness_function, 30, true, validator_options)
+          .Run();
+  ASSERT_EQ(Shrinker::ShrinkerResultStatus::kStepLimitReached,
+            shrinker_result.status);
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools