Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion fuzztest/internal/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
56 changes: 56 additions & 0 deletions fuzztest/internal/sanitizer_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -37,12 +87,18 @@ absl::StatusOr<std::string> 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));
}
Expand Down
176 changes: 176 additions & 0 deletions fuzztest/internal/sanitizer_interface_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,182 @@ TEST(ParseCrashTypeFromSanitizerSummaryTest, ExtractsCrashTypeForMSan) {
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");
Expand Down
Loading