Keep `weighted_distribution_` in sync with `records_` when pruning all-zero weights. When all corpus inputs have weight zero, `WeightedDistribution::RemoveRandomWeightedSubset` removes every entry from `weighted_distribution_` before returning `subset_to_remove`. `Corpus::Prune` then erases one index from `subset_to_remove` so `records_` retains one random input, leaving `weighted_distribution_` empty and causing check failures on subsequent corpus operations. Restore one zero weight in `weighted_distribution_` when keeping that fallback input, and add a unit test covering `Corpus::Prune` with all-zero weights. PiperOrigin-RevId: 1002585725
diff --git a/centipede/corpus.cc b/centipede/corpus.cc index 5c194b5..0b216f4 100644 --- a/centipede/corpus.cc +++ b/centipede/corpus.cc
@@ -213,6 +213,8 @@ subset_to_remove.erase( subset_to_remove.begin() + absl::Uniform<size_t>(rng, 0, subset_to_remove.size())); + // Restore one weight since RemoveRandomWeightedSubset removed all of them. + weighted_distribution_.AddWeight(0); } RemoveSubset(subset_to_remove, records_);
diff --git a/centipede/corpus_test.cc b/centipede/corpus_test.cc index 46e4d4e..5187a60 100644 --- a/centipede/corpus_test.cc +++ b/centipede/corpus_test.cc
@@ -338,6 +338,23 @@ corpus.Prune(fs, coverage_frontier, max_corpus_size, rng); } +TEST(Corpus, PruneCorpusWithAllZeroWeights) { + Corpus corpus; + Rng rng(0); + + // Newly added inputs have weight 0 before UpdateWeights is called. + corpus.Add({1}, {}, {}, {}, FeatureSet(1, {}), + CoverageFrontier(BinaryInfo{})); + corpus.Add({2}, {}, {}, {}, FeatureSet(1, {}), + CoverageFrontier(BinaryInfo{})); + + EXPECT_EQ(corpus.Prune(FeatureSet(1, {}), CoverageFrontier(BinaryInfo{}), + /*max_corpus_size=*/1000, rng), + 1); + EXPECT_EQ(corpus.NumActive(), 1); + EXPECT_EQ(corpus.WeightedRandom(rng), 0); +} + // Regression test for a crash in Corpus::Prune(). TEST(Corpus, PruneRegressionTest1) { PCTable pc_table(100);