From c0ae9ee8719a31d9f85ee0e147b0e5be4cc05f09 Mon Sep 17 00:00:00 2001 From: Xinhao Yuan Date: Mon, 6 Jul 2026 14:31:20 -0700 Subject: [PATCH] No public description PiperOrigin-RevId: 943498575 --- centipede/BUILD | 3 +- centipede/centipede_test.cc | 21 +- centipede/runner.cc | 320 ++++++++++++------ centipede/runner_interface.h | 77 +++-- centipede/testing/async_failing_target.cc | 22 +- centipede/testing/external_target.cc | 25 +- centipede/testing/fuzz_target_with_config.cc | 14 +- .../fuzz_target_with_custom_mutator.cc | 32 +- centipede/testing/seeded_fuzz_target.cc | 25 +- fuzztest/internal/BUILD | 2 + fuzztest/internal/centipede_adaptor.cc | 130 +++---- 11 files changed, 435 insertions(+), 236 deletions(-) diff --git a/centipede/BUILD b/centipede/BUILD index 518d7a6d5..1d8c33626 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -1139,6 +1139,7 @@ cc_library( linkstatic = True, # Must be linked statically even when dynamic_mode=on. deps = [ ":centipede_runner_no_main", + ":execution_metadata", ":mutation_data", "@abseil-cpp//absl/base:nullability", "@abseil-cpp//absl/types:span", @@ -1957,7 +1958,7 @@ cc_test( timeout = "long", srcs = ["centipede_test.cc"], data = [ - "@com_google_fuzztest//centipede", + ":centipede", "@com_google_fuzztest//centipede/testing:abort_fuzz_target", "@com_google_fuzztest//centipede/testing:async_failing_target", "@com_google_fuzztest//centipede/testing:expensive_startup_fuzz_target", diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index e242d2d95..f51b151ff 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc @@ -963,14 +963,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, GetsSeedInputs) { CentipedeDefaultCallbacks callbacks(env, stop_condition); std::vector seeds; - EXPECT_EQ(callbacks.GetSeeds(10, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq(std::vector{ - {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}})); - EXPECT_EQ(callbacks.GetSeeds(5, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq( - std::vector{{0}, {1}, {2}, {3}, {4}})); - EXPECT_EQ(callbacks.GetSeeds(100, seeds), 10); - EXPECT_THAT(seeds, testing::ContainerEq(std::vector{ + callbacks.GetSeeds(10, seeds); + EXPECT_THAT(seeds, testing::IsSupersetOf(std::vector{ {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}})); } @@ -1277,12 +1271,13 @@ TEST_F(CentipedeWithTemporaryLocalDir, UsesProvidedCustomMutator) { "centipede/testing/fuzz_target_with_custom_mutator"); CentipedeDefaultCallbacks callbacks(env, stop_condition); - const std::vector inputs = {{1}, {2}, {3}, {4}, {5}, {6}}; - const std::vector mutants = callbacks.Mutate( - GetMutationInputRefsFromDataInputs(inputs), inputs.size()); + const std::vector inputs = {{99}}; + const std::vector mutants = + callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs), 5); - // The custom mutator just returns the original inputs as mutants. - EXPECT_EQ(inputs, GetDataFromMutants(mutants)); + // The custom mutator just duplicates the original inputs as mutants. + EXPECT_EQ(GetDataFromMutants(mutants), + (std::vector{{99}, {99}, {99}, {99}, {99}})); } TEST_F(CentipedeWithTemporaryLocalDir, FailsOnMisbehavingCustomMutator) { diff --git a/centipede/runner.cc b/centipede/runner.cc index 82b2b4920..3b3fc7641 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -124,12 +124,12 @@ static void WaitWatchdogThreadIdle() { static void CheckWatchdogLimits() { const uint64_t curr_time = time(nullptr); struct Resource { - const char *what; - const char *units; + const char* what; + const char* units; uint64_t value; uint64_t limit; bool ignore_report; - const char *failure; + const char* failure; }; const uint64_t input_start_time = state->input_start_time; if (input_start_time == 0) return; @@ -152,7 +152,7 @@ static void CheckWatchdogLimits() { /*failure=*/kExecutionFailureRssLimitExceeded.data(), }}, }; - for (const auto &resource : resources) { + for (const auto& resource : resources) { if (resource.limit != 0 && resource.value > resource.limit) { if (!watchdog_failure_found.exchange(true)) { if (resource.ignore_report) { @@ -188,7 +188,7 @@ static void CheckWatchdogLimits() { // Watchdog thread. Periodically checks if it's time to abort due to a // timeout/OOM. -[[noreturn]] static void *WatchdogThread(void *unused) { +[[noreturn]] static void* WatchdogThread(void* unused) { // Since the watchdog is internal and does not execute user code, disable // SanCov tracing and TLS traversal. tls.traced = false; @@ -252,7 +252,7 @@ void GlobalRunnerState::ResetTimers() { // Byte array mutation fallback for a custom mutator, as defined here: // https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md extern "C" __attribute__((weak)) size_t -CentipedeLLVMFuzzerMutateCallback(uint8_t *data, size_t size, size_t max_size) { +CentipedeLLVMFuzzerMutateCallback(uint8_t* data, size_t size, size_t max_size) { // TODO(kcc): [as-needed] fix the interface mismatch. // LLVMFuzzerMutate is an array-based interface (for compatibility reasons) // while ByteArray has a vector-based interface. @@ -275,7 +275,7 @@ CentipedeLLVMFuzzerMutateCallback(uint8_t *data, size_t size, size_t max_size) { return array.size(); } -extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, +extern "C" size_t LLVMFuzzerMutate(uint8_t* data, size_t size, size_t max_size) { return CentipedeLLVMFuzzerMutateCallback(data, size, max_size); } @@ -283,7 +283,7 @@ extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, // An arbitrary large size for input data. static const size_t kMaxDataSize = 1 << 20; -static void WriteFeaturesToFile(FILE *file, const feature_t *features, +static void WriteFeaturesToFile(FILE* file, const feature_t* features, size_t size) { if (!size) return; auto bytes_written = fwrite(features, 1, sizeof(features[0]) * size, file); @@ -307,19 +307,30 @@ static void PrepareCoverage(bool full_clear) { PrepareSancov(full_clear); } -void RunnerCallbacks::GetSeeds(std::function seed_callback) { - seed_callback({0}); -} +void RunnerCallbacks::GetPresetSeedInputs( + const std::function& seed_callback) {} + +void* RunnerCallbacks::GetRandomSeedInput() { return DeserializeInput({0}); } std::string RunnerCallbacks::GetSerializedTargetConfig() { return ""; } -bool RunnerCallbacks::Mutate( - absl::Span /*inputs*/, size_t /*num_mutants*/, - std::function /*new_mutant_callback*/) { +void* RunnerCallbacks::Mutate(void* origin, + const ExecutionMetadata& origin_metadata) { + RunnerCheck(HasCustomMutator(), + "RunnerCallbacks::Mutate called despite HasCustomMutator() " + "returning false. This is a runner bug!"); RunnerCheck(!HasCustomMutator(), "Class deriving from RunnerCallbacks must implement Mutate() if " "HasCustomMutator() returns true."); - return true; + // Should be unreachable here. + return nullptr; +} + +void* RunnerCallbacks::CrossOver(void* origin, + const ExecutionMetadata& origin_metadata, + void* other, + const ExecutionMetadata& other_metadata) { + return Mutate(origin, origin_metadata); } class LegacyRunnerCallbacks : public RunnerCallbacks { @@ -331,10 +342,11 @@ class LegacyRunnerCallbacks : public RunnerCallbacks { custom_mutator_cb_(custom_mutator_cb), custom_crossover_cb_(custom_crossover_cb) {} - bool Execute(ByteSpan input) override { + bool Execute(void* input) override { PrintErrorAndExitIf(test_one_input_cb_ == nullptr, "missing test_on_input_cb"); - const int retval = test_one_input_cb_(input.data(), input.size()); + const auto* input_ba = reinterpret_cast(input); + const int retval = test_one_input_cb_(input_ba->data(), input_ba->size()); PrintErrorAndExitIf( retval != -1 && retval != 0, "test_on_input_cb returns invalid value other than -1 and 0"); @@ -345,8 +357,15 @@ class LegacyRunnerCallbacks : public RunnerCallbacks { return custom_mutator_cb_ != nullptr; } - bool Mutate(absl::Span inputs, size_t num_mutants, - std::function new_mutant_callback) override; + void SerializeInput(void* input, + const std::function& bytes_sink) override; + void* DeserializeInput(ByteSpan input_bytes) override; + void FreeInput(void* input) override; + + void* Mutate(void* origin, const ExecutionMetadata& origin_metadata) override; + void* CrossOver(void* origin, const ExecutionMetadata& origin_metadata, + void* other, + const ExecutionMetadata& other_metadata) override; private: FuzzerTestOneInputCallback test_one_input_cb_; @@ -362,8 +381,7 @@ std::unique_ptr CreateLegacyRunnerCallbacks( test_one_input_cb, custom_mutator_cb, custom_crossover_cb); } -static void RunOneInput(const uint8_t *data, size_t size, - RunnerCallbacks &callbacks) { +static void RunOneInput(void* input, RunnerCallbacks& callbacks) { state->stats = {}; size_t last_time_usec = 0; auto UsecSinceLast = [&last_time_usec]() { @@ -376,7 +394,7 @@ static void RunOneInput(const uint8_t *data, size_t size, PrepareCoverage(/*full_clear=*/false); state->stats.prep_time_usec = UsecSinceLast(); state->ResetTimers(); - int target_return_value = callbacks.Execute({data, size}) ? 0 : -1; + int target_return_value = callbacks.Execute(input) ? 0 : -1; state->stats.exec_time_usec = UsecSinceLast(); CheckWatchdogLimits(); if (fuzztest::internal::state->input_start_time.exchange(0) != 0) { @@ -390,18 +408,20 @@ static void RunOneInput(const uint8_t *data, size_t size, // Runs one input provided in file `input_path`. // Produces coverage data in file `input_path`-features. __attribute__((noinline)) // so that we see it in profile. -static void ReadOneInputExecuteItAndDumpCoverage(const char *input_path, - RunnerCallbacks &callbacks) { +static void ReadOneInputExecuteItAndDumpCoverage(const char* input_path, + RunnerCallbacks& callbacks) { // Read the input. auto data = ReadBytesFromFilePath(input_path); - RunOneInput(data.data(), data.size(), callbacks); + void* input = callbacks.DeserializeInput(data); + RunOneInput(input, callbacks); + callbacks.FreeInput(input); // Dump features to a file. char features_file_path[PATH_MAX]; snprintf(features_file_path, sizeof(features_file_path), "%s-features", input_path); - FILE *features_file = fopen(features_file_path, "w"); + FILE* features_file = fopen(features_file_path, "w"); PrintErrorAndExitIf(features_file == nullptr, "can't open coverage file"); const SanCovRuntimeRawFeatureParts sancov_features = @@ -413,13 +433,13 @@ static void ReadOneInputExecuteItAndDumpCoverage(const char *input_path, // Starts sending the outputs (coverage, etc.) to `outputs_blobseq`. // Returns true on success. -static bool StartSendingOutputsToEngine(BlobSequence &outputs_blobseq) { +static bool StartSendingOutputsToEngine(BlobSequence& outputs_blobseq) { return BatchResult::WriteInputBegin(outputs_blobseq); } // Copy all the sancov features to `data` with given `capacity` in bytes. // Returns the byte size of sancov features. -static size_t CopyFeatures(uint8_t *data, size_t capacity) { +static size_t CopyFeatures(uint8_t* data, size_t capacity) { const SanCovRuntimeRawFeatureParts sancov_features = SanCovRuntimeGetFeatures(); const size_t features_len_in_bytes = @@ -431,7 +451,7 @@ static size_t CopyFeatures(uint8_t *data, size_t capacity) { // Finishes sending the outputs (coverage, etc.) to `outputs_blobseq`. // Returns true on success. -static bool FinishSendingOutputsToEngine(BlobSequence &outputs_blobseq) { +static bool FinishSendingOutputsToEngine(BlobSequence& outputs_blobseq) { { LockGuard lock(state->execution_result_override_mu); bool has_overridden_execution_result = false; @@ -476,30 +496,32 @@ static bool FinishSendingOutputsToEngine(BlobSequence &outputs_blobseq) { // Handles an ExecutionRequest, see RequestExecution(). Reads inputs from // `inputs_blobseq`, runs them, saves coverage features to `outputs_blobseq`. // Returns EXIT_SUCCESS on success and EXIT_FAILURE otherwise. -static int ExecuteInputsFromShmem(BlobSequence &inputs_blobseq, - BlobSequence &outputs_blobseq, - RunnerCallbacks &callbacks) { +static int ExecuteInputsFromShmem(BlobSequence& inputs_blobseq, + BlobSequence& outputs_blobseq, + RunnerCallbacks& callbacks) { size_t num_inputs = 0; if (!IsExecutionRequest(inputs_blobseq.Read())) return EXIT_FAILURE; if (!IsNumInputs(inputs_blobseq.Read(), num_inputs)) return EXIT_FAILURE; - CentipedeBeginExecutionBatch(); - + std::vector inputs; + inputs.reserve(num_inputs); for (size_t i = 0; i < num_inputs; i++) { auto blob = inputs_blobseq.Read(); // TODO(kcc): distinguish bad input from end of stream. - if (!blob.IsValid()) return EXIT_SUCCESS; // no more blobs to read. + if (!blob.IsValid()) break; // no more blobs to read. if (!IsDataInput(blob)) return EXIT_FAILURE; // TODO(kcc): [impl] handle sizes larger than kMaxDataSize. size_t size = std::min(kMaxDataSize, blob.size); - // Copy from blob to data so that to not pass the shared memory further. - std::vector data(blob.data, blob.data + size); + inputs.push_back(callbacks.DeserializeInput({blob.data, size})); + } - // Starting execution of one more input. + CentipedeBeginExecutionBatch(); + + for (void* input : inputs) { if (!StartSendingOutputsToEngine(outputs_blobseq)) break; - RunOneInput(data.data(), data.size(), callbacks); + RunOneInput(input, callbacks); if (state->has_failure_description.load()) break; @@ -508,13 +530,24 @@ static int ExecuteInputsFromShmem(BlobSequence &inputs_blobseq, CentipedeEndExecutionBatch(); + for (void* input : inputs) { + callbacks.FreeInput(input); + } + return state->has_failure_description.load() ? EXIT_FAILURE : EXIT_SUCCESS; } // Dumps seed inputs to `output_dir`. Also see `GetSeedsViaExternalBinary()`. -static void DumpSeedsToDir(RunnerCallbacks &callbacks, const char *output_dir) { +static void DumpSeedsToDir(RunnerCallbacks& callbacks, const char* output_dir) { size_t seed_index = 0; - callbacks.GetSeeds([&](ByteSpan seed) { + // Declare it on the outer scope to save allocations. + ByteArray serialized; + auto dump_seed_callback = [&](void* seed) { + serialized.clear(); + callbacks.SerializeInput(seed, [&](ByteSpan bytes) { + serialized.insert(serialized.end(), bytes.begin(), bytes.end()); + }); + callbacks.FreeInput(seed); // Cap seed index within 9 digits. If this was triggered, the dumping would // take forever.. if (seed_index >= 1000000000) return; @@ -523,22 +556,26 @@ static void DumpSeedsToDir(RunnerCallbacks &callbacks, const char *output_dir) { snprintf(seed_path_buf, PATH_MAX, "%s/%09lu", output_dir, seed_index); PrintErrorAndExitIf(num_path_chars >= PATH_MAX, "seed path reaches PATH_MAX"); - FILE *output_file = fopen(seed_path_buf, "w"); + FILE* output_file = fopen(seed_path_buf, "w"); const size_t num_bytes_written = - fwrite(seed.data(), 1, seed.size(), output_file); - PrintErrorAndExitIf(num_bytes_written != seed.size(), + fwrite(serialized.data(), 1, serialized.size(), output_file); + PrintErrorAndExitIf(num_bytes_written != serialized.size(), "wrong number of bytes written for cf table"); fclose(output_file); ++seed_index; - }); + }; + callbacks.GetPresetSeedInputs(dump_seed_callback); + while (seed_index < 32) { + dump_seed_callback(callbacks.GetRandomSeedInput()); + } } // Dumps serialized target config to `output_file_path`. Also see // `GetSerializedTargetConfigViaExternalBinary()`. -static void DumpSerializedTargetConfigToFile(RunnerCallbacks &callbacks, - const char *output_file_path) { +static void DumpSerializedTargetConfigToFile(RunnerCallbacks& callbacks, + const char* output_file_path) { const std::string config = callbacks.GetSerializedTargetConfig(); - FILE *output_file = fopen(output_file_path, "w"); + FILE* output_file = fopen(output_file_path, "w"); const size_t num_bytes_written = fwrite(config.data(), 1, config.size(), output_file); PrintErrorAndExitIf( @@ -551,6 +588,51 @@ static void DumpSerializedTargetConfigToFile(RunnerCallbacks &callbacks, // TODO(kcc): [as-needed] optionally pass an external seed. static unsigned GetRandomSeed() { return time(nullptr); } +void MutateInputs(RunnerCallbacks& callbacks, + absl::Span inputs, size_t num_mutants, + std::function new_mutant_callback) { + static unsigned int seed = GetRandomSeed(); + std::vector input_objects; + input_objects.resize(inputs.size(), nullptr); + for (size_t i = 0; i < inputs.size(); ++i) { + input_objects[i] = callbacks.DeserializeInput(inputs[i].data); + } + ByteArray mutant_data; + ExecutionMetadata empty_metadata; + for (size_t i = 0; i < num_mutants; ++i) { + const size_t origin_index = rand_r(&seed) % inputs.size(); + void* mutant = nullptr; + if (rand_r(&seed) % 100 < state->run_time_flags.crossover_level) { + // Perform crossover `crossover_level`% of the time. + const size_t other_index = rand_r(&seed) % inputs.size(); + mutant = callbacks.CrossOver(input_objects[origin_index], + inputs[origin_index].metadata != nullptr + ? *inputs[origin_index].metadata + : empty_metadata, + input_objects[other_index], + inputs[other_index].metadata != nullptr + ? *inputs[other_index].metadata + : empty_metadata); + } else { + mutant = callbacks.Mutate(input_objects[origin_index], + inputs[origin_index].metadata != nullptr + ? *inputs[origin_index].metadata + : empty_metadata); + } + mutant_data.clear(); + callbacks.SerializeInput(mutant, [&mutant_data](ByteSpan bytes) { + mutant_data.insert(mutant_data.end(), bytes.begin(), bytes.end()); + }); + new_mutant_callback( + MutantRef{{(unsigned char*)mutant_data.data(), mutant_data.size()}, + origin_index}); + callbacks.FreeInput(mutant); + } + for (void* input_object : input_objects) { + callbacks.FreeInput(input_object); + } +} + // Handles a Mutation Request, see RequestMutation(). // Mutates inputs read from `inputs_blobseq`, // writes the mutants to `outputs_blobseq` @@ -560,9 +642,9 @@ static unsigned GetRandomSeed() { return time(nullptr); } // returns EXIT_FAILURE. // // TODO(kcc): [impl] make use of custom_crossover_cb, if available. -static int MutateInputsFromShmem(BlobSequence &inputs_blobseq, - BlobSequence &outputs_blobseq, - RunnerCallbacks &callbacks) { +static int MutateInputsFromShmem(BlobSequence& inputs_blobseq, + BlobSequence& outputs_blobseq, + RunnerCallbacks& callbacks) { // Read max_num_mutants. size_t num_mutants = 0; size_t num_inputs = 0; @@ -610,60 +692,90 @@ static int MutateInputsFromShmem(BlobSequence &inputs_blobseq, } if (!callbacks.HasCustomMutator()) return EXIT_SUCCESS; - if (!callbacks.Mutate(input_refs, num_mutants, [&](MutantRef mutant) { - MutationResult::WriteMutant(mutant, outputs_blobseq); - })) { - return EXIT_FAILURE; + { + bool succ = true; + MutateInputs(callbacks, input_refs, num_mutants, [&](MutantRef mutant) { + succ = succ && MutationResult::WriteMutant(mutant, outputs_blobseq); + }); + if (!succ) return EXIT_FAILURE; } + return EXIT_SUCCESS; } -bool LegacyRunnerCallbacks::Mutate( - absl::Span inputs, size_t num_mutants, - std::function new_mutant_callback) { - if (custom_mutator_cb_ == nullptr) return false; - unsigned int seed = GetRandomSeed(); - const size_t num_inputs = inputs.size(); +void LegacyRunnerCallbacks::SerializeInput( + void* input, const std::function& bytes_sink) { + const auto* input_object = reinterpret_cast(input); + bytes_sink(*input_object); +} + +void* LegacyRunnerCallbacks::DeserializeInput(ByteSpan input_bytes) { + return reinterpret_cast( + new ByteArray{input_bytes.begin(), input_bytes.end()}); +} + +void LegacyRunnerCallbacks::FreeInput(void* input) { + delete reinterpret_cast(input); +} + +void* LegacyRunnerCallbacks::Mutate(void* origin, + const ExecutionMetadata& origin_metadata) { + const auto* origin_ba = reinterpret_cast(origin); + state->byte_array_mutator->SetMetadata(origin_metadata); const size_t max_mutant_size = state->run_time_flags.max_len; - constexpr size_t kAverageMutationAttempts = 2; - // Reused across iterations to save memory allocations. - Mutant mutant; - for (size_t attempt = 0, num_outputs = 0; - attempt < num_mutants * kAverageMutationAttempts && - num_outputs < num_mutants; - ++attempt) { - 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, - mutant.data.begin()); - size_t new_size = 0; - if ((custom_crossover_cb_ != nullptr) && - rand_r(&seed) % 100 < state->run_time_flags.crossover_level) { - // Perform crossover `crossover_level`% of the time. - const auto &other_data = inputs[rand_r(&seed) % num_inputs].data; - new_size = custom_crossover_cb_(input_data.data(), input_data.size(), - other_data.data(), other_data.size(), - mutant.data.data(), max_mutant_size, - rand_r(&seed)); - } else { - new_size = custom_mutator_cb_(mutant.data.data(), size, max_mutant_size, - rand_r(&seed)); - } - if (new_size == 0) continue; - if (new_size > max_mutant_size) new_size = max_mutant_size; - mutant.data.resize(new_size); - new_mutant_callback(MutantRef{mutant}); - ++num_outputs; + const size_t size = std::min(max_mutant_size, origin_ba->size()); + auto* mutant = new ByteArray(); + mutant->resize(max_mutant_size); + std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin()); + static unsigned int seed = GetRandomSeed(); + size_t new_size = + custom_mutator_cb_(mutant->data(), size, max_mutant_size, rand_r(&seed)); + if (new_size == 0) { + new_size = 1; + mutant->assign({0}); + } else if (new_size == max_mutant_size) { + new_size = max_mutant_size; + mutant->resize(new_size); + } else { + mutant->resize(new_size); } - return true; + return reinterpret_cast(mutant); +} + +void* LegacyRunnerCallbacks::CrossOver( + void* origin, const ExecutionMetadata& origin_metadata, void* other, + const ExecutionMetadata& other_metadata) { + if (custom_crossover_cb_ == nullptr) { + return Mutate(origin, origin_metadata); + } + const auto* origin_ba = reinterpret_cast(origin); + const auto* other_ba = reinterpret_cast(other); + static unsigned int seed = GetRandomSeed(); + state->byte_array_mutator->SetMetadata((rand_r(&seed) & 1) ? origin_metadata + : other_metadata); + const size_t max_mutant_size = state->run_time_flags.max_len; + const size_t size = std::min(max_mutant_size, origin_ba->size()); + auto* mutant = new ByteArray(); + mutant->resize(max_mutant_size); + std::copy(origin_ba->begin(), origin_ba->begin() + size, mutant->begin()); + size_t new_size = custom_crossover_cb_( + origin_ba->data(), origin_ba->size(), other_ba->data(), other_ba->size(), + mutant->data(), max_mutant_size, rand_r(&seed)); + if (new_size == 0) { + new_size = 1; + mutant->assign({0}); + } else if (new_size == max_mutant_size) { + new_size = max_mutant_size; + mutant->resize(new_size); + } else { + mutant->resize(new_size); + } + return reinterpret_cast(mutant); } // Returns the current process VmSize, in bytes. static size_t GetVmSizeInBytes() { - FILE *f = fopen("/proc/self/statm", "r"); // man proc + FILE* f = fopen("/proc/self/statm", "r"); // man proc if (!f) return 0; size_t vm_size = 0; // NOTE: Ignore any (unlikely) failures to suppress a compiler warning. @@ -874,7 +986,7 @@ static int HandlePersistentMode(RunnerCallbacks& callbacks, // Default: Execute ReadOneInputExecuteItAndDumpCoverage() for all inputs.// // // Note: argc/argv are used for only ReadOneInputExecuteItAndDumpCoverage(). -int RunnerMain(int argc, char **argv, RunnerCallbacks &callbacks) { +int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks) { state->centipede_runner_main_executed = true; fprintf(stderr, "Centipede fuzz target runner; argv[0]: %s flags: %s\n", @@ -915,7 +1027,7 @@ int RunnerMain(int argc, char **argv, RunnerCallbacks &callbacks) { } // namespace fuzztest::internal extern "C" int LLVMFuzzerRunDriver( - int *absl_nonnull argc, char ***absl_nonnull argv, + int* absl_nonnull argc, char*** absl_nonnull argv, FuzzerTestOneInputCallback test_one_input_cb) { if (LLVMFuzzerInitialize) LLVMFuzzerInitialize(argc, argv); return RunnerMain(*argc, *argv, @@ -945,9 +1057,9 @@ extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input) { timeout_per_input; } -extern "C" __attribute__((weak)) const char *absl_nullable +extern "C" __attribute__((weak)) const char* absl_nullable CentipedeGetRunnerFlags() { - if (const char *runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) + if (const char* runner_flags_env = getenv("CENTIPEDE_RUNNER_FLAGS")) return strdup(runner_flags_env); return nullptr; } @@ -999,7 +1111,7 @@ extern "C" int CentipedeSetCurrentThreadTraced(int traced) { return old_traced; } -extern "C" size_t CentipedeGetExecutionResult(uint8_t *data, size_t capacity) { +extern "C" size_t CentipedeGetExecutionResult(uint8_t* data, size_t capacity) { fuzztest::internal::BlobSequence outputs_blobseq(data, capacity); if (!fuzztest::internal::StartSendingOutputsToEngine(outputs_blobseq)) return 0; @@ -1008,11 +1120,11 @@ extern "C" size_t CentipedeGetExecutionResult(uint8_t *data, size_t capacity) { return outputs_blobseq.offset(); } -extern "C" size_t CentipedeGetCoverageData(uint8_t *data, size_t capacity) { +extern "C" size_t CentipedeGetCoverageData(uint8_t* data, size_t capacity) { return fuzztest::internal::CopyFeatures(data, capacity); } -extern "C" void CentipedeSetExecutionResult(const uint8_t *data, size_t size) { +extern "C" void CentipedeSetExecutionResult(const uint8_t* data, size_t size) { using fuzztest::internal::state; fuzztest::internal::LockGuard lock(state->execution_result_override_mu); if (!state->execution_result_override) @@ -1020,14 +1132,14 @@ extern "C" void CentipedeSetExecutionResult(const uint8_t *data, size_t size) { state->execution_result_override->ClearAndResize(1); if (data == nullptr) return; // Removing const here should be fine as we don't write to `blobseq`. - fuzztest::internal::BlobSequence blobseq(const_cast(data), size); + fuzztest::internal::BlobSequence blobseq(const_cast(data), size); state->execution_result_override->Read(blobseq); fuzztest::internal::RunnerCheck( state->execution_result_override->num_outputs_read() == 1, "Failed to set execution result from CentipedeSetExecutionResult"); } -extern "C" void CentipedeSetFailureDescription(const char *description) { +extern "C" void CentipedeSetFailureDescription(const char* description) { using fuzztest::internal::state; if (state->failure_description_path == nullptr) return; if (state->has_failure_description.exchange(true)) return; diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h index c622daf28..b2d4c1136 100644 --- a/centipede/runner_interface.h +++ b/centipede/runner_interface.h @@ -26,36 +26,37 @@ #include "absl/base/nullability.h" #include "absl/types/span.h" +#include "./centipede/execution_metadata.h" #include "./centipede/mutation_data.h" #include "./common/defs.h" // Typedefs for the libFuzzer API, https://llvm.org/docs/LibFuzzer.html -using FuzzerTestOneInputCallback = int (*)(const uint8_t *data, size_t size); -using FuzzerInitializeCallback = int (*)(int *argc, char ***argv); -using FuzzerCustomMutatorCallback = size_t (*)(uint8_t *data, size_t size, +using FuzzerTestOneInputCallback = int (*)(const uint8_t* data, size_t size); +using FuzzerInitializeCallback = int (*)(int* argc, char*** argv); +using FuzzerCustomMutatorCallback = size_t (*)(uint8_t* data, size_t size, size_t max_size, unsigned int seed); using FuzzerCustomCrossOverCallback = size_t (*)( - const uint8_t *data1, size_t size1, const uint8_t *data2, size_t size2, - uint8_t *out, size_t max_out_size, unsigned int seed); + const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2, + uint8_t* out, size_t max_out_size, unsigned int seed); // This is the header-less interface of libFuzzer, see // https://llvm.org/docs/LibFuzzer.html. extern "C" { -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); -__attribute__((weak)) int LLVMFuzzerInitialize(int *absl_nonnull argc, - char ***absl_nonnull argv); -__attribute__((weak)) size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); +__attribute__((weak)) int LLVMFuzzerInitialize(int* absl_nonnull argc, + char*** absl_nonnull argv); +__attribute__((weak)) size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed); __attribute__((weak)) size_t LLVMFuzzerCustomCrossOver( - const uint8_t *data1, size_t size1, const uint8_t *data2, size_t size2, - uint8_t *out, size_t max_out_size, unsigned int seed); + const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2, + uint8_t* out, size_t max_out_size, unsigned int seed); } // extern "C" // https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library extern "C" int LLVMFuzzerRunDriver( - int *absl_nonnull argc, char ***absl_nonnull argv, + int* absl_nonnull argc, char*** absl_nonnull argv, FuzzerTestOneInputCallback test_one_input_cb); // Reconfigures the RSS limit to `rss_limit_mb` - 0 indicates no limit. @@ -73,10 +74,10 @@ extern "C" void CentipedeSetTimeoutPerInput(uint64_t timeout_per_input); // // It should return either a nullptr or a constant string that is valid // throughout the entire process life-time. -extern "C" const char *absl_nullable CentipedeGetRunnerFlags(); +extern "C" const char* absl_nullable CentipedeGetRunnerFlags(); // An overridable function to override `LLVMFuzzerMutate` behavior. -extern "C" size_t CentipedeLLVMFuzzerMutateCallback(uint8_t *data, size_t size, +extern "C" size_t CentipedeLLVMFuzzerMutateCallback(uint8_t* data, size_t size, size_t max_size); // Prepares to run a batch of test executions that ends with calling @@ -109,26 +110,26 @@ extern "C" int CentipedeSetCurrentThreadTraced(int traced); // processing an input. This function saves the data to the provided buffer and // returns the size of the saved data. It may be called after // CentipedeFinalizeProcessing(). -extern "C" size_t CentipedeGetExecutionResult(uint8_t *data, size_t capacity); +extern "C" size_t CentipedeGetExecutionResult(uint8_t* data, size_t capacity); // Retrieves the coverage data collected during the processing of an input. // This function saves the raw coverage data to the provided buffer and returns // the size of the saved data. It may be called after // CentipedeFinalizeProcessing(). -extern "C" size_t CentipedeGetCoverageData(uint8_t *data, size_t capacity); +extern "C" size_t CentipedeGetCoverageData(uint8_t* data, size_t capacity); // Set the current execution result to the opaque memory `data` with `size`. // Such data is retrieved using `CentipedeGetExecutionResult`, possibly from // another process. When `data` is `nullptr`, will set the execution result to // "empty" with no features or metadata. -extern "C" void CentipedeSetExecutionResult(const uint8_t *data, size_t size); +extern "C" void CentipedeSetExecutionResult(const uint8_t* data, size_t size); // Set the failure description for the runner to propagate further. Only the // description from the first call will be used. // // If used during executing batch inputs, the rest of the inputs would be // skipped and the batch would be considered as failed. -extern "C" void CentipedeSetFailureDescription(const char *description); +extern "C" void CentipedeSetFailureDescription(const char* description); namespace fuzztest::internal { @@ -140,23 +141,37 @@ class RunnerCallbacks { public: // Attempts to execute the test logic using `input`, and returns false if the // input should be ignored from the corpus, true otherwise. - virtual bool Execute(ByteSpan input) = 0; - // Generates seed inputs by calling `seed_callback` for each input. - // The default implementation generates a single-byte input {0}. - virtual void GetSeeds(std::function seed_callback); + virtual bool Execute(void* input) = 0; + // Provides the (deterministically) preset seed inputs by calling + // `seed_callback` on each of them. The default implementation provides no + // inputs. + virtual void GetPresetSeedInputs( + const std::function& seed_callback); + // Returns a random seed input. + // The default implementation returns the input deserialized from the + // single-byte input {0}. + virtual void* GetRandomSeedInput(); // Returns the serialized configuration from the test target. The default // implementation returns the empty string. virtual std::string GetSerializedTargetConfig(); // Returns true if and only if the test target has a custom mutator. virtual bool HasCustomMutator() const = 0; - // Generates at most `num_mutants` mutants by calling `new_mutant_callback` - // for each mutant. Returns true on success, false otherwise. - // - // TODO(xinhaoyuan): Consider supporting only_shrink to speed up - // input shrinking. - virtual bool Mutate(absl::Span inputs, - size_t num_mutants, - std::function new_mutant_callback); + // Mutates `origin` with `origin_metadata`. Must be overridden if + // `HasCustomMutator()` returns true. + virtual void* Mutate(void* origin, const ExecutionMetadata& origin_metadata); + // Crosses over `origin` (with `origin_metadata`) with `other` (with + // `other_metadata`). The default implementation returns `Mutate(origin, + // origin_metadata)` + virtual void* CrossOver(void* origin, + const ExecutionMetadata& origin_metadata, void* other, + const ExecutionMetadata& other_metadata); + // Serializes `input` as bytes into `bytes_sink`. Must emit non-empty bytes. + virtual void SerializeInput( + void* input, const std::function& bytes_sink) = 0; + // Creates and returns the input deserialized from `bytes`. + virtual void* DeserializeInput(ByteSpan bytes) = 0; + // Called when the runner is done with `input`. + virtual void FreeInput(void* input) = 0; virtual ~RunnerCallbacks() = default; }; @@ -173,7 +188,7 @@ std::unique_ptr CreateLegacyRunnerCallbacks( // // As an *experiment* we want to allow user code to call RunnerMain(). // This is not a guaranteed public interface (yet) and may disappear w/o notice. -int RunnerMain(int argc, char **argv, RunnerCallbacks &callbacks); +int RunnerMain(int argc, char** argv, RunnerCallbacks& callbacks); } // namespace fuzztest::internal diff --git a/centipede/testing/async_failing_target.cc b/centipede/testing/async_failing_target.cc index a3fc7dd69..a2a776430 100644 --- a/centipede/testing/async_failing_target.cc +++ b/centipede/testing/async_failing_target.cc @@ -26,24 +26,34 @@ namespace { class AsyncFailingTargetRunnerCallbacks : public fuzztest::internal::RunnerCallbacks { public: - bool Execute(fuzztest::internal::ByteSpan input) override { + bool Execute(void* input) override { to_fail_in_mutation = true; return true; } - bool Mutate(absl::Span inputs, - size_t num_mutants, - std::function - new_mutant_callback) override { + void* Mutate(void* inputs, + const fuzztest::internal::ExecutionMetadata& metadata) override { if (to_fail_in_mutation) { fprintf(stderr, "Fail in mutation\n"); std::abort(); } - return true; + return nullptr; } bool HasCustomMutator() const override { return true; } + void* DeserializeInput(fuzztest::internal::ByteSpan input) override { + return nullptr; + } + + void SerializeInput(void* input, + const std::function& + bytes_sink) override { + bytes_sink({0}); + } + + void FreeInput(void* input) override {} + private: bool to_fail_in_mutation = false; }; diff --git a/centipede/testing/external_target.cc b/centipede/testing/external_target.cc index b77ea6483..3e1fbdff4 100644 --- a/centipede/testing/external_target.cc +++ b/centipede/testing/external_target.cc @@ -52,7 +52,9 @@ void sendall(int sock, const uint8_t* data, size_t size) { class ExternalTargetRunnerCallbacks : public fuzztest::internal::RunnerCallbacks { public: - bool Execute(fuzztest::internal::ByteSpan input) override { + bool Execute(void* input_raw) override { + const auto* input = + reinterpret_cast(input_raw); const char* port_env = getenv("TARGET_PORT"); int port = 0; CHECK(port_env && absl::SimpleAtoi(port_env, &port)) @@ -72,10 +74,10 @@ class ExternalTargetRunnerCallbacks const int enable_nodelay = 1; setsockopt(conn_sock, SOL_TCP, TCP_NODELAY, &enable_nodelay, sizeof(enable_nodelay)); - const uint64_t input_size = input.size(); + const uint64_t input_size = input->size(); sendall(conn_sock, reinterpret_cast(&input_size), sizeof(input_size)); - sendall(conn_sock, input.data(), input_size); + sendall(conn_sock, input->data(), input_size); int match_result = 0; recvall(conn_sock, reinterpret_cast(&match_result), sizeof(match_result)); @@ -96,6 +98,23 @@ class ExternalTargetRunnerCallbacks } bool HasCustomMutator() const override { return false; } + + void* DeserializeInput(fuzztest::internal::ByteSpan input) override { + return reinterpret_cast( + new fuzztest::internal::ByteArray{input.begin(), input.end()}); + } + + void SerializeInput(void* input, + const std::function& + bytes_sink) override { + const auto* ba = + reinterpret_cast(input); + bytes_sink(*ba); + } + + void FreeInput(void* input) override { + delete reinterpret_cast(input); + } }; } // namespace diff --git a/centipede/testing/fuzz_target_with_config.cc b/centipede/testing/fuzz_target_with_config.cc index 77788c065..93a8379c0 100644 --- a/centipede/testing/fuzz_target_with_config.cc +++ b/centipede/testing/fuzz_target_with_config.cc @@ -32,12 +32,24 @@ class FakeSerializedConfigRunnerCallbacks public: // Trivial implementations for the execution and mutation logic, even though // they should not be used in the tests that use this test binary. - bool Execute(ByteSpan input) override { return true; } + bool Execute(void* input) override { return true; } bool HasCustomMutator() const override { return false; } std::string GetSerializedTargetConfig() override { return "fake serialized config"; } + + void* DeserializeInput(fuzztest::internal::ByteSpan input) override { + return nullptr; + } + + void SerializeInput(void* input, + const std::function& + bytes_sink) override { + bytes_sink({0}); + } + + void FreeInput(void* input) override {} }; int main(int argc, char** absl_nonnull argv) { diff --git a/centipede/testing/fuzz_target_with_custom_mutator.cc b/centipede/testing/fuzz_target_with_custom_mutator.cc index e6c28af8e..dcd3735a8 100644 --- a/centipede/testing/fuzz_target_with_custom_mutator.cc +++ b/centipede/testing/fuzz_target_with_custom_mutator.cc @@ -33,18 +33,32 @@ using fuzztest::internal::MutantRef; class CustomMutatorRunnerCallbacks : public fuzztest::internal::RunnerCallbacks { public: - bool Execute(ByteSpan input) override { return true; } + bool Execute(void* input) override { return true; } bool HasCustomMutator() const override { return true; } - bool Mutate(absl::Span inputs, - size_t num_mutants, - std::function 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, i}); - } - return true; + void* Mutate(void* input, + const fuzztest::internal::ExecutionMetadata& metadata) override { + const auto* ba = + reinterpret_cast(input); + return reinterpret_cast(new fuzztest::internal::ByteArray{*ba}); + } + + void* DeserializeInput(fuzztest::internal::ByteSpan input) override { + return reinterpret_cast( + new fuzztest::internal::ByteArray{input.begin(), input.end()}); + } + + void SerializeInput(void* input, + const std::function& + bytes_sink) override { + const auto* ba = + reinterpret_cast(input); + bytes_sink(*ba); + } + + void FreeInput(void* input) override { + delete reinterpret_cast(input); } }; diff --git a/centipede/testing/seeded_fuzz_target.cc b/centipede/testing/seeded_fuzz_target.cc index 0530109dd..285358d43 100644 --- a/centipede/testing/seeded_fuzz_target.cc +++ b/centipede/testing/seeded_fuzz_target.cc @@ -24,18 +24,37 @@ using fuzztest::internal::ByteSpan; class SeededRunnerCallbacks : public fuzztest::internal::RunnerCallbacks { public: - bool Execute(ByteSpan input) override { + bool Execute(void* input) override { // Should not be called in the test, but return true anyway. return true; } - void GetSeeds(std::function seed_callback) override { + void GetPresetSeedInputs( + const std::function& seed_callback) override { constexpr size_t kNumAvailSeeds = 10; for (size_t i = 0; i < kNumAvailSeeds; ++i) - seed_callback({static_cast(i)}); + seed_callback(reinterpret_cast( + new fuzztest::internal::ByteArray{static_cast(i)})); } bool HasCustomMutator() const override { return false; } + + void* DeserializeInput(fuzztest::internal::ByteSpan input) override { + return reinterpret_cast( + new fuzztest::internal::ByteArray{input.begin(), input.end()}); + } + + void SerializeInput(void* input, + const std::function& + bytes_sink) override { + const auto* ba = + reinterpret_cast(input); + bytes_sink(*ba); + } + + void FreeInput(void* input) override { + delete reinterpret_cast(input); + } }; int main(int argc, char** absl_nonnull argv) { diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index 2bf470ff8..3bea5e9e6 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD @@ -81,6 +81,7 @@ cc_library( "@abseil-cpp//absl/algorithm:container", "@abseil-cpp//absl/base:no_destructor", "@abseil-cpp//absl/cleanup", + "@abseil-cpp//absl/container:node_hash_map", "@abseil-cpp//absl/functional:any_invocable", "@abseil-cpp//absl/memory", "@abseil-cpp//absl/random", @@ -97,6 +98,7 @@ cc_library( "@com_google_fuzztest//centipede:centipede_interface", "@com_google_fuzztest//centipede:centipede_runner_no_main", "@com_google_fuzztest//centipede:environment", + "@com_google_fuzztest//centipede:execution_metadata", "@com_google_fuzztest//centipede:fuzztest_mutator", "@com_google_fuzztest//centipede:mutation_data", "@com_google_fuzztest//centipede:runner_result", diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 276a33119..c4507bb1f 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc @@ -48,6 +48,7 @@ #include "absl/algorithm/container.h" #include "absl/base/no_destructor.h" #include "absl/cleanup/cleanup.h" +#include "absl/container/node_hash_map.h" #include "absl/functional/any_invocable.h" #include "absl/memory/memory.h" #include "absl/random/distributions.h" @@ -68,6 +69,7 @@ #include "./centipede/centipede_default_callbacks.h" #include "./centipede/centipede_interface.h" #include "./centipede/environment.h" +#include "./centipede/execution_metadata.h" #include "./centipede/fuzztest_mutator.h" #include "./centipede/mutation_data.h" #include "./centipede/runner_interface.h" @@ -493,7 +495,10 @@ class CentipedeAdaptorRunnerCallbacks configuration_(*configuration), prng_(GetRandomSeed()) {} - bool Execute(fuzztest::internal::ByteSpan input) override { + bool Execute(void* input) override { + const auto* input_object = + reinterpret_cast(input); + // Disable tracing until running the property function in // `CentipedeFxitureDriver::RunFuzzTestIteration()` const int old_traced = CentipedeSetCurrentThreadTraced(/*traced=*/0); @@ -517,84 +522,79 @@ class CentipedeAdaptorRunnerCallbacks } // We should avoid doing anything other than executing the input here so // that we don't affect the execution time. - auto parsed_input = - fuzzer_impl_.TryParse({(char*)input.data(), input.size()}); - if (parsed_input.ok()) { - fuzzer_impl_.RunOneInput({*std::move(parsed_input)}); - if (runtime_.external_failure_detected()) { - // This would take effect only if no previous description is set. - CentipedeSetFailureDescription( - "INPUT FAILURE: external failure detected."); - } - return true; + fuzzer_impl_.RunOneInput(*input_object); + if (runtime_.external_failure_detected()) { + // This would take effect only if no previous description is set. + CentipedeSetFailureDescription( + "INPUT FAILURE: external failure detected."); } - return false; + return true; } - void GetSeeds(std::function seed_callback) - override { + void GetPresetSeedInputs( + const std::function& seed_callback) override { std::vector seeds = fuzzer_impl_.fixture_driver_->GetSeeds(); - constexpr int kInitialValuesInSeeds = 32; - for (int i = 0; i < kInitialValuesInSeeds; ++i) { - seeds.push_back(fuzzer_impl_.params_domain_.Init(prng_)); - } - absl::c_shuffle(seeds, prng_); for (const auto& seed : seeds) { - const auto seed_serialized = - SerializeIRObject(fuzzer_impl_.params_domain_.SerializeCorpus(seed)); - seed_callback(fuzztest::internal::AsByteSpan(seed_serialized)); + auto* input = new FuzzTestFuzzerImpl::Input{std::move(seed)}; + seed_callback(reinterpret_cast(input)); } } + void* GetRandomSeedInput() override { + auto seed = fuzzer_impl_.params_domain_.Init(prng_); + return reinterpret_cast( + new FuzzTestFuzzerImpl::Input{std::move(seed)}); + } + std::string GetSerializedTargetConfig() override { return configuration_.Serialize(); } bool HasCustomMutator() const override { return true; } - bool Mutate(absl::Span inputs, size_t num_mutants, - std::function new_mutant_callback) override { - if (inputs.empty()) return false; - cmp_tables.resize(inputs.size()); - absl::Cleanup cmp_tables_cleaner = [this]() { cmp_tables.clear(); }; - for (size_t i = 0; i < num_mutants; ++i) { - const auto choice = absl::Uniform(prng_, 0, 1); - size_t origin_index = Mutant::kOriginNone; - std::string mutant_data; - constexpr double kDomainInitRatio = 0.0001; - if (choice < kDomainInitRatio) { - mutant_data = - SerializeIRObject(fuzzer_impl_.params_domain_.SerializeCorpus( - fuzzer_impl_.params_domain_.Init(prng_))); - } else { - origin_index = absl::Uniform(prng_, 0, inputs.size()); - const auto& origin = inputs[origin_index].data; - auto parsed_origin = - fuzzer_impl_.TryParse({(const char*)origin.data(), origin.size()}); - if (!parsed_origin.ok()) { - parsed_origin = fuzzer_impl_.params_domain_.Init(prng_); - } - auto mutant = FuzzTestFuzzerImpl::Input{*std::move(parsed_origin)}; - if (runtime_.run_mode() == RunMode::kFuzz && - !cmp_tables[origin_index].has_value() && - inputs[origin_index].metadata != nullptr) { - cmp_tables[origin_index].emplace(/*compact=*/true); - PopulateCmpEntries(*inputs[origin_index].metadata, - *cmp_tables[origin_index]); - } - fuzzer_impl_.MutateValue( - mutant, prng_, - {cmp_tables[origin_index].has_value() ? &*cmp_tables[origin_index] - : nullptr}); - mutant_data = SerializeIRObject( - fuzzer_impl_.params_domain_.SerializeCorpus(mutant.args)); - } - new_mutant_callback( - MutantRef{{(unsigned char*)mutant_data.data(), mutant_data.size()}, - origin_index}); + void SerializeInput( + void* input, const std::function& bytes_sink) override { + auto* input_object = reinterpret_cast(input); + std::string serialized_input = SerializeIRObject( + fuzzer_impl_.params_domain_.SerializeCorpus(input_object->args)); + bytes_sink({reinterpret_cast(serialized_input.data()), + serialized_input.size()}); + } + + void* DeserializeInput(ByteSpan input_bytes) override { + auto parse_result = fuzzer_impl_.TryParse( + {(const char*)input_bytes.data(), input_bytes.size()}); + if (parse_result.ok()) { + return reinterpret_cast( + new FuzzTestFuzzerImpl::Input{*std::move(parse_result)}); } - return true; + return reinterpret_cast( + new FuzzTestFuzzerImpl::Input{fuzzer_impl_.params_domain_.Init(prng_)}); + } + + void FreeInput(void* input) override { + auto it = cmp_tables_.find(input); + if (it != cmp_tables_.end()) { + delete it->second; + cmp_tables_.erase(it); + } + delete reinterpret_cast(input); + } + + void* Mutate(void* origin, + const ExecutionMetadata& origin_metadata) override { + const auto* origin_object = + reinterpret_cast(origin); + auto* mutant = new FuzzTestFuzzerImpl::Input{*origin_object}; + auto& cmp_table = cmp_tables_[origin]; + if (cmp_table == nullptr) { + cmp_table = + new fuzztest::internal::TablesOfRecentCompares{/*compact=*/true}; + PopulateCmpEntries(origin_metadata, *cmp_table); + } + fuzzer_impl_.MutateValue(*mutant, prng_, {cmp_table}); + return mutant; } ~CentipedeAdaptorRunnerCallbacks() override { runtime_.UnsetCurrentArgs(); } @@ -604,8 +604,8 @@ class CentipedeAdaptorRunnerCallbacks FuzzTestFuzzerImpl& fuzzer_impl_; const Configuration& configuration_; absl::BitGen prng_; - std::vector> - cmp_tables; + absl::node_hash_map + cmp_tables_; }; namespace {