From 303e8b26869ba5d94b64ac315af03ea5c7a91359 Mon Sep 17 00:00:00 2001 From: Daniel Lowengrub Date: Fri, 21 Aug 2026 13:13:16 -0700 Subject: [PATCH] Enable sanitizer reporting and death callback for ThreadSanitizer in FuzzTest. PiperOrigin-RevId: 968654154 --- fuzztest/internal/runtime.cc | 3 +- fuzztest/internal/sanitizer_interface.cc | 56 ++++++ fuzztest/internal/sanitizer_interface_test.cc | 176 ++++++++++++++++++ 3 files changed, 234 insertions(+), 1 deletion(-) diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc index f03dbaf18..a70ddb8b8 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 diff --git a/fuzztest/internal/sanitizer_interface.cc b/fuzztest/internal/sanitizer_interface.cc index 3a81d10a3..4af138d7b 100644 --- a/fuzztest/internal/sanitizer_interface.cc +++ b/fuzztest/internal/sanitizer_interface.cc @@ -15,15 +15,65 @@ #include "./fuzztest/internal/sanitizer_interface.h" #include +#include #include #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 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 ParseCrashTypeFromSanitizerSummary( absl::string_view error_summary) { @@ -37,12 +87,18 @@ absl::StatusOr ParseCrashTypeFromSanitizerSummary( "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 44b60a284..0cf5c22de 100644 --- a/fuzztest/internal/sanitizer_interface_test.cc +++ b/fuzztest/internal/sanitizer_interface_test.cc @@ -70,6 +70,182 @@ TEST(ParseCrashTypeFromSanitizerSummaryTest, ExtractsCrashTypeForMSan) { EXPECT_EQ(*crash_type, "use-of-uninitialized-value"); } +TEST(ParseCrashTypeFromSanitizerSummaryTest, + ExtractsCrashTypeForTSanDataRaceOnVptr) { + const absl::StatusOr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 crash_type = ParseCrashTypeFromSanitizerSummary("Missing SUMMARY prefix");