Track mutant origins. This is to enable input reduction (replacing corpus input with smaller mutants if the coverage matches) and corpus mutation stats for new scheduling methods. PiperOrigin-RevId: 877456645
diff --git a/centipede/byte_array_mutator.cc b/centipede/byte_array_mutator.cc index f117eea..fc2597d 100644 --- a/centipede/byte_array_mutator.cc +++ b/centipede/byte_array_mutator.cc
@@ -333,8 +333,8 @@ std::vector<Mutant> mutants; mutants.reserve(num_mutants); for (size_t i = 0; i < num_mutants; ++i) { - Mutant mutant; - mutant.data = inputs[rng_() % num_inputs].data; + const size_t origin = rng_() % num_inputs; + auto mutant = Mutant{inputs[origin].data, origin}; if (mutant.data.size() <= max_len_ && knobs_.GenerateBool(knob_mutate_or_crossover, rng_())) { // Do crossover only if the mutant is not over the max_len_.
diff --git a/centipede/centipede.cc b/centipede/centipede.cc index 345af69..f204cce 100644 --- a/centipede/centipede.cc +++ b/centipede/centipede.cc
@@ -100,6 +100,19 @@ namespace fuzztest::internal { +namespace { + +std::vector<MutantRef> InputsToMutantRefs(const std::vector<ByteSpan>& inputs) { + std::vector<MutantRef> mutants; + mutants.reserve(inputs.size()); + for (const auto input : inputs) { + mutants.push_back(MutantRef{input, Mutant::kOriginNone}); + } + return mutants; +} + +} // namespace + Centipede::Centipede(const Environment& env, CentipedeCallbacks& user_callbacks, const BinaryInfo& binary_info, CoverageLogger& coverage_logger, std::atomic<Stats>& stats) @@ -428,20 +441,24 @@ } bool Centipede::RunBatch( - absl::Span<const ByteSpan> input_vec, + absl::Span<const MutantRef> mutants, BlobFileWriter* absl_nullable corpus_file, BlobFileWriter* absl_nullable features_file, BlobFileWriter* absl_nullable unconditional_features_file) { BatchResult batch_result; - bool success = ExecuteAndReportCrash(env_.binary, input_vec, batch_result); - FUZZTEST_CHECK_EQ(input_vec.size(), batch_result.results().size()); + std::vector<ByteSpan> inputs; + inputs.reserve(mutants.size()); + for (auto mutant : mutants) { + inputs.push_back(mutant.data); + } + bool success = ExecuteAndReportCrash(env_.binary, inputs, batch_result); + FUZZTEST_CHECK_EQ(mutants.size(), batch_result.results().size()); for (const auto &extra_binary : env_.extra_binaries) { if (ShouldStop()) break; BatchResult extra_batch_result; - success = - ExecuteAndReportCrash(extra_binary, input_vec, extra_batch_result) && - success; + success = ExecuteAndReportCrash(extra_binary, inputs, extra_batch_result) && + success; } if (EarlyStopRequested()) return false; if (!success && env_.exit_on_crash) { @@ -449,9 +466,8 @@ RequestEarlyStop(EXIT_FAILURE); return false; } - FUZZTEST_CHECK_EQ(batch_result.results().size(), input_vec.size()); bool batch_gained_new_coverage = false; - for (size_t i = 0; i < input_vec.size(); i++) { + for (size_t i = 0; i < mutants.size(); i++) { if (ShouldStop()) break; FeatureVec &fv = batch_result.results()[i].mutable_features(); bool function_filter_passed = function_filter_.filter(fv); @@ -460,28 +476,28 @@ input_gained_new_coverage = true; if (unconditional_features_file != nullptr) { FUZZTEST_CHECK_OK(unconditional_features_file->Write( - PackFeaturesAndHash(input_vec[i], fv))); + PackFeaturesAndHash(inputs[i], fv))); } if (input_gained_new_coverage) { // TODO(kcc): [impl] add stats for filtered-out inputs. - if (!InputPassesFilter(input_vec[i])) continue; + if (!InputPassesFilter(inputs[i])) continue; fs_.MergeFeatures(fv); LogFeaturesAsSymbols(fv); batch_gained_new_coverage = true; FUZZTEST_CHECK_GT(fv.size(), 0UL); if (function_filter_passed) { - corpus_.Add(input_vec[i], fv, batch_result.results()[i].metadata(), + corpus_.Add(inputs[i], fv, batch_result.results()[i].metadata(), batch_result.results()[i].stats(), fs_, coverage_frontier_); } if (corpus_file != nullptr) { - FUZZTEST_CHECK_OK(corpus_file->Write(input_vec[i])); + FUZZTEST_CHECK_OK(corpus_file->Write(inputs[i])); } if (!env_.corpus_dir.empty() && !env_.corpus_dir[0].empty()) { - WriteToLocalHashedFileInDir(env_.corpus_dir[0], input_vec[i]); + WriteToLocalHashedFileInDir(env_.corpus_dir[0], inputs[i]); } if (features_file != nullptr) { FUZZTEST_CHECK_OK( - features_file->Write(PackFeaturesAndHash(input_vec[i], fv))); + features_file->Write(PackFeaturesAndHash(inputs[i], fv))); } } } @@ -583,8 +599,9 @@ while (!to_rerun.empty()) { if (ShouldStop()) break; size_t batch_size = std::min(to_rerun.size(), env_.batch_size); - std::vector<ByteSpan> batch(to_rerun.end() - batch_size, to_rerun.end()); - if (RunBatch(batch, nullptr, nullptr, features_file.get())) { + if (RunBatch( + InputsToMutantRefs({to_rerun.end() - batch_size, to_rerun.end()}), + nullptr, nullptr, features_file.get())) { UpdateAndMaybeLogStats("rerun-old", 1); } to_rerun.resize(to_rerun.size() - batch_size); @@ -779,7 +796,7 @@ seed_inputs.push_back({0}); } - RunBatch(std::vector<ByteSpan>{seed_inputs.begin(), seed_inputs.end()}, + RunBatch(InputsToMutantRefs({seed_inputs.begin(), seed_inputs.end()}), corpus_file, features_file, /*unconditional_features_file=*/nullptr); FUZZTEST_LOG(INFO) << "Number of input seeds available: " @@ -861,11 +878,15 @@ auto remaining_runs = env_.num_runs - new_runs; auto batch_size = std::min(env_.batch_size, remaining_runs); std::vector<MutationInputRef> mutation_inputs; + std::vector<size_t> mutate_input_to_corpus_idx; mutation_inputs.reserve(env_.mutate_batch_size); + mutate_input_to_corpus_idx.reserve(env_.mutate_batch_size); for (size_t i = 0; i < env_.mutate_batch_size; i++) { - const auto& corpus_record = env_.use_corpus_weights - ? corpus_.WeightedRandom(rng_) - : corpus_.UniformRandom(rng_); + const size_t origin = env_.use_corpus_weights + ? corpus_.WeightedRandom(rng_) + : corpus_.UniformRandom(rng_); + mutate_input_to_corpus_idx.push_back(origin); + const auto& corpus_record = corpus_.Records()[origin]; mutation_inputs.push_back( MutationInputRef{corpus_record.data, &corpus_record.metadata}); } @@ -875,13 +896,19 @@ if (ShouldStop()) break; new_runs += mutants.size(); - std::vector<ByteSpan> inputs; - inputs.reserve(mutants.size()); + std::vector<MutantRef> mutant_refs; + mutant_refs.reserve(mutants.size()); for (auto& mutant : mutants) { - inputs.push_back(mutant.data); + if (mutant.origin == Mutant::kOriginNone) { + mutant_refs.push_back(MutantRef{mutant.data, Mutant::kOriginNone}); + } else { + FUZZTEST_CHECK_LT(mutant.origin, mutate_input_to_corpus_idx.size()); + mutant_refs.push_back( + MutantRef{mutant.data, mutate_input_to_corpus_idx[mutant.origin]}); + } } bool gained_new_coverage = - RunBatch(inputs, corpus_file.get(), features_file.get(), nullptr); + RunBatch(mutant_refs, corpus_file.get(), features_file.get(), nullptr); if (gained_new_coverage) { UpdateAndMaybeLogStats("new-feature", 1);
diff --git a/centipede/centipede.h b/centipede/centipede.h index b090b1f..18fa83d 100644 --- a/centipede/centipede.h +++ b/centipede/centipede.h
@@ -76,7 +76,7 @@ std::string_view dir); private: - // Executes inputs from `input_vec`. + // Executes inputs from `mutants` and update the corpus. // For every input, its pruned features are written to // `unconditional_features_file`, (if that's non-null). // For every input that caused new features to be observed: @@ -84,8 +84,8 @@ // * the input is written to `corpus_file` (if that's non-null). // * its features are written to `features_file` (if that's non-null). // Returns true if new features were observed. - // Post-condition: `batch_result.results.size()` == `input_vec.size()`. - bool RunBatch(absl::Span<const ByteSpan> input_vec, + // Post-condition: `batch_result.results.size()` == `mutants.size()`. + bool RunBatch(absl::Span<const MutantRef> mutants, BlobFileWriter* absl_nullable corpus_file, BlobFileWriter* absl_nullable features_file, BlobFileWriter* absl_nullable unconditional_features_file);
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index a034cc1..d5497f3 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -121,12 +121,13 @@ for (size_t i = 0; i < num_mutants; ++i) { num_mutations_++; if (num_mutations_ < 256) { - mutants.push_back({/*data=*/{static_cast<uint8_t>(num_mutations_)}}); + mutants.push_back({/*data=*/{static_cast<uint8_t>(num_mutations_)}, + Mutant::kOriginNone}); continue; } uint8_t byte0 = (num_mutations_ - 256) / 256; uint8_t byte1 = (num_mutations_ - 256) % 256; - mutants.push_back({/*data=*/{byte0, byte1}}); + mutants.push_back({/*data=*/{byte0, byte1}, Mutant::kOriginNone}); } return mutants; } @@ -514,6 +515,7 @@ for (auto &mutant : mutants) { mutant.data.resize(1); mutant.data[0] = ++number_of_mutations_; + mutant.origin = Mutant::kOriginNone; } return mutants; } @@ -603,7 +605,8 @@ std::vector<Mutant> mutants; mutants.reserve(num_mutants); for (size_t i = 0; i < num_mutants; ++i) { - mutants.push_back({/*data=*/GetMutant(++number_of_mutations_)}); + mutants.push_back( + {/*data=*/GetMutant(++number_of_mutations_), Mutant::kOriginNone}); } return mutants; } @@ -720,6 +723,7 @@ for (auto &mutant : mutants) { mutant.data.resize(1); mutant.data[0] = ++number_of_mutations_; + mutant.origin = Mutant::kOriginNone; } return mutants; } @@ -853,7 +857,8 @@ mutants.reserve(num_mutants); for (size_t i = 0; i < num_mutants; ++i) { // The contents of each mutant is simply its sequential number. - mutants.push_back({/*data=*/{static_cast<uint8_t>(curr_input_idx_++)}}); + mutants.push_back({/*data=*/{static_cast<uint8_t>(curr_input_idx_++)}, + Mutant::kOriginNone}); } return mutants; } @@ -1009,7 +1014,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { - return {num_mutants, {/*data=*/{0}}}; + return {num_mutants, {/*data=*/{0}, Mutant::kOriginNone}}; } bool thread_check_passed() { return thread_check_passed_; } @@ -1065,7 +1070,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { - return {num_mutants, {/*data=*/{0}}}; + return {num_mutants, {/*data=*/{0}, Mutant::kOriginNone}}; } int execute_count() const { return execute_count_; } @@ -1103,7 +1108,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { - return {num_mutants, {/*data=*/{0}}}; + return {num_mutants, {/*data=*/{0}, Mutant::kOriginNone}}; } int execute_count() const { return execute_count_; } @@ -1141,7 +1146,7 @@ std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants) override { - return {num_mutants, {/*data=*/{0}}}; + return {num_mutants, {/*data=*/{0}, Mutant::kOriginNone}}; } int execute_count() const { return execute_count_; }
diff --git a/centipede/corpus.cc b/centipede/corpus.cc index def1496..ba1df9b 100644 --- a/centipede/corpus.cc +++ b/centipede/corpus.cc
@@ -242,12 +242,12 @@ weighted_distribution_.AddWeight(0); } -const CorpusRecord& Corpus::WeightedRandom(absl::BitGenRef rng) const { - return records_[weighted_distribution_.RandomIndex(rng)]; +size_t Corpus::WeightedRandom(absl::BitGenRef rng) const { + return weighted_distribution_.RandomIndex(rng); } -const CorpusRecord& Corpus::UniformRandom(absl::BitGenRef rng) const { - return records_[absl::Uniform<size_t>(rng, 0, records_.size())]; +size_t Corpus::UniformRandom(absl::BitGenRef rng) const { + return absl::Uniform<size_t>(rng, 0, records_.size()); } void Corpus::DumpStatsToFile(const FeatureSet &fs, std::string_view filepath,
diff --git a/centipede/corpus.h b/centipede/corpus.h index 72ef3ce..4c16ad3 100644 --- a/centipede/corpus.h +++ b/centipede/corpus.h
@@ -149,11 +149,11 @@ size_t NumActive() const { return records_.size(); } // Returns the max and avg sizes of the inputs. std::pair<size_t, size_t> MaxAndAvgSize() const; - // Returns a random active corpus record using weighted distribution. + // Returns a random active corpus record index using weighted distribution. // See WeightedDistribution. - const CorpusRecord& WeightedRandom(absl::BitGenRef rng) const; - // Returns a random active corpus record using uniform distribution. - const CorpusRecord& UniformRandom(absl::BitGenRef rng) const; + size_t WeightedRandom(absl::BitGenRef rng) const; + // Returns a random active corpus record index using uniform distribution. + size_t UniformRandom(absl::BitGenRef rng) const; // Returns the element with index 'idx', where `idx` < NumActive(). const ByteArray &Get(size_t idx) const { return records_[idx].data; } // Returns the execution metadata for the element `idx`, `idx` < NumActive().
diff --git a/centipede/corpus_test.cc b/centipede/corpus_test.cc index 4b47d5c..46e4d4e 100644 --- a/centipede/corpus_test.cc +++ b/centipede/corpus_test.cc
@@ -173,8 +173,7 @@ freq.clear(); freq.resize(corpus.NumActive()); for (int i = 0; i < kNumIter; i++) { - const auto& record = corpus.WeightedRandom(rng); - const auto id = record.data[0]; + const auto id = corpus.Records()[corpus.WeightedRandom(rng)].data[0]; ASSERT_LT(id, freq.size()); freq[id]++; } @@ -215,8 +214,7 @@ freq.clear(); freq.resize(corpus.NumActive()); for (int i = 0; i < kNumIter; i++) { - const auto& record = corpus.WeightedRandom(rng); - const auto id = record.data[0]; + const auto id = corpus.Records()[corpus.WeightedRandom(rng)].data[0]; ASSERT_LT(id, freq.size()); freq[id]++; } @@ -256,8 +254,7 @@ freq.clear(); freq.resize(corpus.NumActive()); for (int i = 0; i < kNumIter; i++) { - const auto& record = corpus.WeightedRandom(rng); - const auto id = record.data[0]; + const auto id = corpus.Records()[corpus.WeightedRandom(rng)].data[0]; ASSERT_LT(id, freq.size()); freq[id]++; } @@ -300,10 +297,8 @@ freq.clear(); freq.resize(corpus.NumActive()); for (int i = 0; i < kNumIter; i++) { - const auto& record = corpus.WeightedRandom(rng); - const auto id = record.data[0]; - ASSERT_LT(id, freq.size()); - freq[id]++; + const size_t idx = corpus.WeightedRandom(rng); + freq[idx]++; } };
diff --git a/centipede/dispatcher.cc b/centipede/dispatcher.cc index 6980b35..3ecb1f0 100644 --- a/centipede/dispatcher.cc +++ b/centipede/dispatcher.cc
@@ -537,10 +537,13 @@ "mutant must be non-empty with a valid pointer"); auto* output = GetOutputsBlobSequence(); DispatcherCheck(output != nullptr, "outputs blob sequence must exist"); - DispatcherCheck( - MutationResult::WriteMutant( - MutantRef{{static_cast<const uint8_t*>(data), size}}, *output), - "failed to write mutant"); + DispatcherCheck(MutationResult::WriteMutant( + MutantRef{{static_cast<const uint8_t*>(data), size}, + // TODO(xinhaoyuan): change the dispatcher + // interface to include the origin. + fuzztest::internal::Mutant::kOriginNone}, + *output), + "failed to write mutant"); } void FuzzTestDispatcherEmitFeedbackAs32BitFeatures(const uint32_t* features,
diff --git a/centipede/fuzztest_mutator.cc b/centipede/fuzztest_mutator.cc index f46041f..2fcf8d2 100644 --- a/centipede/fuzztest_mutator.cc +++ b/centipede/fuzztest_mutator.cc
@@ -146,13 +146,13 @@ cmp_tables.resize(inputs.size()); std::vector<Mutant> mutants; mutants.reserve(num_mutants); - for (int i = 0; i < num_mutants; ++i) { - auto index = absl::Uniform<size_t>(prng_, 0, inputs.size()); - if (!cmp_tables[index].has_value() && inputs[index].metadata != nullptr) { - cmp_tables[index].emplace(/*compact=*/true); - PopulateCmpEntries(*inputs[index].metadata, *cmp_tables[index]); + for (size_t i = 0; i < num_mutants; ++i) { + const size_t origin = absl::Uniform<size_t>(prng_, 0, inputs.size()); + if (!cmp_tables[origin].has_value() && inputs[origin].metadata != nullptr) { + cmp_tables[origin].emplace(/*compact=*/true); + PopulateCmpEntries(*inputs[origin].metadata, *cmp_tables[origin]); } - Mutant mutant = {/*data=*/inputs[index].data}; + auto mutant = Mutant{inputs[origin].data, origin}; if (mutant.data.size() > max_len_) mutant.data.resize(max_len_); if (knobs_.GenerateBool(knob_mutate_or_crossover, prng_())) { // Perform crossover with some other input. It may be the same input. @@ -162,8 +162,8 @@ } else { domain_->Mutate( mutant.data, prng_, - {/*cmp_tables=*/cmp_tables[index].has_value() ? &*cmp_tables[index] - : nullptr}, + {/*cmp_tables=*/cmp_tables[origin].has_value() ? &*cmp_tables[origin] + : nullptr}, /*only_shrink=*/false); } mutants.push_back(std::move(mutant));
diff --git a/centipede/mutation_data.h b/centipede/mutation_data.h index a4ef7f1..4dc0ee5 100644 --- a/centipede/mutation_data.h +++ b/centipede/mutation_data.h
@@ -19,6 +19,7 @@ #ifndef THIRD_PARTY_CENTIPEDE_MUTATION_DATA_H_ #define THIRD_PARTY_CENTIPEDE_MUTATION_DATA_H_ +#include <cstddef> #include <vector> #include "./centipede/execution_metadata.h" @@ -52,10 +53,16 @@ struct Mutant { // The mutant `data`. ByteArray data; + // The index of the input used to mutate into `data`. The base array may be + // different depending on the context: As mutation output it refers to the + // mutation input batch; As execution input it refers to the in-memory corpus. + size_t origin = kOriginNone; + // A special `origin` value to indicate that the mutant has no origin. + static constexpr size_t kOriginNone = static_cast<size_t>(-1); }; inline bool operator==(const Mutant& mutant, const Mutant& other) { - return mutant.data == other.data; + return mutant.data == other.data && mutant.origin == other.origin; } // A reference counterpart of `Mutant`. Needed because it can be constructed @@ -66,9 +73,11 @@ explicit MutantRef(const Mutant& mutant) : data(mutant.data) {} - explicit MutantRef(ByteSpan data) : data(data) {} + explicit MutantRef(ByteSpan data, size_t origin) + : data(data), origin(origin) {} ByteSpan data; + size_t origin = Mutant::kOriginNone; }; inline std::vector<ByteArray> GetDataFromMutants(
diff --git a/centipede/runner.cc b/centipede/runner.cc index 1ccc2f8..3cf4888 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc
@@ -646,7 +646,9 @@ attempt < num_mutants * kAverageMutationAttempts && num_outputs < num_mutants; ++attempt) { - const auto& input_data = inputs[rand_r(&seed) % num_inputs].data; + mutant.origin = rand_r(&seed) % num_inputs; + const auto& input_data = inputs[mutant.origin].data; + size_t size = std::min(input_data.size(), max_mutant_size); mutant.data.resize(max_mutant_size); std::copy(input_data.cbegin(), input_data.cbegin() + size,
diff --git a/centipede/runner_result.cc b/centipede/runner_result.cc index 7760fa5..07b7810 100644 --- a/centipede/runner_result.cc +++ b/centipede/runner_result.cc
@@ -45,6 +45,7 @@ // Mutation result tags. kTagHasCustomMutator, + kTagMutantOrigin, kTagMutant, }; @@ -187,6 +188,10 @@ } bool MutationResult::WriteMutant(MutantRef mutant, BlobSequence& blobseq) { + if (!blobseq.Write({kTagMutantOrigin, sizeof(mutant.origin), + reinterpret_cast<const uint8_t*>(&mutant.origin)})) { + return false; + } return blobseq.Write({kTagMutant, mutant.data.size(), mutant.data.data()}); } @@ -200,10 +205,17 @@ mutants_.clear(); mutants_.reserve(num_mutants); for (size_t i = 0; i < num_mutants; ++i) { + size_t origin = Mutant::kOriginNone; + { + const Blob blob = blobseq.Read(); + if (blob.tag != kTagMutantOrigin) return false; + if (blob.size != sizeof(origin)) return false; + std::memcpy(&origin, blob.data, sizeof(origin)); + } const Blob blob = blobseq.Read(); if (blob.tag != kTagMutant) return false; if (blob.size == 0) break; - mutants_.push_back({ByteArray{blob.data, blob.data + blob.size}}); + mutants_.push_back({ByteArray{blob.data, blob.data + blob.size}, origin}); } return true; }
diff --git a/centipede/runner_result_test.cc b/centipede/runner_result_test.cc index 5ac46f3..20ce433 100644 --- a/centipede/runner_result_test.cc +++ b/centipede/runner_result_test.cc
@@ -214,18 +214,18 @@ // Write a mutation result. ASSERT_TRUE(MutationResult::WriteHasCustomMutator(true, blobseq)); - ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{1, 2, 3}}, blobseq)); - ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{4, 5, 6}}, blobseq)); - ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{7, 8, 9}}, blobseq)); + ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{1, 2, 3}, 3}, blobseq)); + ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{4, 5, 6}, 2}, blobseq)); + ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{7, 8, 9}, 1}, blobseq)); blobseq.Reset(); MutationResult mutation_result; ASSERT_TRUE(mutation_result.Read(3, blobseq)); EXPECT_TRUE(mutation_result.has_custom_mutator()); - EXPECT_THAT( - mutation_result.mutants(), - ElementsAre(Mutant{{1, 2, 3}}, Mutant{{4, 5, 6}}, Mutant{{7, 8, 9}})); + EXPECT_THAT(mutation_result.mutants(), + ElementsAre(Mutant{{1, 2, 3}, 3}, Mutant{{4, 5, 6}, 2}, + Mutant{{7, 8, 9}, 1})); } TEST(ExecutionResult, ReadResultSucceedsOnlyWithInputBegin) {
diff --git a/centipede/testing/fuzz_target_with_custom_mutator.cc b/centipede/testing/fuzz_target_with_custom_mutator.cc index ec686ed..a760a19 100644 --- a/centipede/testing/fuzz_target_with_custom_mutator.cc +++ b/centipede/testing/fuzz_target_with_custom_mutator.cc
@@ -43,7 +43,7 @@ std::function<void(MutantRef)> new_mutant_callback) override { for (size_t i = 0; i < inputs.size() && i < num_mutants; ++i) { // Just return the original input as a mutant. - new_mutant_callback(MutantRef{inputs[i].data}); + new_mutant_callback(MutantRef{inputs[i].data, i}); } return true; }
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 33b2628..ef874eb 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc
@@ -523,6 +523,7 @@ absl::Cleanup cmp_tables_cleaner = [this]() { cmp_tables.clear(); }; for (size_t i = 0; i < num_mutants; ++i) { const auto choice = absl::Uniform<double>(prng_, 0, 1); + size_t origin_index = Mutant::kOriginNone; std::string mutant_data; constexpr double kDomainInitRatio = 0.0001; if (choice < kDomainInitRatio) { @@ -530,8 +531,7 @@ SerializeIRObject(fuzzer_impl_.params_domain_.SerializeCorpus( fuzzer_impl_.params_domain_.Init(prng_))); } else { - const auto origin_index = - absl::Uniform<size_t>(prng_, 0, inputs.size()); + origin_index = absl::Uniform<size_t>(prng_, 0, inputs.size()); const auto& origin = inputs[origin_index].data; auto parsed_origin = fuzzer_impl_.TryParse({(const char*)origin.data(), origin.size()}); @@ -554,7 +554,8 @@ fuzzer_impl_.params_domain_.SerializeCorpus(mutant.args)); } new_mutant_callback( - MutantRef{{(unsigned char*)mutant_data.data(), mutant_data.size()}}); + MutantRef{{(unsigned char*)mutant_data.data(), mutant_data.size()}, + origin_index}); } return true; }