Enable sanitizer reporting and death callback for ThreadSanitizer in FuzzTest.

PiperOrigin-RevId: 968654154
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc
index f03dbaf..a70ddb8 100644
--- a/fuzztest/internal/runtime.cc
+++ b/fuzztest/internal/runtime.cc
@@ -71,7 +71,8 @@
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/status.h"
 
-#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
+#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
+    defined(THREAD_SANITIZER)
 #define FUZZTEST_HAS_SANITIZER
 #include <sanitizer/common_interface_defs.h>
 
diff --git a/fuzztest/internal/sanitizer_interface.cc b/fuzztest/internal/sanitizer_interface.cc
index 3a81d10..4af138d 100644
--- a/fuzztest/internal/sanitizer_interface.cc
+++ b/fuzztest/internal/sanitizer_interface.cc
@@ -15,15 +15,65 @@
 #include "./fuzztest/internal/sanitizer_interface.h"
 
 #include <cstddef>
+#include <optional>
 #include <string>
 
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
+#include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/strings/strip.h"
 
 namespace fuzztest::internal {
+namespace {
+
+std::optional<std::string> MaybeExtractTsanCrashType(
+    absl::string_view sanitizer_name, absl::string_view error_summary) {
+  if (sanitizer_name != "ThreadSanitizer") return std::nullopt;
+
+  if (absl::StartsWith(error_summary, "data race") ||
+      absl::StartsWith(error_summary, "race on") ||
+      absl::StartsWith(error_summary, "Swift access race")) {
+    return "data-race";
+  }
+  if (absl::StartsWith(error_summary, "destroy of a locked mutex")) {
+    return "destroy-locked-mutex";
+  }
+  if (absl::StartsWith(error_summary, "double lock of a mutex")) {
+    return "double-lock-of-mutex";
+  }
+  if (absl::StartsWith(error_summary, "lock-order-inversion")) {
+    return "lock-order-inversion";
+  }
+  if (absl::StartsWith(error_summary, "mutex held in the wrong context")) {
+    return "mutex-held-in-wrong-context";
+  }
+  if (absl::StartsWith(error_summary, "read lock of a write locked mutex")) {
+    return "read-lock-of-write-locked-mutex";
+  }
+  if (absl::StartsWith(error_summary, "read unlock of a write locked mutex")) {
+    return "read-unlock-of-write-locked-mutex";
+  }
+  if (absl::StartsWith(error_summary, "signal handler spoils errno")) {
+    return "signal-handler-spoils-errno";
+  }
+  if (absl::StartsWith(error_summary, "signal-unsafe call")) {
+    return "signal-unsafe-call-inside-of-a-signal";
+  }
+  if (absl::StartsWith(error_summary, "thread leak")) {
+    return "thread-leak";
+  }
+  if (absl::StartsWith(error_summary, "unlock of an unlocked mutex")) {
+    return "unlock-unlocked-mutex";
+  }
+  if (absl::StartsWith(error_summary, "use of an invalid mutex")) {
+    return "use-invalid-mutex";
+  }
+  return std::nullopt;
+}
+
+}  // namespace
 
 absl::StatusOr<std::string> ParseCrashTypeFromSanitizerSummary(
     absl::string_view error_summary) {
@@ -37,12 +87,18 @@
         "No `: ` following the sanitizer name in sanitizer error summary: ",
         error_summary));
   }
+  const absl::string_view sanitizer_name = error_summary.substr(0, colon_pos);
   error_summary.remove_prefix(colon_pos + 2);
   // Explicitly handle summaries like:
   // SUMMARY: AddressSanitizer: 1000 byte(s) leaked in 1000 allocation(s).
   if (error_summary.find("byte(s) leaked") != error_summary.npos) {
     return "memory-leak";
   }
+  if (auto tsan_crash_type =
+          MaybeExtractTsanCrashType(sanitizer_name, error_summary);
+      tsan_crash_type.has_value()) {
+    return *tsan_crash_type;
+  }
   const size_t space_pos = error_summary.find(' ');
   return std::string(error_summary.substr(0, space_pos));
 }
diff --git a/fuzztest/internal/sanitizer_interface_test.cc b/fuzztest/internal/sanitizer_interface_test.cc
index 44b60a2..0cf5c22 100644
--- a/fuzztest/internal/sanitizer_interface_test.cc
+++ b/fuzztest/internal/sanitizer_interface_test.cc
@@ -70,6 +70,182 @@
   EXPECT_EQ(*crash_type, "use-of-uninitialized-value");
 }
 
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanDataRaceOnVptr) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: data race on vptr (ctor/dtor vs virtual "
+          "call) some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "data-race");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest, ExtractsCrashTypeForTSanDataRace) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: data race "
+          "some/file.cc:33:37 in operator()");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "data-race");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanDestroyLocked) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: destroy of a locked mutex "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "destroy-locked-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanDoubleLock) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: double lock of a mutex some/file.cc:12:34 "
+          "in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "double-lock-of-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest, ExtractsCrashTypeForTSanDeadlock) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: lock-order-inversion (potential "
+          "deadlock) some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "lock-order-inversion");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanMutexHeldWrongContext) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: mutex held in the wrong context "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "mutex-held-in-wrong-context");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanExternalRace) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: race on external object "
+          "some/file.cc:12:34 "
+          "in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "data-race");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanBadReadLock) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: read lock of a write locked mutex "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "read-lock-of-write-locked-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanBadReadUnlock) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: read unlock of a write locked mutex "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "read-unlock-of-write-locked-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanErrnoInSignal) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: signal handler spoils errno "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "signal-handler-spoils-errno");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanSignalUnsafe) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: signal-unsafe call inside of a signal "
+          "some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "signal-unsafe-call-inside-of-a-signal");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanSwiftAccessRace) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: Swift access race some/file.cc:12:34 in "
+          "Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "data-race");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanThreadLeak) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: thread leak some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "thread-leak");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanBadUnlock) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: unlock of an unlocked mutex (or by a "
+          "wrong thread) some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "unlock-unlocked-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanInvalidMutex) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: use of an invalid mutex (e.g. "
+          "uninitialized or destroyed) some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "use-invalid-mutex");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanHeapUseAfterFree) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: heap-use-after-free some/file.cc:12:34 "
+          "in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "heap-use-after-free");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest,
+     ExtractsCrashTypeForTSanFallbackSingleToken) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: ThreadSanitizer: unknown-crash-type some/file.cc:12:34 "
+          "in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "unknown-crash-type");
+}
+
+TEST(ParseCrashTypeFromSanitizerSummaryTest, IgnoresTsanCrashTypeForNonTSan) {
+  const absl::StatusOr<std::string> crash_type =
+      ParseCrashTypeFromSanitizerSummary(
+          "SUMMARY: AddressSanitizer: data race some/file.cc:12:34 in Foo");
+  ASSERT_TRUE(crash_type.ok());
+  EXPECT_EQ(*crash_type, "data");
+}
+
 TEST(ParseCrashTypeFromSanitizerSummaryTest, FailsOnMissingSummaryPrefix) {
   const absl::StatusOr<std::string> crash_type =
       ParseCrashTypeFromSanitizerSummary("Missing SUMMARY prefix");