Add the ability to set an external crash reporter for crash summaries.

PiperOrigin-RevId: 777628969
diff --git a/centipede/BUILD b/centipede/BUILD
index c43809e..cbc0df6 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -847,7 +847,9 @@
         "@com_google_fuzztest//common:remote_file",
         "@com_google_fuzztest//common:status_macros",
         "@com_google_fuzztest//fuzztest/internal:configuration",
-    ],
+    ] + select({
+        "//conditions:default": [],
+    }),
 )
 
 cc_library(
@@ -1858,6 +1860,7 @@
     srcs = ["crash_summary_test.cc"],
     deps = [
         ":crash_summary",
+        "@abseil-cpp//absl/log:check",
         "@googletest//:gtest_main",
     ],
 )
diff --git a/centipede/crash_summary.cc b/centipede/crash_summary.cc
index 8dbf313..6be9368 100644
--- a/centipede/crash_summary.cc
+++ b/centipede/crash_summary.cc
@@ -21,12 +21,20 @@
 #include "./common/defs.h"
 
 namespace fuzztest::internal {
+namespace {
+
+ExternalCrashReporter external_crash_reporter = nullptr;
+
+}  // namespace
 
 void CrashSummary::AddCrash(Crash crash) {
   crashes_.push_back(std::move(crash));
 }
 
 void CrashSummary::Report(absl::FormatRawSink sink) const {
+  if (external_crash_reporter != nullptr) {
+    external_crash_reporter(*this);
+  }
   absl::Format(sink, "=== Summary of detected crashes ===\n\n");
   absl::Format(sink, "Binary ID    : %s\n", binary_id());
   absl::Format(sink, "Fuzz test    : %s\n", fuzz_test());
@@ -43,4 +51,8 @@
   absl::Format(sink, "=== End of summary of detected crashes ===\n\n");
 }
 
+void SetExternalCrashReporter(ExternalCrashReporter reporter) {
+  external_crash_reporter = reporter;
+}
+
 }  // namespace fuzztest::internal
diff --git a/centipede/crash_summary.h b/centipede/crash_summary.h
index b5c631d..067ed36 100644
--- a/centipede/crash_summary.h
+++ b/centipede/crash_summary.h
@@ -54,6 +54,8 @@
   void AddCrash(Crash crash);
 
   // Reports a summary of the crashes to `sink`.
+  // If an external crash reporter has been set with `SetExternalCrashReporter`,
+  // calls it with the stored crashes.
   void Report(absl::FormatRawSink sink) const;
 
   std::string_view binary_id() const { return binary_id_; }
@@ -71,6 +73,12 @@
   std::vector<Crash> crashes_;
 };
 
+using ExternalCrashReporter = void (*)(const CrashSummary&);
+
+// Sets an external crash reporter that will be called when a `CrashSummary` is
+// reported.
+void SetExternalCrashReporter(ExternalCrashReporter reporter);
+
 }  // namespace fuzztest::internal
 
 #endif  // FUZZTEST_CENTIPEDE_CRASH_SUMMARY_H_
diff --git a/centipede/crash_summary_test.cc b/centipede/crash_summary_test.cc
index 1656a42..891bd5c 100644
--- a/centipede/crash_summary_test.cc
+++ b/centipede/crash_summary_test.cc
@@ -19,14 +19,36 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/log/check.h"
 
 namespace fuzztest::internal {
 namespace {
 
 using ::testing::AllOf;
 using ::testing::HasSubstr;
+using ::testing::Pointee;
 
-TEST(CrashSummaryTest, ReportPrintsSummary) {
+class CrashSummaryTest : public testing::Test {
+ public:
+  ~CrashSummaryTest() {
+    if (dumped_summary_ != nullptr) {
+      delete dumped_summary_;
+      dumped_summary_ = nullptr;
+    }
+  }
+
+ protected:
+  static void DumpCrashSummary(const CrashSummary& summary) {
+    CHECK(dumped_summary_ == nullptr);
+    dumped_summary_ = new CrashSummary{summary};
+  };
+
+  static CrashSummary* dumped_summary_;
+};
+
+CrashSummary* CrashSummaryTest::dumped_summary_ = nullptr;
+
+TEST_F(CrashSummaryTest, ReportPrintsSummary) {
   CrashSummary summary("binary_id", "fuzz_test");
   summary.AddCrash({"id1", "category1", "signature1", "description1"});
   summary.AddCrash({"id2", "category2",
@@ -50,5 +72,16 @@
             HasSubstr("Description: description2")));
 }
 
+TEST_F(CrashSummaryTest, ReportCallsExternalCrashReporter) {
+  CrashSummary summary("binary_id", "fuzz_test");
+  summary.AddCrash({"id1", "category1", "signature1", "description1"});
+  summary.AddCrash({"id2", "category2", "signature2", "description2"});
+  SetExternalCrashReporter(DumpCrashSummary);
+  std::string output;
+  summary.Report(&output);
+
+  EXPECT_THAT(dumped_summary_, Pointee(summary));
+}
+
 }  // namespace
 }  // namespace fuzztest::internal