No public description PiperOrigin-RevId: 943408631
diff --git a/centipede/BUILD b/centipede/BUILD index 3392294..607811f 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -979,6 +979,7 @@ name = "engine_worker", srcs = [ "engine_worker.cc", + "runner_utils.cc", "runner_utils.h", ], hdrs = ["engine_worker_abi.h"], @@ -1956,6 +1957,7 @@ timeout = "long", srcs = ["centipede_test.cc"], data = [ + "@com_google_fuzztest//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", @@ -1963,6 +1965,7 @@ "@com_google_fuzztest//centipede/testing:fuzz_target_with_custom_mutator", "@com_google_fuzztest//centipede/testing:hanging_fuzz_target", "@com_google_fuzztest//centipede/testing:seeded_fuzz_target", + "@com_google_fuzztest//centipede/testing:test_binary_for_engine_testing", "@com_google_fuzztest//centipede/testing:test_fuzz_target", "@com_google_fuzztest//centipede/testing:test_input_filter", ],
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 7a7f1d7..17383e8 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -1391,5 +1391,44 @@ HasSubstr("Mutate() succeeded"))); } +TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInWorkerMode) { + TempCorpusDir tmp_dir{test_info_->name()}; + Environment env; + env.workdir = tmp_dir.path(); + env.binary = GetDataDependencyFilepath( + "centipede/testing/test_binary_for_engine_testing"); + env.test_name = "some_test"; + env.populate_binary_info = false; + env.fork_server = false; + env.persistent_mode = false; + env.exit_on_crash = true; + fuzztest::internal::DefaultCallbacksFactory< + fuzztest::internal::CentipedeDefaultCallbacks> + callbacks; + EXPECT_DEATH( + [&] { std::exit(CentipedeMain(env, callbacks)); }(), + ContainsRegex("Failure *: INPUT FAILURE: some_failure_description")); +} + +TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInStandaloneMode) { + const std::string centipede_path = + GetDataDependencyFilepath("centipede/centipede"); + const std::string test_binary_path = GetDataDependencyFilepath( + "centipede/testing/test_binary_for_engine_testing"); + // Create a temporary dir and enter it for running the test binary, because + // the test binary uses CWD as the engine workdir. + TempCorpusDir tmp_dir{test_info_->name()}; + const auto test_command = + absl::StrCat("cd ", tmp_dir.path().string(), + " && env FUZZTEST_CENTIPEDE_BINARY_PATH=", centipede_path, + " ", test_binary_path); + EXPECT_DEATH( + [&] { + const int status = std::system(test_command.c_str()); + std::exit(WEXITSTATUS(status)); + }(), + ContainsRegex("Failure *: INPUT FAILURE: some_failure_description")); +} + } // namespace } // namespace fuzztest::internal
diff --git a/centipede/runner_utils.cc b/centipede/runner_utils.cc index 8d14650..8010574 100644 --- a/centipede/runner_utils.cc +++ b/centipede/runner_utils.cc
@@ -26,6 +26,12 @@ namespace fuzztest::internal { +// Weak dummy definition of LSan interface in case LSan is missing. +__attribute__((weak)) void __lsan_register_root_region(const void* p, + size_t size) {} +__attribute__((weak)) void __lsan_unregister_root_region(const void* p, + size_t size) {} + void PrintErrorAndExitIf(bool condition, const char* absl_nonnull error) { if (!condition) return; fprintf(stderr, "error: %s\n", error);
diff --git a/centipede/runner_utils.h b/centipede/runner_utils.h index 71b3005..1722bfa 100644 --- a/centipede/runner_utils.h +++ b/centipede/runner_utils.h
@@ -71,10 +71,9 @@ // written, false otherwise due to errors. bool WriteAll(int fd, const char* data, size_t size); -extern "C" void __lsan_register_root_region(const void* p, size_t size) - __attribute__((weak)); -extern "C" void __lsan_unregister_root_region(const void* p, size_t size) - __attribute__((weak)); +// Leak sanitizer interface functions. +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 @@ -107,18 +106,14 @@ new (&space_) T(std::forward<Args>(args)...); // Needed otherwise lsan may lose track of the pointers inside the object as // it is in-place constructed from the byte array. - if (__lsan_register_root_region) { - __lsan_register_root_region(&space_, sizeof(space_)); - } + __lsan_register_root_region(&space_, sizeof(space_)); } // Destructs the actual object (without reclaiming the space). It can only be // called at most once after recent `Construct()`. void Destruct() { get()->~T(); - if (__lsan_unregister_root_region) { - __lsan_unregister_root_region(&space_, sizeof(space_)); - } + __lsan_unregister_root_region(&space_, sizeof(space_)); } // Gets the pointer to the actual object backed by a plain byte array. Using
diff --git a/centipede/testing/BUILD b/centipede/testing/BUILD index e40ae58..1ddf5e5 100644 --- a/centipede/testing/BUILD +++ b/centipede/testing/BUILD
@@ -497,3 +497,14 @@ "@com_google_fuzztest//centipede:test_util_sh", ], ) + +cc_binary( + name = "test_binary_for_engine_testing", + srcs = ["test_binary_for_engine_testing.cc"], + deps = [ + "@abseil-cpp//absl/strings", + "@com_google_fuzztest//centipede:engine_abi", + "@com_google_fuzztest//centipede:engine_controller_with_subprocess", + "@com_google_fuzztest//centipede:engine_worker", + ], +)
diff --git a/centipede/testing/test_binary_for_engine_testing.cc b/centipede/testing/test_binary_for_engine_testing.cc new file mode 100644 index 0000000..75c1a42 --- /dev/null +++ b/centipede/testing/test_binary_for_engine_testing.cc
@@ -0,0 +1,212 @@ +// Copyright 2026 The FuzzTest 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 <cstdint> +#include <cstdlib> +#include <string> +#include <string_view> +#include <vector> + +#include "absl/strings/str_cat.h" +#include "./centipede/engine_abi.h" +#include "./centipede/engine_controller_abi.h" +#include "./centipede/engine_worker_abi.h" + +namespace { + +FuzzTestBytesView ToBytesView(std::string_view sv) { + return {reinterpret_cast<const uint8_t*>(sv.data()), sv.size()}; +} + +void SetUpCoverageDomains(FuzzTestAdapterCtx* ctx, + const FuzzTestCoverageDomainRegistry* registry) { + static constexpr std::string_view kDomainName = "base"; + const FuzzTestCoverageDomain domain = { + /*domain_id=*/0, + /*name=*/ToBytesView(kDomainName), + /*feature_id_bit_size=*/27, + /*counter_bit_size=*/0, + }; + registry->Register(registry->ctx, &domain); +} + +struct AdapterCtx { + const FuzzTestDiagnosticSink* diagnostic_sink; +}; + +struct TestInput { + std::string content; +}; + +void EmitInput(std::string_view input_content, const FuzzTestInputSink* sink) { + sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>( + new TestInput{std::string{input_content}})); +} + +void GetRandomSeedInput(FuzzTestAdapterCtx* ctx, + const FuzzTestInputSink* sink) { + EmitInput("random_seed", sink); +} + +void Mutate(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle input, int shrink, + const FuzzTestInputSink* sink) { + const auto* input_object = reinterpret_cast<TestInput*>(input); + if (input_object->content == "random_seed") { + EmitInput("mutant_1", sink); + } else if (input_object->content == "mutant_1") { + EmitInput("mutant_2", sink); + } else if (input_object->content == "mutant_2") { + EmitInput("mutant_3", sink); + } else { + EmitInput("bad_input", sink); + } +} + +void AddFeature(uint32_t domain, uint32_t feature, uint32_t counter, + std::vector<uint64_t>& out) { + out.push_back((static_cast<uint64_t>(domain) << 59) | + (static_cast<uint64_t>(feature) << 32) | counter); +} + +void Execute(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle input, + const FuzzTestFeedbackSink* sink) { + std::vector<uint64_t> features; + auto* adapter_ctx = reinterpret_cast<AdapterCtx*>(ctx); + const auto* input_object = reinterpret_cast<TestInput*>(input); + if (input_object->content == "random_seed") { + AddFeature(0, 0, 0, features); + AddFeature(0, 1, 0, features); + AddFeature(0, 2, 0, features); + } else if (input_object->content == "mutant_1") { + AddFeature(0, 3, 0, features); + AddFeature(0, 4, 0, features); + } else if (input_object->content == "mutant_2") { + AddFeature(0, 5, 0, features); + } else if (input_object->content == "mutant_3") { + static constexpr std::string_view kDescription = "some_failure_description"; + static constexpr std::string_view kSignature = "some_signature"; + const auto description_view = ToBytesView(kDescription); + const auto signature_view = ToBytesView(kSignature); + adapter_ctx->diagnostic_sink->EmitFinding( + adapter_ctx->diagnostic_sink->ctx, &description_view, &signature_view); + } else { + static constexpr std::string_view kDescription = + "some_other_failure_description"; + static constexpr std::string_view kSignature = "some_other_signature"; + const auto description_view = ToBytesView(kDescription); + const auto signature_view = ToBytesView(kSignature); + adapter_ctx->diagnostic_sink->EmitFinding( + adapter_ctx->diagnostic_sink->ctx, &description_view, &signature_view); + } + const auto features_view = FuzzTestUint64sView{ + features.data(), + features.size(), + }; + sink->EmitCoverageFeatures(sink->ctx, &features_view); +} + +void DeserializeInputContent(FuzzTestAdapterCtx* ctx, + const FuzzTestBytesView* content, + const FuzzTestInputSink* sink) { + auto* input = new TestInput{ + std::string{reinterpret_cast<const char*>(content->data), content->size}}; + sink->Emit(sink->ctx, reinterpret_cast<FuzzTestInputHandle>(input)); +} + +void SerializeInputContent(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle input, + const FuzzTestBytesSink* sink) { + auto* input_object = reinterpret_cast<TestInput*>(input); + const FuzzTestBytesView bytes = { + /*data=*/reinterpret_cast<const uint8_t*>(input_object->content.data()), + /*size=*/input_object->content.size(), + }; + sink->Emit(sink->ctx, &bytes); +} + +void FreeInput(FuzzTestAdapterCtx* ctx, FuzzTestInputHandle input) { + delete reinterpret_cast<TestInput*>(input); +} + +void FreeCtx(FuzzTestAdapterCtx* ctx) { + delete reinterpret_cast<AdapterCtx*>(ctx); +} + +void ConstructAdapter(const FuzzTestDiagnosticSink* sink, + FuzzTestAdapter* adapter_out) { + adapter_out->ctx = + reinterpret_cast<FuzzTestAdapterCtx*>(new AdapterCtx{sink}); + adapter_out->SetUpCoverageDomains = SetUpCoverageDomains; + adapter_out->GetRandomSeedInput = GetRandomSeedInput; + adapter_out->Mutate = Mutate; + adapter_out->Execute = Execute; + adapter_out->DeserializeInputContent = DeserializeInputContent; + adapter_out->SerializeInputContent = SerializeInputContent; + adapter_out->FreeInput = FreeInput; + adapter_out->FreeCtx = FreeCtx; +} + +FuzzTestControllerStatus ControllerRun(const FuzzTestAdapterManager* manager, + const std::vector<std::string>& flags) { + std::vector<FuzzTestBytesView> flags_bytes_view_list; + flags_bytes_view_list.reserve(flags.size()); + for (const auto& flag : flags) { + flags_bytes_view_list.push_back(FuzzTestBytesView{ + /*data=*/reinterpret_cast<const uint8_t*>(flag.data()), + /*size=*/flag.size(), + }); + } + const FuzzTestBytesViews flags_bytes_views = { + /*views=*/flags_bytes_view_list.data(), + /*count=*/flags_bytes_view_list.size(), + }; + return FuzzTestControllerRun(manager, &flags_bytes_views); +} + +} // namespace + +int main(int argc, char** argv) { + FuzzTestAdapterManager manager = { + /*ctx=*/nullptr, + /*GetBinaryId=*/ + [](FuzzTestAdapterManagerCtx* ctx, const FuzzTestBytesSink* sink) { + static constexpr std::string_view kBinaryId = "some_binary_id"; + const auto bytes = ToBytesView(kBinaryId); + sink->Emit(sink->ctx, &bytes); + }, + /*GetTestName=*/ + [](FuzzTestAdapterManagerCtx* ctx, const FuzzTestBytesSink* sink) { + static constexpr std::string_view kTestName = "some_test"; + const auto bytes = ToBytesView(kTestName); + sink->Emit(sink->ctx, &bytes); + }, + /*ConstructAdapter=*/ + [](FuzzTestAdapterManagerCtx* ctx, + const FuzzTestDiagnosticSink* diagnostic_sink, + FuzzTestAdapter* adapter_out) { + ConstructAdapter(diagnostic_sink, adapter_out); + }, + }; + if (const auto worker_status = FuzzTestWorkerMaybeRun(&manager); + worker_status != kFuzzTestWorkerNotRequired) { + return worker_status == kFuzzTestWorkerSuccess ? EXIT_SUCCESS + : EXIT_FAILURE; + } + return ControllerRun(&manager, {absl::StrCat("--binary=", argv[0]), + "--test_name=some_test", + "--populate_binary_info=0", "--fork_server=0", + "--persistent_mode=0", "--exit_on_crash"}) == + kFuzzTestControllerSuccess + ? EXIT_SUCCESS + : EXIT_FAILURE; +}