Minimize new crash inputs updating corpus database. This is enabled and and capped by the new flag `minimize_corpus_database_new_crashes_duration`. PiperOrigin-RevId: 828029112
diff --git a/centipede/BUILD b/centipede/BUILD index 5e1690e..0c63299 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", ], @@ -904,6 +905,7 @@ ":centipede_callbacks", ":crash_summary", ":environment", + ":minimize_crash", ":runner_result", ":stop", ":workdir",
diff --git a/centipede/centipede_flags.inc b/centipede/centipede_flags.inc index 2a21f1e..d459b16 100644 --- a/centipede/centipede_flags.inc +++ b/centipede/centipede_flags.inc
@@ -532,3 +532,7 @@ "or deduplication before considering it non-reproducible.") CENTIPEDE_FLAG(int, replay_coverage_attempts, 1, "Number of attempts to replay coverage/corpus inputs.") +CENTIPEDE_FLAG(absl::Duration, fuzztest_minimize_new_crashes_duration, + absl::ZeroDuration(), + "The time limit for minimizing new crashes when updating the " + "FuzzTest corpus database.")
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/crash_deduplication.cc b/centipede/crash_deduplication.cc index dbc44ba..0e11b2d 100644 --- a/centipede/crash_deduplication.cc +++ b/centipede/crash_deduplication.cc
@@ -38,6 +38,7 @@ #include "./centipede/centipede_callbacks.h" #include "./centipede/crash_summary.h" #include "./centipede/environment.h" +#include "./centipede/minimize_crash.h" #include "./centipede/runner_result.h" #include "./centipede/stop.h" #include "./centipede/workdir.h" @@ -308,17 +309,14 @@ } absl::flat_hash_map<std::string, CrashDetails> FindNewCrashes( + const Environment& env, CentipedeCallbacksFactory& callbacks_factory, const absl::flat_hash_map<std::string, CrashDetails>& new_crashes_by_signature, absl::Span<const IncubatingCrash> incubating_crashes, - absl::Span<const ExistingCrash> existing_crashes) { + absl::Span<const ExistingCrash> existing_crashes, + StopCondition& stop_condition) { absl::flat_hash_map<std::string, CrashDetails> new_crashes; - // Add new crashes from the current run. - for (const auto& [signature, details] : new_crashes_by_signature) { - new_crashes[signature] = details; - } - // Add reproducing incubating crashes. for (const auto& incubating : incubating_crashes) { if (!incubating.new_signature.empty()) { @@ -333,6 +331,63 @@ } } + std::vector<std::string> crash_signatures_to_minimize; + // Add new crashes from the current run. + for (const auto& [signature, details] : new_crashes_by_signature) { + auto [it, inserted] = new_crashes.try_emplace(signature, details); + if (!inserted) continue; + crash_signatures_to_minimize.push_back(signature); + } + + if (env.fuzztest_minimize_new_crashes_duration <= absl::ZeroDuration()) { + // Minimization is disabled. + return new_crashes; + } + + const absl::Time previous_stop_time = stop_condition.GetStopTime(); + const absl::Time minimization_deadline = + std::min(previous_stop_time, + absl::Now() + env.fuzztest_minimize_new_crashes_duration); + for (size_t i = 0; i < crash_signatures_to_minimize.size(); ++i) { + if (stop_condition.ShouldStop()) { + break; + } + const auto& signature = crash_signatures_to_minimize[i]; + auto& details = new_crashes[signature]; + ByteArray input_bytes; + if (const auto status = + RemoteFileGetContents(details.input_path, input_bytes); + !status.ok()) { + FUZZTEST_LOG(ERROR) + << "Failed to read new crash to minimize - skipping minimization"; + continue; + } + const auto now = absl::Now(); + // Let the remaining new crashes evenly split what's remaining for + // `minimization_deadline`. + stop_condition.SetStopTime(now + + (minimization_deadline - now) / + (crash_signatures_to_minimize.size() - i)); + const auto result = MinimizeCrash(input_bytes, env, callbacks_factory, + signature, stop_condition); + stop_condition.SetStopTime(previous_stop_time); + if (!result.has_value()) { + FUZZTEST_LOG(ERROR) << "Failed to find a smaller crash."; + continue; + } + const auto minimized_input_path = absl::StrCat(details.input_path, ".min"); + if (const auto status = + RemoteFileSetContents(minimized_input_path, result->input); + !status.ok()) { + FUZZTEST_LOG(ERROR) + << "Failed to save the minimized crash - skipping minimization"; + continue; + } + details.input_signature = Hash(result->input); + details.description = std::move(result->description); + details.input_path = minimized_input_path; + } + return new_crashes; } @@ -742,8 +797,9 @@ stop_condition, absl::MakeSpan(incubating_to_replay))); - absl::flat_hash_map<std::string, CrashDetails> new_crashes = FindNewCrashes( - new_crashes_by_signature, incubating_crashes, existing_crashes); + absl::flat_hash_map<std::string, CrashDetails> new_crashes = + FindNewCrashes(env, callbacks_factory, new_crashes_by_signature, + incubating_crashes, existing_crashes, stop_condition); std::vector<ExistingCrashAction> actions = ComputeExistingCrashActions(existing_crashes, new_crashes);
diff --git a/centipede/environment.cc b/centipede/environment.cc index f17f751..bef0b48 100644 --- a/centipede/environment.cc +++ b/centipede/environment.cc
@@ -253,6 +253,8 @@ if (replay_crash_attempts == Default().replay_crash_attempts) { replay_crash_attempts = config.replay_crash_attempts; } + fuzztest_minimize_new_crashes_duration = + config.minimize_corpus_database_new_crashes_duration; // Allow more crashes to be reported when running with FuzzTest. This allows // more unique crashes to collected after deduplication. But we don't want to
diff --git a/centipede/environment_test.cc b/centipede/environment_test.cc index 8589e9e..ebf32ea 100644 --- a/centipede/environment_test.cc +++ b/centipede/environment_test.cc
@@ -198,6 +198,7 @@ config.replay_coverage_inputs = true; config.time_limit = absl::Seconds(10); config.time_budget_type = TimeBudgetType::kPerTest; + config.minimize_corpus_database_new_crashes_duration = absl::Seconds(123); env.UpdateWithTargetConfig(config); @@ -209,6 +210,7 @@ EXPECT_EQ(env.fuzztest_execution_id, "exec"); EXPECT_TRUE(env.fuzztest_replay_coverage_inputs); EXPECT_EQ(env.fuzztest_time_limit_per_test, absl::Seconds(10)); + EXPECT_EQ(env.fuzztest_minimize_new_crashes_duration, absl::Seconds(123)); } TEST(Environment, UpdatesReplayOnlyConfiguration) {
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
diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc index 3f80a9c..b8af0bd 100644 --- a/e2e_tests/corpus_database_test.cc +++ b/e2e_tests/corpus_database_test.cc
@@ -152,9 +152,11 @@ UpdateCorpusDatabaseTest::run_map_{}; TEST_P(UpdateCorpusDatabaseTest, RunsFuzzTests) { - EXPECT_THAT_LOG(GetUpdateCorpusDatabaseStdErr(), - AllOf(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays"), - HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow"))); + EXPECT_THAT_LOG( + GetUpdateCorpusDatabaseStdErr(), + AllOf(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays"), + HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow"), + HasSubstr("Fuzzing FuzzTest.FailsWithMinimizableCrashingSeed"))); } TEST_P(UpdateCorpusDatabaseTest, UsesMultipleShardsForFuzzingAndDistillation) { @@ -170,7 +172,8 @@ GetUpdateCorpusDatabaseStdErr(), AllOf(ContainsRegex(R"re(Failure\s*: GoogleTest assertion failure)re"), ContainsRegex(R"re(Failure\s*: heap-buffer-overflow)re"), - ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re"))); + ContainsRegex(R"re(Failure\s*: stack-limit-exceeded)re"), + ContainsRegex(R"re(Failure\s*: SIGABRT)re"))); } TEST_P(UpdateCorpusDatabaseTest, DeduplicatesCrashes) { @@ -190,6 +193,11 @@ R"re((?s)=== Summary of detected crashes === .*?Fuzz test : FuzzTest.FailsWithStackOverflow .*?Total crashes: 1 +.*?=== End of summary of detected crashes ===)re"), + ContainsRegex( + R"re((?s)=== Summary of detected crashes === +.*?Fuzz test : FuzzTest.FailsWithMinimizableCrashingSeed +.*?Total crashes: 1 .*?=== End of summary of detected crashes ===)re"))); } @@ -209,6 +217,10 @@ HasSubstr("Fuzzing FuzzTest.FailsWithStackOverflow for 10s")); EXPECT_THAT_LOG(std_err, Not(HasSubstr("Fuzzing FuzzTest.FailsInTwoWays for 10s"))); + EXPECT_THAT_LOG( + std_err, + Not(HasSubstr( + "Fuzzing FuzzTest.FailsWithMinimizableCrashingSeed for 10s"))); } TEST_P(UpdateCorpusDatabaseTest, StartsNewFuzzTestRunsWithoutExecutionIds) { @@ -378,6 +390,10 @@ ContainsRegex( R"re((?s)=== Summary of detected crashes === .*?Fuzz test : FuzzTest.FailsWithStackOverflow +.*?=== End of summary of detected crashes ===)re"), + ContainsRegex( + R"re((?s)=== Summary of detected crashes === +.*?Fuzz test : FuzzTest.FailsWithMinimizableCrashingSeed .*?=== End of summary of detected crashes ===)re"))); } @@ -491,6 +507,43 @@ EXPECT_FALSE(fuzzing_time_file.ok()) << *fuzzing_time_file; } +TEST_P(UpdateCorpusDatabaseTest, MinimizesNewCrashesWithFlagSet) { + TempDir corpus_database; + + // 1st run that minimizes the new crash. + RunOptions fst_run_options; + fst_run_options.fuzztest_flags = { + {"corpus_database", corpus_database.path()}, + {"fuzz", "FailsWithMinimizableCrashingSeed"}, + {"fuzz_for", "5s"}, + {"minimize_corpus_database_new_crashes_duration", "5s"}, + {"execution_id", "some_execution_id_1"}, + }; + fst_run_options.timeout = absl::Seconds(20); + auto [fst_status_unused, fst_std_out_unused, fst_std_err] = + RunBinaryMaybeWithCentipede(GetCorpusDatabaseTestingBinaryPath(), + fst_run_options); + + EXPECT_THAT_LOG(fst_std_err, + HasSubstr("Starting the crash minimization loop")); + + // 2nd run that should not trigger minimization due to no new crashes. + RunOptions snd_run_options; + snd_run_options.fuzztest_flags = { + {"corpus_database", corpus_database.path()}, + {"fuzz", "FailsWithMinimizableCrashingSeed"}, + {"fuzz_for", "5s"}, + {"minimize_corpus_database_new_crashes_duration", "5s"}, + {"execution_id", "some_execution_id_2"}, + }; + snd_run_options.timeout = absl::Seconds(20); + auto [snd_status_unused, snd_std_out_unused, snd_std_err] = + RunBinaryMaybeWithCentipede(GetCorpusDatabaseTestingBinaryPath(), + snd_run_options); + EXPECT_THAT_LOG(snd_std_err, + Not(HasSubstr("Starting the crash minimization loop"))); +} + INSTANTIATE_TEST_SUITE_P( UpdateCorpusDatabaseTestWithExecutionModel, UpdateCorpusDatabaseTest, testing::ValuesIn({
diff --git a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc index 8a4bdcc..4cd5020 100644 --- a/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_corpus_database_testing.cc
@@ -13,6 +13,7 @@ // limitations under the License. #include <cstddef> +#include <cstdlib> #include "gtest/gtest.h" #include "./fuzztest/fuzztest.h" @@ -56,4 +57,14 @@ FUZZ_TEST(FuzzTest, FailsWithStackOverflow) .WithDomains(fuzztest::Just(kDepthToReachStackOverflow)); +// The failing seed can be minimized to "fuz". +void FailsWithMinimizableCrashingSeed(const std::string& s) { + if (!s.empty() && s[0] == 'f' && s[s.size() >> 1] == 'u' && + s[s.size() - 1] == 'z') { + std::abort(); + } +} +FUZZ_TEST(FuzzTest, FailsWithMinimizableCrashingSeed) + .WithSeeds({"f.....u.....z"}); + } // namespace
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc index 74c1bc5..9d6c27b 100644 --- a/fuzztest/init_fuzztest.cc +++ b/fuzztest/init_fuzztest.cc
@@ -135,6 +135,12 @@ "value of --" FUZZTEST_FLAG_PREFIX "time_budget_type."); FUZZTEST_DEFINE_FLAG( + absl::Duration, minimize_corpus_database_new_crashes_duration, + absl::ZeroDuration(), + "When set to non-zero duration, specifies the duration to minimize newly " + "found crashes when updating the corpus database."); + +FUZZTEST_DEFINE_FLAG( fuzztest::internal::TimeBudgetType, time_budget_type, fuzztest::internal::TimeBudgetType::kPerTest, "Determines whether the time budget specified by --" FUZZTEST_FLAG_PREFIX @@ -424,6 +430,8 @@ absl::GetFlag(FUZZTEST_FLAG(time_limit_per_input)), time_limit, time_budget_type, + absl::GetFlag( + FUZZTEST_FLAG(minimize_corpus_database_new_crashes_duration)), num_jobs, absl::GetFlag(FUZZTEST_FLAG(internal_centipede_command)), absl::GetFlag(FUZZTEST_FLAG(internal_crashing_input_to_reproduce)),
diff --git a/fuzztest/internal/configuration.cc b/fuzztest/internal/configuration.cc index c2de6ca..6b2e657 100644 --- a/fuzztest/internal/configuration.cc +++ b/fuzztest/internal/configuration.cc
@@ -203,6 +203,8 @@ absl::FormatDuration(time_limit_per_input); std::string time_limit_str = absl::FormatDuration(time_limit); std::string time_budget_type_str = AbslUnparseFlag(time_budget_type); + std::string minimize_corpus_database_new_crashes_duration_str = + absl::FormatDuration(minimize_corpus_database_new_crashes_duration); std::string out; out.resize( SpaceFor(corpus_database) + SpaceFor(stats_root) + @@ -216,6 +218,7 @@ SpaceFor(subprocess_cleanup_timeout_str) + SpaceFor(stack_limit) + SpaceFor(rss_limit) + SpaceFor(time_limit_per_input_str) + SpaceFor(time_limit_str) + SpaceFor(time_budget_type_str) + + SpaceFor(minimize_corpus_database_new_crashes_duration_str) + SpaceFor(jobs) + SpaceFor(centipede_command) + SpaceFor(crashing_input_to_reproduce) + SpaceFor(replay_crash_attempts) + SpaceFor(reproduction_command_template)); @@ -240,6 +243,8 @@ offset = WriteString(out, offset, time_limit_per_input_str); offset = WriteString(out, offset, time_limit_str); offset = WriteString(out, offset, time_budget_type_str); + offset = WriteString(out, offset, + minimize_corpus_database_new_crashes_duration_str); offset = WriteIntegral(out, offset, jobs); offset = WriteOptionalString(out, offset, centipede_command); offset = WriteOptionalString(out, offset, crashing_input_to_reproduce); @@ -274,6 +279,8 @@ ASSIGN_OR_RETURN(time_limit_per_input_str, ConsumeString(serialized)); ASSIGN_OR_RETURN(time_limit_str, ConsumeString(serialized)); ASSIGN_OR_RETURN(time_budget_type_str, ConsumeString(serialized)); + ASSIGN_OR_RETURN(minimize_corpus_database_new_crashes_duration_str, + ConsumeString(serialized)); ASSIGN_OR_RETURN(jobs, Consume<size_t>(serialized)); ASSIGN_OR_RETURN(centipede_command, ConsumeOptionalString(serialized)); ASSIGN_OR_RETURN(crashing_input_to_reproduce, @@ -292,6 +299,9 @@ ASSIGN_OR_RETURN(time_limit, ParseDuration(*time_limit_str)); ASSIGN_OR_RETURN(time_budget_type, ParseTimeBudgetType(*time_budget_type_str)); + ASSIGN_OR_RETURN( + minimize_corpus_database_new_crashes_duration, + ParseDuration(*minimize_corpus_database_new_crashes_duration_str)); return Configuration{*std::move(corpus_database), *std::move(stats_root), *std::move(workdir_root), @@ -312,6 +322,7 @@ *time_limit_per_input, *time_limit, *time_budget_type, + *minimize_corpus_database_new_crashes_duration, *jobs, *std::move(centipede_command), *std::move(crashing_input_to_reproduce),
diff --git a/fuzztest/internal/configuration.h b/fuzztest/internal/configuration.h index 0aceee9..b40577d 100644 --- a/fuzztest/internal/configuration.h +++ b/fuzztest/internal/configuration.h
@@ -99,6 +99,8 @@ absl::Duration time_limit = absl::InfiniteDuration(); // Whether the time limit is for each test or for all tests in the binary. TimeBudgetType time_budget_type = TimeBudgetType::kPerTest; + // The duration for minimizing new crashes when updating corpus database. + absl::Duration minimize_corpus_database_new_crashes_duration; // The number of fuzzing jobs to run in parallel. Zero indicates that the // number of jobs is unspecified by the test binary. size_t jobs = 0;
diff --git a/fuzztest/internal/configuration_test.cc b/fuzztest/internal/configuration_test.cc index a2ccf21..16cf440 100644 --- a/fuzztest/internal/configuration_test.cc +++ b/fuzztest/internal/configuration_test.cc
@@ -36,6 +36,8 @@ config.time_limit_per_input == other->time_limit_per_input && config.time_limit == other->time_limit && config.time_budget_type == other->time_budget_type && + config.minimize_corpus_database_new_crashes_duration == + other->minimize_corpus_database_new_crashes_duration && config.jobs == other->jobs && config.centipede_command == other->centipede_command && config.crashing_input_to_reproduce == @@ -47,31 +49,33 @@ TEST(ConfigurationTest, DeserializeYieldsSerializedConfigurationWithoutOptionalValues) { - Configuration configuration{"corpus_database", - "stats_root", - "workdir_root", - "binary_identifier", - /*fuzz_tests=*/{}, - /*fuzz_tests_in_current_shard=*/{}, - /*continue_after_crash=*/true, - /*reproduce_findings_as_separate_tests=*/true, - /*replay_coverage_inputs=*/true, - /*only_replay=*/true, - /*update_corpus_database=*/true, - /*replay_in_single_process=*/true, - "execution_id", - /*print_subprocess_log=*/true, - /*subprocess_cleanup_time=*/absl::Seconds(42), - /*stack_limit=*/100, - /*rss_limit=*/200, - /*time_limit_per_input=*/absl::Seconds(42), - /*time_limit=*/absl::Minutes(42), - /*time_budget_type=*/TimeBudgetType::kPerTest, - /*jobs=*/1, - /*centipede_command=*/std::nullopt, - /*crashing_input_to_reproduce=*/std::nullopt, - /*replay_crash_attempts=*/5, - /*reproduction_command_template=*/std::nullopt}; + Configuration configuration{ + "corpus_database", + "stats_root", + "workdir_root", + "binary_identifier", + /*fuzz_tests=*/{}, + /*fuzz_tests_in_current_shard=*/{}, + /*continue_after_crash=*/true, + /*reproduce_findings_as_separate_tests=*/true, + /*replay_coverage_inputs=*/true, + /*only_replay=*/true, + /*update_corpus_database=*/true, + /*replay_in_single_process=*/true, + "execution_id", + /*print_subprocess_log=*/true, + /*subprocess_cleanup_time=*/absl::Seconds(42), + /*stack_limit=*/100, + /*rss_limit=*/200, + /*time_limit_per_input=*/absl::Seconds(42), + /*time_limit=*/absl::Minutes(42), + /*time_budget_type=*/TimeBudgetType::kPerTest, + /*minimize_corpus_database_new_crashes_duration=*/absl::Seconds(123), + /*jobs=*/1, + /*centipede_command=*/std::nullopt, + /*crashing_input_to_reproduce=*/std::nullopt, + /*replay_crash_attempts=*/5, + /*reproduction_command_template=*/std::nullopt}; EXPECT_THAT(Configuration::Deserialize(configuration.Serialize()), IsOkAndEquals(configuration)); @@ -79,31 +83,33 @@ TEST(ConfigurationTest, DeserializeYieldsSerializedConfigurationWithOptionalValues) { - Configuration configuration{"corpus_database", - "stats_root", - "workdir_root", - "binary_identifier", - {"FuzzTest1", "FuzzTest2"}, - {"FuzzTest1"}, - /*continue_after_crash=*/true, - /*reproduce_findings_as_separate_tests=*/true, - /*replay_coverage_inputs=*/true, - /*only_replay=*/true, - /*update_corpus_database=*/true, - /*replay_in_single_process=*/true, - "execution_id", - /*print_subprocess_log=*/true, - /*subprocess_cleanup_time=*/absl::Seconds(42), - /*stack_limit=*/100, - /*rss_limit=*/200, - /*time_limit_per_input=*/absl::Seconds(42), - /*time_limit=*/absl::Minutes(42), - /*time_budget_type=*/TimeBudgetType::kPerTest, - /*jobs=*/1, - "centipede_command", - "crashing_input_to_reproduce", - /*replay_crash_attempts=*/20, - "reproduction_command_template"}; + Configuration configuration{ + "corpus_database", + "stats_root", + "workdir_root", + "binary_identifier", + {"FuzzTest1", "FuzzTest2"}, + {"FuzzTest1"}, + /*continue_after_crash=*/true, + /*reproduce_findings_as_separate_tests=*/true, + /*replay_coverage_inputs=*/true, + /*only_replay=*/true, + /*update_corpus_database=*/true, + /*replay_in_single_process=*/true, + "execution_id", + /*print_subprocess_log=*/true, + /*subprocess_cleanup_time=*/absl::Seconds(42), + /*stack_limit=*/100, + /*rss_limit=*/200, + /*time_limit_per_input=*/absl::Seconds(42), + /*time_limit=*/absl::Minutes(42), + /*time_budget_type=*/TimeBudgetType::kPerTest, + /*minimize_corpus_database_new_crashes_duration=*/absl::Seconds(123), + /*jobs=*/1, + "centipede_command", + "crashing_input_to_reproduce", + /*replay_crash_attempts=*/20, + "reproduction_command_template"}; EXPECT_THAT(Configuration::Deserialize(configuration.Serialize()), IsOkAndEquals(configuration));