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;
+}