Make MinimizeCrash use replay_crash_attempts. PiperOrigin-RevId: 998796541
diff --git a/centipede/BUILD b/centipede/BUILD index 5e1690e..e5675e4 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -384,6 +384,7 @@ "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/container:flat_hash_set", "@abseil-cpp//absl/synchronization", + "@abseil-cpp//absl/types:span", "@com_google_fuzztest//common:defs", "@com_google_fuzztest//common:logging", ],
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc index fea652e..ee22718 100644 --- a/centipede/centipede_interface.cc +++ b/centipede/centipede_interface.cc
@@ -123,7 +123,9 @@ ScopedCentipedeCallbacks scoped_callbacks(callbacks_factory, env, stop_condition); CreateLocalDirRemovedAtExit(TemporaryLocalDirPath()); - if (scoped_callbacks.callbacks()->Execute(env.binary, {crashy_input}, + const std::vector<ByteSpan> inputs( + std::max<size_t>(1, env.replay_crash_attempts), crashy_input); + if (scoped_callbacks.callbacks()->Execute(env.binary, inputs, batch_result)) { FUZZTEST_LOG(ERROR) << "The original crashy input did not crash; exiting"; stop_condition.RequestStop(EXIT_FAILURE,
diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc index 66ef9a5..d7feba5 100644 --- a/centipede/minimize_crash.cc +++ b/centipede/minimize_crash.cc
@@ -26,6 +26,7 @@ #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_set.h" #include "absl/synchronization/mutex.h" +#include "absl/types/span.h" #include "./centipede/centipede_callbacks.h" #include "./centipede/environment.h" #include "./centipede/mutation_data.h" @@ -89,6 +90,7 @@ auto callbacks = scoped_callback.callbacks(); BatchResult batch_result; + const size_t max_attempts = std::max<size_t>(1, env.replay_crash_attempts); size_t num_batches = env.num_runs / env.batch_size; for (size_t i = 0; i < num_batches; ++i) { if (stop_condition.ShouldStop()) break; @@ -127,22 +129,38 @@ std::sort(smaller_mutants.begin(), smaller_mutants.end(), [](const auto& a, const auto& b) { return a.size() < b.size(); }); - // Execute all mutants. If a new crasher is found, add it to `state`. - if (callbacks->Execute(env.binary, smaller_mutants, batch_result)) { - continue; + std::vector<ByteSpan> inputs_to_execute; + inputs_to_execute.reserve(smaller_mutants.size() * max_attempts); + for (const auto& m : smaller_mutants) { + inputs_to_execute.insert(inputs_to_execute.end(), max_attempts, m); } - if (batch_result.failure_signature() != crash_signature) { - continue; - } + // Execute all mutants in chunks of at most `env.batch_size`. If a new + // crasher is found, add it to `state`. + for (size_t offset = 0; offset < inputs_to_execute.size(); + offset += env.batch_size) { + if (stop_condition.ShouldStop()) break; + const size_t chunk_size = + std::min(inputs_to_execute.size() - offset, env.batch_size); + const absl::Span<const ByteSpan> chunk = + absl::MakeConstSpan(inputs_to_execute).subspan(offset, chunk_size); + if (callbacks->Execute(env.binary, chunk, batch_result)) { + continue; + } - size_t crash_inputs_idx = batch_result.num_outputs_read(); - FUZZTEST_CHECK_LT(crash_inputs_idx, smaller_mutants.size()); - const auto& new_crasher = smaller_mutants[crash_inputs_idx]; - FUZZTEST_LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": " - << AsPrintableString(new_crasher, /*max_len=*/40); - state.AddCrasher({new_crasher.begin(), new_crasher.end()}, - std::move(batch_result.failure_description())); + if (batch_result.failure_signature() != crash_signature) { + continue; + } + + size_t crash_inputs_idx = batch_result.num_outputs_read(); + FUZZTEST_CHECK_LT(crash_inputs_idx, chunk.size()); + const auto& new_crasher = chunk[crash_inputs_idx]; + FUZZTEST_LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": " + << AsPrintableString(new_crasher, /*max_len=*/40); + state.AddCrasher({new_crasher.begin(), new_crasher.end()}, + std::move(batch_result.failure_description())); + break; + } } }
diff --git a/centipede/minimize_crash_test.cc b/centipede/minimize_crash_test.cc index 5b27047..6920065 100644 --- a/centipede/minimize_crash_test.cc +++ b/centipede/minimize_crash_test.cc
@@ -14,6 +14,7 @@ #include "./centipede/minimize_crash.h" +#include <cstddef> #include <cstdlib> #include <string> #include <string_view> @@ -40,15 +41,27 @@ // A mock for CentipedeCallbacks. class MinimizerMock : public CentipedeCallbacks { public: - MinimizerMock(const Environment& env, StopCondition& stop_condition) - : CentipedeCallbacks(env, stop_condition) {} + MinimizerMock(const Environment& env, StopCondition& stop_condition, + size_t crash_on_attempt = 1) + : CentipedeCallbacks(env, stop_condition), + crash_on_attempt_(crash_on_attempt) {} - // Runs FuzzMe() on every input, imitates failure if FuzzMe() returns true. + // Runs FuzzMe() on every input, imitates failure if FuzzMe() returns true + // `crash_on_attempt_` consecutive times on the same input. bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs, BatchResult& batch_result) override { + EXPECT_LE(inputs.size(), env_.batch_size); batch_result.ClearAndResize(inputs.size()); - for (auto input : inputs) { - if (FuzzMe(input)) { + for (size_t i = 0; i < inputs.size(); ++i) { + const auto& input = inputs[i]; + if (input.data() == last_input_.data() && + input.size() == last_input_.size()) { + ++current_attempts_; + } else { + last_input_ = input; + current_attempts_ = 1; + } + if (FuzzMe(input) && current_attempts_ >= crash_on_attempt_) { batch_result.exit_code() = EXIT_FAILURE; // Set signature differently to test signature matching behavior. batch_result.failure_signature() = @@ -73,16 +86,26 @@ } return true; } + + size_t crash_on_attempt_ = 1; + ByteSpan last_input_; + size_t current_attempts_ = 0; }; // Factory that creates/destroys MinimizerMock. class MinimizerMockFactory : public CentipedeCallbacksFactory { public: + explicit MinimizerMockFactory(size_t crash_on_attempt = 1) + : crash_on_attempt_(crash_on_attempt) {} + CentipedeCallbacks* absl_nonnull create( const Environment& env, StopCondition& stop_condition) override { - return new MinimizerMock(env, stop_condition); + return new MinimizerMock(env, stop_condition, crash_on_attempt_); } void destroy(CentipedeCallbacks *cb) override { delete cb; } + + private: + size_t crash_on_attempt_ = 1; }; TEST(MinimizeTest, FailsWhenCrasherCannotBeMinimized) { @@ -154,5 +177,27 @@ EXPECT_THAT(minimized_crashers, UnorderedElementsAre(ByteArray{'z'})); } +TEST(MinimizeTest, MinimizesWithReplayCrashAttempts) { + Environment env; + env.num_runs = 100000; + MinimizerMockFactory factory(/*crash_on_attempt=*/3); + StopCondition stop_condition; + + ByteArray original_crasher = {'f', 'f', 'f', 'f', 'f', 'f', + 'z', 'z', 'z', 'z', 'z', 'z'}; + env.replay_crash_attempts = 2; + EXPECT_FALSE(MinimizeCrash(original_crasher, env, factory, "signature one", + stop_condition) + .has_value()); + + env.replay_crash_attempts = 3; + auto result = MinimizeCrash(original_crasher, env, factory, "signature one", + stop_condition); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->input, ByteArray{'f'}); + EXPECT_EQ(result->description, "description one"); + EXPECT_FALSE(stop_condition.StopRequested()); +} + } // namespace } // namespace fuzztest::internal