Skip redundant second replay for single-input non-persistent crash batches. During crash replay (`ReplayCrash`), Centipede executes a batch containing a single crashing input (`input_vec.size() == 1`). Previously, when that input crashed, `Centipede::ReportCrash` re-executed the binary a second time to isolate the reproducer from the batch, and skipped writing crash metadata (`<hash>.desc` and `<hash>.sig`) if the second execution timed out (for example, due to slow stack trace printing in the signal handler), failed to reproduce a flaky crash, or produced a different signature. When crash metadata files were not written, `GetCrashesFromWorkdir` returned zero crashes (`Total crashes: 0`) and `CrashSummary` omitted the reproduced crash. Disable `persistent_mode` in `ReplayCrash` so the single crashing input runs in an isolated process, and update `Centipede::ExecuteAndReportCrash` and `Centipede::ReportCrash` to save the reproducer and crash metadata directly from `batch_result` without re-executing the binary when `input_vec.size() == 1` and `persistent_mode` is disabled. PiperOrigin-RevId: 986722767
diff --git a/centipede/centipede.cc b/centipede/centipede.cc index c81217d..7566883 100644 --- a/centipede/centipede.cc +++ b/centipede/centipede.cc
@@ -111,6 +111,37 @@ return mutants; } +void SaveReproducer(const WorkDir& wd, std::string_view log_prefix, + size_t input_idx, ByteSpan reproducer, + const BatchResult& result) { + auto hash = Hash(reproducer); + auto crash_dir = wd.CrashReproducerDirPaths().MyShard(); + FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir)); + std::string input_file_path = std::filesystem::path(crash_dir) / hash; + auto crash_metadata_dir = wd.CrashMetadataDirPaths().MyShard(); + FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir)); + std::string crash_metadata_path_prefix = + std::filesystem::path(crash_metadata_dir) / hash; + FUZZTEST_LOG(INFO) + << log_prefix << "Detected crash-reproducing input:" + << "\nInput index : " << input_idx + << "\nInput bytes : " << AsPrintableString(reproducer, /*max_len=*/32) + << "\nExit code : " << result.exit_code() + << "\nFailure : " << result.failure_description() + << "\nSignature : " + << AsPrintableString(AsByteSpan(result.failure_signature()), + /*max_len=*/32) + << "\nSaving input to: " << input_file_path << "\nSaving crash" // + << "\nmetadata to : " << crash_metadata_path_prefix << ".*"; + FUZZTEST_CHECK_OK(RemoteFileSetContents(input_file_path, reproducer)); + FUZZTEST_CHECK_OK( + RemoteFileSetContents(absl::StrCat(crash_metadata_path_prefix, ".desc"), + result.failure_description())); + FUZZTEST_CHECK_OK( + RemoteFileSetContents(absl::StrCat(crash_metadata_path_prefix, ".sig"), + result.failure_signature())); +} + } // namespace Centipede::Centipede(const Environment& env, CentipedeCallbacks& user_callbacks, @@ -396,7 +427,11 @@ << batch_result.failure_description(); return true; } - if (stop_condition_.ShouldStop()) { + const bool can_report_without_reexecution = + input_vec.size() == 1 && !env_.persistent_mode && + batch_result.IsInputFailure() && !stop_condition_.StopRequested() && + batch_result.failure_description() != kExecutionFailurePerBatchTimeout; + if (stop_condition_.ShouldStop() && !can_report_without_reexecution) { FUZZTEST_LOG_FIRST_N(WARNING, 1) << "Crash found but the stop condition is met - not reporting further " "possibly related crashes."; @@ -1059,6 +1094,17 @@ return; } + if (input_vec.size() == 1 && !env_.persistent_mode && + batch_result.IsInputFailure()) { + FUZZTEST_LOG(INFO) + << log_prefix + << "Single-input batch in non-persistent mode; skipping re-execution " + "and saving the reproducer directly."; + SaveReproducer(wd_, log_prefix, /*input_idx=*/0, input_vec[0], + batch_result); + return; + } + // Determine the optimal order of the inputs to try to maximize the chances of // finding the reproducer fast. std::vector<size_t> input_idxs_to_try; @@ -1095,34 +1141,8 @@ one_input_batch_result.failure_signature() == batch_result.failure_signature() && !stop_condition_.ShouldStop()) { - auto hash = Hash(one_input); - auto crash_dir = wd_.CrashReproducerDirPaths().MyShard(); - FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir)); - std::string input_file_path = std::filesystem::path(crash_dir) / hash; - auto crash_metadata_dir = wd_.CrashMetadataDirPaths().MyShard(); - FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir)); - std::string crash_metadata_path_prefix = - std::filesystem::path(crash_metadata_dir) / hash; - FUZZTEST_LOG(INFO) - << log_prefix << "Detected crash-reproducing input:" - << "\nInput index : " << input_idx << "\nInput bytes : " - << AsPrintableString(one_input, /*max_len=*/32) - << "\nExit code : " << one_input_batch_result.exit_code() - << "\nFailure : " - << one_input_batch_result.failure_description() - << "\nSignature : " - << AsPrintableString( - AsByteSpan(one_input_batch_result.failure_signature()), - /*max_len=*/32) - << "\nSaving input to: " << input_file_path << "\nSaving crash" // - << "\nmetadata to : " << crash_metadata_path_prefix << ".*"; - FUZZTEST_CHECK_OK(RemoteFileSetContents(input_file_path, one_input)); - FUZZTEST_CHECK_OK(RemoteFileSetContents( - absl::StrCat(crash_metadata_path_prefix, ".desc"), - one_input_batch_result.failure_description())); - FUZZTEST_CHECK_OK(RemoteFileSetContents( - absl::StrCat(crash_metadata_path_prefix, ".sig"), - one_input_batch_result.failure_signature())); + SaveReproducer(wd_, log_prefix, input_idx, one_input, + one_input_batch_result); return; } }
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc index 44ba0c9..e6ba1eb 100644 --- a/centipede/centipede_interface.cc +++ b/centipede/centipede_interface.cc
@@ -670,6 +670,7 @@ crash_corpus_config, env.binary_name, env.binary_hash)); Environment run_crash_env = env; run_crash_env.load_shards_only = true; + run_crash_env.persistent_mode = false; Fuzz(run_crash_env, {}, "", callbacks_factory, stop_condition); if (env.report_crash_summary) { CrashSummary crash_summary{env.fuzztest_binary_identifier, env.test_name};
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 1eae7ea..ac1ade9 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -1046,6 +1046,39 @@ EXPECT_TRUE(std::filesystem::exists(crasher_path)) << crasher_path; } +TEST(Centipede, SingleInputNonPersistentSkipsTriageReplay) { + TempDir temp_dir{test_info_->name()}; + Environment env; + env.workdir = temp_dir.path(); + env.num_runs = 5; + env.batch_size = 1; + env.persistent_mode = false; + env.require_pc_table = false; + env.exit_on_crash = true; + + // Fail on first pass for input 2, and never fail in triage + // (`fail_on_triage_attempt = 99`). + FlakyCrashingInputMock mock(env, /*crashing_input_idx=*/2, + /*fail_on_triage_attempt=*/99); + NonOwningCallbacksFactory factory(mock); + CentipedeMain(env, factory); + + // Triage re-execution must be skipped (`triage_attempts() == 0`) and the + // reproducer + metadata files must still be written. + EXPECT_EQ(mock.triage_attempts(), 0); + const auto crashing_input_hash = Hash(mock.crashing_input()); + const auto crasher_path = + std::filesystem::path{WorkDir{env}.CrashReproducerDirPaths().MyShard()} / + crashing_input_hash; + EXPECT_TRUE(std::filesystem::exists(crasher_path)) << crasher_path; + const auto metadata_dir = + std::filesystem::path{WorkDir{env}.CrashMetadataDirPaths().MyShard()}; + EXPECT_TRUE( + std::filesystem::exists(metadata_dir / (crashing_input_hash + ".desc"))); + EXPECT_TRUE( + std::filesystem::exists(metadata_dir / (crashing_input_hash + ".sig"))); +} + TEST_F(CentipedeWithTemporaryLocalDir, GetsSeedInputs) { Environment env; env.binary =