diff --git a/centipede/BUILD b/centipede/BUILD index d486c85dc..dcaabf1c8 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -984,21 +984,6 @@ cc_library( # This has no dependency and only uses string, pair and vector. ) -cc_library( - name = "dispatcher", - srcs = ["dispatcher.cc"], - hdrs = ["dispatcher.h"], - deps = [ - ":execution_metadata", - ":mutation_data", - ":runner_request", - ":runner_result", - ":shared_memory_blob_sequence", - "@abseil-cpp//absl/base:nullability", - "@com_google_fuzztest//common:defs", - ], -) - cc_library( name = "engine_worker", srcs = [ diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc index 1bcb1e3bd..961e93535 100644 --- a/centipede/centipede_callbacks.cc +++ b/centipede/centipede_callbacks.cc @@ -614,9 +614,8 @@ int CentipedeCallbacks::ExecuteCentipedeSancovBinaryWithShmem( ReadFromLocalFile(failure_signature_path_, batch_result.failure_signature()); } else { - // TODO(xinhaoyuan): Refactor runner to use dispatcher so this branch - // can be removed. Crash deduplication assumes that the failure - // signature contains no dashes and that it can be used as a file name. + // Crash deduplication assumes that the failure signature contains no + // dashes and that it can be used as a file name. batch_result.failure_signature() = Hash(batch_result.failure_description()); } diff --git a/centipede/dispatcher.cc b/centipede/dispatcher.cc deleted file mode 100644 index 3ecb1f0bd..000000000 --- a/centipede/dispatcher.cc +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright 2025 The Centipede Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "./centipede/dispatcher.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "absl/base/nullability.h" -#include "./centipede/execution_metadata.h" -#include "./centipede/mutation_data.h" -#include "./centipede/runner_request.h" -#include "./centipede/runner_result.h" -#include "./centipede/shared_memory_blob_sequence.h" -#include "./common/defs.h" - -namespace fuzztest::internal { - -namespace { - -// Logging needs to be signal safe. - -struct LogErrNo {}; -struct LogLnSync {}; - -void DispatcherLog() {} - -template -void DispatcherLog(const T& first, const Rest&... rest) { - if constexpr (std::is_same_v) { - auto saved_errno = errno; - char err_buf[80]; - if (strerror_r(saved_errno, err_buf, sizeof(err_buf)) != 0) { - constexpr std::string_view kFallbackMsg = "[strerror_r failed]"; - static_assert(kFallbackMsg.size() < sizeof(err_buf)); - std::memcpy(err_buf, kFallbackMsg.data(), kFallbackMsg.size()); - err_buf[kFallbackMsg.size()] = 0; - } - DispatcherLog(err_buf); - } else if constexpr (std::is_same_v) { - write(STDERR_FILENO, "\n", 1); - fsync(STDERR_FILENO); - } else { - std::string_view sv = first; - while (!sv.empty()) { - const int r = write(STDERR_FILENO, sv.data(), sv.size()); - if (r <= 0) break; - sv = sv.substr(r); - } - } - DispatcherLog(rest...); -} - -inline void DispatcherCheck(bool condition, std::string_view error) { - if (!condition) { - DispatcherLog(error, LogLnSync{}); - std::_Exit(1); - } -} - -const char* GetDispatcherFlags() { - static auto dispatcher_flags = []() -> const char* { - // TODO(xinhaoyuan): Rename the env name to FUZZTEST_DISPATCHER_FLAGS. - const char* env_flags = std::getenv("CENTIPEDE_RUNNER_FLAGS"); - if (env_flags == nullptr) return nullptr; - const char* result = strdup(env_flags); - DispatcherCheck(result != nullptr, "Cannot copy the dispatcher flags"); - return result; - }(); - return dispatcher_flags; -} - -std::optional GetDispatcherFlag( - const char* absl_nonnull flag_header) { - const char* dispatcher_flags = GetDispatcherFlags(); - if (dispatcher_flags == nullptr) return std::nullopt; - // Extract "value" from ":flag=value:" - const char* beg = std::strstr(dispatcher_flags, flag_header); - if (!beg) return std::nullopt; - const char* value_beg = beg + std::strlen(flag_header); - const char* value_end = std::strstr(value_beg, ":"); - if (!value_end) return std::nullopt; - return std::string_view{value_beg, - static_cast(value_end - value_beg)}; -} - -bool HasDispatcherSwitchFlag(const char* absl_nonnull switch_flag) { - const char* dispatcher_flags = GetDispatcherFlags(); - if (dispatcher_flags == nullptr) return false; - return std::strstr(dispatcher_flags, switch_flag) != nullptr; -} - -enum class DispatcherAction { - kGetBinaryId, - kListTests, - kTestGetSeeds, - kTestMutate, - kTestExecute, -}; - -constexpr char kDispatcherBinaryIdOutputFlagHeader[] = ":binary_id_output="; -constexpr char kDispatcherTestNameFlagHeader[] = ":test="; -constexpr char kDispatcherTestListingPrefixFlagHeader[] = - ":test_listing_prefix="; -constexpr char kDispatcherTestGetSeedsOutputDirFlagHeader[] = - ":arg1="; // TODO: Use better flag names when standardizing the protocol. -constexpr char kDispatcherFailureDescriptionPathFlagHeader[] = - ":failure_description_path="; -constexpr char kDispatcherFailureSignaturePathFlagHeader[] = - ":failure_signature_path="; -constexpr char kDispatcherInputsBlobSequencePathFlagHeader[] = - ":arg1="; // TODO: Use better flag names when standardizing the protocol. -constexpr char kDispatcherOutputsBlobSequencePathFlagHeader[] = - ":arg2="; // TODO: Use better flag names when standardizing the protocol. - -BlobSequence* GetInputsBlobSequence() { - static auto result = []() -> BlobSequence* { - if (std::strstr(GetDispatcherFlags(), ":shmem:") == nullptr) { - return nullptr; - } - auto input_path = - GetDispatcherFlag(kDispatcherInputsBlobSequencePathFlagHeader); - DispatcherCheck(input_path.has_value(), "inputs blob sequence is missing"); - return new SharedMemoryBlobSequence(std::string(*input_path).c_str()); - }(); - return result; -} - -BlobSequence* GetOutputsBlobSequence() { - static auto result = []() -> BlobSequence* { - if (std::strstr(GetDispatcherFlags(), ":shmem:") == nullptr) { - return nullptr; - } - auto output_path = - GetDispatcherFlag(kDispatcherOutputsBlobSequencePathFlagHeader); - DispatcherCheck(output_path.has_value(), - "outputs blob sequence is missing"); - return new SharedMemoryBlobSequence(std::string(*output_path).c_str()); - }(); - return result; -} - -DispatcherAction GetDispatcherAction() { - static DispatcherAction dispatcher_action = [] { - if (HasDispatcherSwitchFlag(":dump_binary_id:")) { - return DispatcherAction::kGetBinaryId; - } - if (HasDispatcherSwitchFlag(":list_tests:")) { - return DispatcherAction::kListTests; - } - if (HasDispatcherSwitchFlag(":dump_seed_inputs:")) { - return DispatcherAction::kTestGetSeeds; - } - auto* inputs_blobseq = GetInputsBlobSequence(); - DispatcherCheck(inputs_blobseq != nullptr, - "input blob sequence is not found"); - auto request_type_blob = inputs_blobseq->Read(); - if (IsMutationRequest(request_type_blob)) { - inputs_blobseq->Reset(); - return DispatcherAction::kTestMutate; - } - if (IsExecutionRequest(request_type_blob)) { - inputs_blobseq->Reset(); - return DispatcherAction::kTestExecute; - } - DispatcherCheck(false, "unknown dispatcher action from the flags"); - // should not reach here. - std::abort(); - }(); - return dispatcher_action; -} - -template -void TrySetFileContents(const char* absl_nonnull path, C... contents) { - // Needs to be signal-safe. - int f = open(path, O_CREAT | O_TRUNC | O_WRONLY, /*mode=*/0660); - if (f == -1) { - DispatcherLog("cannot open path ", path, ": ", LogErrNo{}, LogLnSync{}); - return; - } - ([&] { - std::string_view sv = contents; - while (!sv.empty()) { - const int r = write(f, sv.data(), sv.size()); - if (r < 0) { - DispatcherLog("write() failed on ", path, ": ", LogErrNo{}, - LogLnSync{}); - return false; - } - if (r == 0) { - DispatcherLog("write() on ", path, - " returns 0 unexpectedly. Stopping writing the file."); - return false; - } - sv = sv.substr(r); - } - return true; - }() && - ...); // NOLINT - stop fighting with auto-fomatting. - if (fsync(f) != 0) { - DispatcherLog("fsync() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); - } - if (close(f) != 0) { - DispatcherLog("close() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); - } -} - -static std::atomic in_test_callback = false; - -class TestCallbackGuard { - public: - TestCallbackGuard() { - DispatcherCheck(!in_test_callback.exchange(true), - "test callback is already activated"); - } - - ~TestCallbackGuard() { in_test_callback = false; } -}; - -void DispatcherDoGetBinaryId(const FuzzTestDispatcherCallbacks& callbacks) { - const auto binary_id_output_path = - GetDispatcherFlag(kDispatcherBinaryIdOutputFlagHeader); - DispatcherCheck(binary_id_output_path.has_value(), - "binary ID output path is not set"); - std::string binary_id; - { - TestCallbackGuard guard; - binary_id = callbacks.get_binary_id ? callbacks.get_binary_id() : ""; - } - TrySetFileContents(std::string{*binary_id_output_path}.c_str(), binary_id); -} - -void DispatcherDoListTests(const FuzzTestDispatcherCallbacks& callbacks) { - DispatcherCheck(callbacks.list_tests != nullptr, - "list_tests callback must be set"); - TestCallbackGuard guard; - callbacks.list_tests(); -} - -void DispatcherDoGetSeeds(const FuzzTestDispatcherCallbacks& callbacks) { - if (callbacks.get_seeds == nullptr) { - return; - } - TestCallbackGuard guard; - callbacks.get_seeds(); -} - -int DispatcherDoMutate(const FuzzTestDispatcherCallbacks& callbacks) { - auto* inputs_blobseq = GetInputsBlobSequence(); - auto* outputs_blobseq = GetOutputsBlobSequence(); - DispatcherCheck(inputs_blobseq != nullptr && outputs_blobseq != nullptr, - "inputs/outputs blob sequences must be specified"); - - bool has_mutate = callbacks.mutate != nullptr; - if (!MutationResult::WriteHasCustomMutator(has_mutate, *outputs_blobseq)) { - std::fprintf(stderr, "Failed to write custom mutator indicator!\n"); - return EXIT_FAILURE; - } - if (!has_mutate) { - return EXIT_SUCCESS; - } - - // Read max_num_mutants. - size_t num_mutants = 0; - size_t num_inputs = 0; - if (!IsMutationRequest(inputs_blobseq->Read())) { - std::fprintf(stderr, "Not mutation request!\n"); - return EXIT_FAILURE; - } - if (!IsNumMutants(inputs_blobseq->Read(), num_mutants)) { - std::fprintf(stderr, "No num mutants\n"); - return EXIT_FAILURE; - } - if (!IsNumInputs(inputs_blobseq->Read(), num_inputs)) { - std::fprintf(stderr, "No num inputs\n"); - return EXIT_FAILURE; - } - - struct OwningMutateInput { - ByteArray data; - ExecutionMetadata metadata; - }; - // Note: unclear if we can continue using std::vector (or other STL) - // in the runner. But for now use std::vector. - // - // Collect the inputs into a vector. We copy them instead of using pointers - // into shared memory so that the user code doesn't touch the shared memory. - std::vector owning_inputs; - owning_inputs.reserve(num_inputs); - std::vector inputs; - inputs.reserve(num_inputs); - for (size_t i = 0; i < num_inputs; ++i) { - // If inputs_blobseq have overflown in the engine, we still want to - // handle the first few inputs. - ExecutionMetadata metadata; - if (!IsExecutionMetadata(inputs_blobseq->Read(), metadata)) { - break; - } - auto blob = inputs_blobseq->Read(); - if (!IsDataInput(blob)) break; - owning_inputs.push_back( - OwningMutateInput{/*data=*/ByteArray{blob.data, blob.data + blob.size}, - /*metadata=*/std::move(metadata)}); - inputs.push_back(FuzzTestDispatcherInputForMutate{ - /*input=*/owning_inputs.back().data.data(), - /*input_size=*/owning_inputs.back().data.size(), - /*metadata=*/owning_inputs.back().metadata.cmp_data.data(), - /*metadata_size=*/owning_inputs.back().metadata.cmp_data.size()}); - } - - { - TestCallbackGuard guard; - fprintf(stderr, "calling custom mutator\n"); - // We ensure that: - // * `inputs` is a valid pointer to an array of - // `FuzzTestDispatcherInputForMutate` objects with length `num_inputs`. - // * Each object of the array contains a valid `input` pointer to - // `input_size` bytes, and a valid `metadata` pointer to `metadata_size` - // bytes. - callbacks.mutate(inputs.data(), inputs.size(), num_mutants, - /*shrink=*/0); - } - return EXIT_SUCCESS; -} - -int DispatcherDoExecute(const FuzzTestDispatcherCallbacks& callbacks) { - DispatcherCheck(callbacks.execute != nullptr, "execute callback must be set"); - auto* inputs_blobseq = GetInputsBlobSequence(); - auto* outputs_blobseq = GetOutputsBlobSequence(); - DispatcherCheck(inputs_blobseq != nullptr && outputs_blobseq != nullptr, - "inputs/ouptuts blob sequence must exist"); - - size_t num_inputs = 0; - DispatcherCheck(IsExecutionRequest(inputs_blobseq->Read()), - "not an execution request"); - DispatcherCheck(IsNumInputs(inputs_blobseq->Read(), num_inputs), - "failed to read num_inputs"); - - for (size_t i = 0; i < num_inputs; i++) { - auto blob = inputs_blobseq->Read(); - if (!blob.IsValid()) return EXIT_SUCCESS; // no more blobs to read. - if (!IsDataInput(blob)) return EXIT_FAILURE; - - // Copy from blob to data so that to not pass the shared memory further. - ByteArray data(blob.data, blob.data + blob.size); - - if (!BatchResult::WriteInputBegin(*outputs_blobseq)) { - // TODO: This is to follow the previous behavior, but should we abort - // here? - break; - } - { - TestCallbackGuard guard; - // We ensure that `input` is a valid pointer to an array of `size` bytes. - callbacks.execute(data.data(), data.size()); - } - if (!BatchResult::WriteInputEnd(*outputs_blobseq)) { - // TODO: This is to follow the previous behavior, but should we abort - // here? - break; - } - } - - return EXIT_SUCCESS; -} - -void DispatcherEmitFailure(const char* absl_nonnull prefix, - const char* absl_nonnull description, - const char* signature, size_t signature_size) { - bool success = false; - [[maybe_unused]] static bool write_once = [=, &success] { - if (const auto failure_description_path = - GetDispatcherFlag(kDispatcherFailureDescriptionPathFlagHeader); - failure_description_path.has_value()) { - TrySetFileContents(std::string{*failure_description_path}.c_str(), prefix, - description); - } - if (const auto failure_signature_path = - GetDispatcherFlag(kDispatcherFailureSignaturePathFlagHeader); - failure_signature_path.has_value()) { - TrySetFileContents(std::string{*failure_signature_path}.c_str(), - std::string_view{signature, signature_size}); - } - success = true; - return true; - }(); - if (!success) { - DispatcherLog("Failed to emit failure ", prefix, description, LogLnSync{}); - } -} - -} // namespace - -} // namespace fuzztest::internal - -using fuzztest::internal::BatchResult; -using fuzztest::internal::DispatcherAction; -using fuzztest::internal::DispatcherCheck; -using fuzztest::internal::DispatcherDoExecute; -using fuzztest::internal::DispatcherDoGetBinaryId; -using fuzztest::internal::DispatcherDoGetSeeds; -using fuzztest::internal::DispatcherDoListTests; -using fuzztest::internal::DispatcherDoMutate; -using fuzztest::internal::DispatcherEmitFailure; -using fuzztest::internal::GetDispatcherAction; -using fuzztest::internal::GetDispatcherFlag; -using fuzztest::internal::GetDispatcherFlags; -using fuzztest::internal::GetOutputsBlobSequence; -using fuzztest::internal::HasDispatcherSwitchFlag; -using fuzztest::internal::in_test_callback; -using fuzztest::internal::kDispatcherTestGetSeedsOutputDirFlagHeader; -using fuzztest::internal::kDispatcherTestListingPrefixFlagHeader; -using fuzztest::internal::kDispatcherTestNameFlagHeader; -using fuzztest::internal::MutantRef; -using fuzztest::internal::MutationResult; - -int FuzzTestDispatcherIsEnabled() { - const char* flags = GetDispatcherFlags(); - if (flags == nullptr) return 0; - fprintf(stderr, "Dispatcher is enabled with flags: %s\n", flags); - return 1; -} - -const char* FuzzTestDispatcherGetTestName() { - static auto test_name = []() -> const char* { - const auto test_name = GetDispatcherFlag(kDispatcherTestNameFlagHeader); - if (!test_name.has_value()) return nullptr; - return strndup(test_name->data(), test_name->size()); - }(); - return test_name; -} - -int FuzzTestDispatcherRun(const FuzzTestDispatcherCallbacks* callbacks) { - DispatcherCheck(callbacks != nullptr, "callbacks must be set"); - if (HasDispatcherSwitchFlag(":dump_configuration:")) { - return 0; - } - switch (GetDispatcherAction()) { - case DispatcherAction::kGetBinaryId: - DispatcherDoGetBinaryId(*callbacks); - break; - case DispatcherAction::kListTests: - DispatcherDoListTests(*callbacks); - break; - case DispatcherAction::kTestGetSeeds: - DispatcherDoGetSeeds(*callbacks); - break; - case DispatcherAction::kTestMutate: - DispatcherDoMutate(*callbacks); - break; - case DispatcherAction::kTestExecute: - DispatcherDoExecute(*callbacks); - break; - default: - DispatcherCheck(false, "unknown dispatcher action to take"); - } - return 0; -} - -void FuzzTestDispatcherEmitTestName(const char* name) { - DispatcherCheck( - GetDispatcherAction() == DispatcherAction::kListTests && in_test_callback, - "must be called inside test callback for listing tests"); - static auto test_listing_prefix = - GetDispatcherFlag(kDispatcherTestListingPrefixFlagHeader); - DispatcherCheck(test_listing_prefix.has_value(), - "test listing path prefix must be set"); - DispatcherCheck(name != nullptr, "test name must be set"); - auto test_output_path = std::string{*test_listing_prefix}; - test_output_path += name; - FILE* f = std::fopen(test_output_path.c_str(), "w"); - if (f == nullptr) { - std::perror("FAILURE: fopen()"); - } - std::fclose(f); -} - -void FuzzTestDispatcherEmitSeed(const void* data, size_t size) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestGetSeeds && - in_test_callback, - "must be called inside test callback for getting seeds"); - DispatcherCheck(size > 0 && data != nullptr, - "seed must be non-empty with a valid pointer"); - static size_t seed_index = 0; - static const char* output_dir = [] { - const auto flag_value = - GetDispatcherFlag(kDispatcherTestGetSeedsOutputDirFlagHeader); - DispatcherCheck(flag_value.has_value(), - "seeds output path must be specified"); - const char* result = strndup(flag_value->data(), flag_value->size()); - DispatcherCheck(result != nullptr, "failed to copy the seeds output path"); - return result; - }(); - // Cap seed index within 9 digits. If this was triggered, the dumping would - // take forever.. - if (seed_index >= 1000000000) return; - char seed_path_buf[PATH_MAX]; - const size_t num_path_chars = - snprintf(seed_path_buf, PATH_MAX, "%s/%09lu", output_dir, seed_index); - DispatcherCheck(num_path_chars < PATH_MAX, "seed path reaches PATH_MAX"); - FILE* output_file = fopen(seed_path_buf, "w"); - const size_t num_bytes_written = fwrite(data, 1, size, output_file); - DispatcherCheck(num_bytes_written == size, - "wrong number of bytes written for seed"); - fclose(output_file); - ++seed_index; -} - -void FuzzTestDispatcherEmitMutant(const void* data, size_t size) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestMutate && - in_test_callback, - "must be called inside test callback for mutating"); - DispatcherCheck(size > 0 && data != nullptr, - "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(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, - size_t num_features) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestExecute && - in_test_callback, - "must be called inside test callback of executing"); - DispatcherCheck(num_features > 0 && features != nullptr, - "feature array must be non-empty with a valid pointer"); - auto* output = GetOutputsBlobSequence(); - DispatcherCheck(output != nullptr, "outputs blob sequence must exist"); - DispatcherCheck(BatchResult::WriteDispatcher32BitFeatures( - features, num_features, *output), - "failed to write feedback"); -} - -void FuzzTestDispatcherEmitFeedbackAsRawFeatures(const uint64_t* features, - size_t num_features) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestExecute && - in_test_callback, - "must be called inside test callback of executing"); - DispatcherCheck(num_features > 0 && features != nullptr, - "feature array must be non-empty with a valid pointer"); - auto* output = GetOutputsBlobSequence(); - DispatcherCheck(output != nullptr, "outputs blob sequence must exist"); - DispatcherCheck( - BatchResult::WriteOneFeatureVec(features, num_features, *output), - "failed to write feedback"); -} - -void FuzzTestDispatcherEmitExecutionMetadata(const void* metadata, - size_t size) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestExecute && - in_test_callback, - "must be called inside test callback of executing"); - DispatcherCheck(size > 0 && metadata != nullptr, - "metadata must be non-empty with a valid pointer"); - auto* output = GetOutputsBlobSequence(); - DispatcherCheck(output != nullptr, "outputs blob sequence must exist"); - DispatcherCheck(BatchResult::WriteMetadata( - {static_cast(metadata), size}, *output), - "failed to write metadata"); -} - -void FuzzTestDispatcherEmitInputFailure(const char* description, - const void* signature, - size_t signature_size) { - DispatcherCheck(GetDispatcherAction() == DispatcherAction::kTestExecute && - in_test_callback, - "must be called inside test callback for executing"); - DispatcherCheck((signature == nullptr) == (signature_size == 0), - "violated invariant: signature should be nullptr if and only " - "if signature_size is 0"); - DispatcherEmitFailure( - "INPUT FAILURE: ", description != nullptr ? description : "", - reinterpret_cast(signature), signature_size); -} - -void FuzzTestDispatcherEmitIgnoredFailure(const char* description) { - DispatcherEmitFailure( - "IGNORED FAILURE: ", description != nullptr ? description : "", - /*signature=*/nullptr, /*signature_size=*/0); -} - -void FuzzTestDispatcherEmitSetupFailure(const char* description) { - DispatcherEmitFailure( - "SETUP FAILURE: ", description != nullptr ? description : "", - /*signature=*/nullptr, /*signature_size=*/0); -} - -void FuzzTestDispatcherEmitSkippedTestFailure(const char* description) { - DispatcherEmitFailure( - "SKIPPED TEST: ", description != nullptr ? description : "", - /*signature=*/nullptr, /*signature_size=*/0); -} diff --git a/centipede/dispatcher.h b/centipede/dispatcher.h deleted file mode 100644 index 536f443c7..000000000 --- a/centipede/dispatcher.h +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2025 The Centipede Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef THIRD_PARTY_CENTIPEDE_DISPATCHER_H_ -#define THIRD_PARTY_CENTIPEDE_DISPATCHER_H_ - -// Dispatcher interface. -// -// This header needs to be C compatible. - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Inputs to perform mutations. -struct FuzzTestDispatcherInputForMutate { - const void* input; - size_t input_size; - const void* metadata; - size_t metadata_size; -}; - -// Callbacks to be provided by the fuzz testing framework to -// `FuzzTestDispatcherRun`. -struct FuzzTestDispatcherCallbacks { - // Optional callback to return an ID for the current binary. If not - // implemented, the controller will generate a default ID based on the binary - // path. - const char* (*get_binary_id)(); - // Callback to emit the list of available tests in the binary using - // `FuzzTestDispatcherEmitTestName`. - void (*list_tests)(); - // Callback to emit the seed inputs for a test using - // `FuzzTestDispatcherEmitSeed`. - void (*get_seeds)(); - // Optional callback to emit at most `num_mutants` from `inputs` with - // `num_inputs` entries using `FuzzTestDispatcherEmitMutant`. `shrink` != 0 - // means to generate smaller mutants than the inputs used for mutation. If not - // implemented, the controller will perform basic string-based mutations. - // - // TODO: xinhaoyuan - Reconsider mutation interface design instead of - // following the existing Centipede/runner protocol. - void (*mutate)(const struct FuzzTestDispatcherInputForMutate* inputs, - size_t num_inputs, size_t num_mutants, int shrink); - // Callback to execute `input` with `size` bytes. The callback should emit - // coverage feedback using `FuzzTestDispatcherEmitFeedback*` functions, and - // any metadata for further mutation using - // `FuzzTestDispatEmitExecutionMetadata`. In case the input caused a failure, - // the callback should emit the failure using - // `FuzzTestDispatcherEmitInputFailure`. - void (*execute)(const void* input, size_t size); -}; - -// Functions provided by the FuzzTest engine. - -// Returns 0 if the dispatcher mode is not enabled in the current process; 1 if -// the dispatcher mode is enabled; other values for unexpected errors. -int FuzzTestDispatcherIsEnabled(); - -// All functions below should be called only after `FuzzTestDispatcherIsEnabled` -// returns 1 in the current process. - -// Returns the test name under operation as an unowned, static, and -// null-terminated string. Returns nullptr if the current process is not -// operating on a specific test. -const char* FuzzTestDispatcherGetTestName(); - -// Give control to the FuzzTest engine to invoke `callbacks`. Returns an exit -// code for the current process desired by the engine. -int FuzzTestDispatcherRun(const struct FuzzTestDispatcherCallbacks* callbacks); - -// Emits a test name. Must be called from the `list_tests` callback. `name` must -// be a null-terminated string. -void FuzzTestDispatcherEmitTestName(const char* name); - -// Emits a seed input. Must be called from the `get_seeds` callback. `data` must -// not be nullptr and `size > 0` must hold. -void FuzzTestDispatcherEmitSeed(const void* data, size_t size); - -// Emits a mutant. Must be called from the `mutate` callback. `data` must not be -// nullptr and `size > 0` must hold. -void FuzzTestDispatcherEmitMutant(const void* data, size_t size); - -// TODO: b/437901326 - Unify the feedback emission interfaces. - -// Emits coverage feedback for the current input as an array of 32-bit features. -// -// For each 32-bit feature, the bit [31] is ignored; the 4 bits [30-27] -// indicate the feature domain for engine prioritization. The remaining 27 bits -// [26-0] represent the actual 27-bit feature ID in the domain. -// -// Must be called from the `execute` callback. `features` must not be nullptr -// and `num_features > 0` must hold. -void FuzzTestDispatcherEmitFeedbackAs32BitFeatures(const uint32_t* features, - size_t num_features); - -// Must only pass here the "raw" features exposed by the sancov runtime. -// -// Must be called from the `execute` callback. `features` must not be nullptr -// and `num_features > 0` must hold. -void FuzzTestDispatcherEmitFeedbackAsRawFeatures(const uint64_t* features, - size_t num_features); - -// Emits metadata of the current input as raw bytes. Must be called from -// the `execute` callback. -void FuzzTestDispatcherEmitExecutionMetadata(const void* metadata, size_t size); - -// Functions for emitting various types of failures. After calling any of these -// functions, later calls of these functions would have no effect, and the -// current process should exit after necessary cleanup. - -// Emits a failure caused by executing an input. Must be called within the -// `execute` callback. `description` should be a null-terminated string, or -// nullptr can be passed for an empty string; `signature` should be nullptr if -// and only if `signature_size == 0`. -void FuzzTestDispatcherEmitInputFailure(const char* description, - const void* signature, - size_t signature_size); - -// Emits a failure that should be ignored (i.e. not affecting the fuzzing -// workflows). `description` should be a null-terminated string, or nullptr can -// be passed for an empty string. -void FuzzTestDispatcherEmitIgnoredFailure(const char* description); - -// Emits a failure caused by the test setup. `description` should be a -// null-terminated string, or nullptr can be passed for an empty string. -void FuzzTestDispatcherEmitSetupFailure(const char* description); - -// Emits a failure due to reasons to skip the entire test. `description` should -// be a null-terminated string, or nullptr can be passed for an empty string. -void FuzzTestDispatcherEmitSkippedTestFailure(const char* description); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/centipede/mutation_data.h b/centipede/mutation_data.h index 4dc0ee5e1..4b388b4e6 100644 --- a/centipede/mutation_data.h +++ b/centipede/mutation_data.h @@ -66,8 +66,7 @@ inline bool operator==(const Mutant& mutant, const Mutant& other) { } // A reference counterpart of `Mutant`. Needed because it can be constructed -// from std::string and/or by the C-only dispatcher without copying the -// underlying data. +// from std::string without copying the underlying data. struct MutantRef { MutantRef() = default; diff --git a/centipede/runner_result.cc b/centipede/runner_result.cc index 07b781024..5d447d9f3 100644 --- a/centipede/runner_result.cc +++ b/centipede/runner_result.cc @@ -37,7 +37,6 @@ enum Tags : Blob::SizeAndTagT { // Execution result tags. kTagFeatures, - kTagDispatcher32BitFeatures, kTagInputBegin, kTagInputEnd, kTagStats, @@ -57,14 +56,6 @@ bool BatchResult::WriteOneFeatureVec(const feature_t *vec, size_t size, reinterpret_cast(vec)}); } -bool BatchResult::WriteDispatcher32BitFeatures(const uint32_t *features, - size_t num_features, - BlobSequence &blobseq) { - return blobseq.Write({kTagDispatcher32BitFeatures, - num_features * sizeof(features[0]), - reinterpret_cast(features)}); -} - bool BatchResult::WriteInputBegin(BlobSequence &blobseq) { return blobseq.Write({kTagInputBegin, 0, nullptr}); } @@ -135,19 +126,6 @@ bool BatchResult::Read(BlobSequence &blobseq) { std::memcpy(features.data(), blob.data, features_size * sizeof(feature_t)); } - if (blob.tag == kTagDispatcher32BitFeatures) { - if (current_execution_result == nullptr) return false; - const size_t size = blob.size / sizeof(uint32_t); - std::vector copied_features; - copied_features.resize(size); - std::memcpy(copied_features.data(), blob.data, size * sizeof(uint32_t)); - auto &features = current_execution_result->mutable_features(); - features.reserve(features.size() + size); - for (uint32_t feature : copied_features) { - features.push_back((feature & 0x7fffffff) + - feature_domains::kUserDomains[0].begin()); - } - } } num_outputs_read_ = num_ends; return true; diff --git a/centipede/runner_result.h b/centipede/runner_result.h index 8266103a6..c95f02040 100644 --- a/centipede/runner_result.h +++ b/centipede/runner_result.h @@ -122,17 +122,6 @@ class BatchResult { // When executing N inputs, the runner will call this at most N times. static bool WriteOneFeatureVec(const feature_t* vec, size_t size, BlobSequence& blobseq); - // Writes a buffer of 32-bit `features` to `blobseq`. - // - // This is a temporary API to work with the dispatcher prototype. - // - // For each 32-bit feature, the bit [31] is ignored; the 4 bits [30-27] - // indicate the domain, which are mapped to the Centipede user-defined domain - // 0-15; the remaining 27 bits [26-0] represent the actual 27-bit feature ID - // in the domain. - static bool WriteDispatcher32BitFeatures(const uint32_t* features, - size_t num_features, - BlobSequence& blobseq); // Writes a special Begin marker before executing an input. static bool WriteInputBegin(BlobSequence& blobseq); // Writes a special End marker after executing an input. diff --git a/centipede/runner_result_test.cc b/centipede/runner_result_test.cc index 20ce43345..f8c86cb33 100644 --- a/centipede/runner_result_test.cc +++ b/centipede/runner_result_test.cc @@ -249,31 +249,6 @@ TEST(ExecutionResult, ReadResultSucceedsOnlyWithInputBegin) { EXPECT_FALSE(batch_result.Read(blobseq)); } -TEST(ExecutionResult, ReadDispatcher32BitFeatures) { - auto buffer = std::make_unique(1000); - BlobSequence blobseq(buffer.get(), 1000); - BatchResult batch_result; - - std::vector dispatcher_features = {0, 1, 0x7fffffff, 0xffffffff}; - - EXPECT_TRUE(BatchResult::WriteInputBegin(blobseq)); - EXPECT_TRUE(BatchResult::WriteDispatcher32BitFeatures( - dispatcher_features.data(), dispatcher_features.size(), blobseq)); - EXPECT_TRUE(BatchResult::WriteInputEnd(blobseq)); - blobseq.Reset(); - batch_result.ClearAndResize(1); - EXPECT_TRUE(batch_result.Read(blobseq)); - - ASSERT_EQ(batch_result.num_outputs_read(), 1); - EXPECT_THAT(batch_result.results()[0].features(), - ElementsAre(feature_domains::kUserDomains[0].ConvertToMe(0), - feature_domains::kUserDomains[0].ConvertToMe(1), - feature_domains::kUserDomains[15].ConvertToMe( - feature_domains::Domain::kDomainSize - 1), - feature_domains::kUserDomains[15].ConvertToMe( - feature_domains::Domain::kDomainSize - 1))); -} - TEST(ExecutionResult, KeepArbitraryBytesFromMetadata) { auto buffer = std::make_unique(1000); BlobSequence blobseq(buffer.get(), 1000); diff --git a/centipede/runner_utils.h b/centipede/runner_utils.h index 1722bfa6f..4b85afa6d 100644 --- a/centipede/runner_utils.h +++ b/centipede/runner_utils.h @@ -76,7 +76,7 @@ extern "C" void __lsan_register_root_region(const void* p, size_t size); extern "C" void __lsan_unregister_root_region(const void* p, size_t size); // Wraps an object of `T` stored as a plain byte array with explicit -// construction/destruction. Needed for runner/dispatcher related global states +// construction/destruction. Needed for runner related global states // that need extended lifetime. (Alternatively we could using dynamic pointers // for them, but that would introduce extra pointer check/dereference on every // use.)