[clang] [compiler-rt] [llvm] [tsan] Revive TSAN's standalone deadlock sanitizer implementation (PR #212748)
Naveen Seth Hanig via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 04:50:29 PDT 2026
https://github.com/naveen-seth updated https://github.com/llvm/llvm-project/pull/212748
>From 03407a0198cbd1b31aa725e7c7595b1d6282eb72 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Wed, 29 Jul 2026 13:20:20 +0200
Subject: [PATCH] [tsan] Revive TSAN's standalone deadlock sanitizer
implementation
This introduces a lightweight ThreadSanitizer submode that focuses exclusively
on lock correctness validation, enabled by `-fsanitize=thread-deadlock`.
The motivation behind this is to allow lock-order inversion and mutex misuse
detection without enabling data-race detection.
This is useful for:
- Programs that exceed regular TSan's shadow memory address space constraints
- Combining with other sanitizers incompatible with TSan
- Platforms where regular TSan is not supported
- Users who are only interested in lock-correctness but not in data-races.
This is implemented as a separate standalone runtime, as the regular TSan
runtime intertwines its lock tracking with SyncVar objects in shadow memory.
A follow up patch will remove the old tsan/dd, which was never wired to the
compiler and is effectively unmaintained/dead code.
This follows the RFC:
https://discourse.llvm.org/t/rfc-add-update-tsan-submode-for-lock-correctness-validation-only/91319?u=naveen-seth
---
clang/include/clang/Basic/Sanitizers.def | 3 +
clang/include/clang/Driver/SanitizerArgs.h | 3 +
clang/lib/CodeGen/BackendUtil.cpp | 9 +
clang/lib/Driver/SanitizerArgs.cpp | 4 +-
clang/lib/Driver/ToolChains/CommonArgs.cpp | 4 +
clang/lib/Driver/ToolChains/Linux.cpp | 1 +
.../cmake/Modules/AllSupportedArchDefs.cmake | 1 +
compiler-rt/cmake/config-ix.cmake | 10 +-
.../sanitizer_internal_defs.h | 3 +
compiler-rt/lib/tsan_deadlock/CMakeLists.txt | 76 ++++
.../tsan_deadlock_interceptors.cpp | 398 ++++++++++++++++++
.../tsan_deadlock/tsan_deadlock_interface.cpp | 36 ++
.../lib/tsan_deadlock/tsan_deadlock_rtl.cpp | 342 +++++++++++++++
.../lib/tsan_deadlock/tsan_deadlock_rtl.h | 102 +++++
compiler-rt/test/tsan_deadlock/CMakeLists.txt | 29 ++
compiler-rt/test/tsan_deadlock/lit.cfg.py | 52 +++
.../test/tsan_deadlock/lit.site.cfg.py.in | 12 +
.../test/tsan_deadlock/mutex_bad_unlock.cpp | 26 ++
.../test/tsan_deadlock/mutex_cycle2.cpp | 31 ++
.../test/tsan_deadlock/mutex_cycle_long.c | 45 ++
.../tsan_deadlock/mutex_destroy_locked.cpp | 20 +
.../tsan_deadlock/mutex_destroy_locked2.cpp | 27 ++
.../test/tsan_deadlock/mutex_double_lock.cpp | 17 +
.../Instrumentation/ThreadSanitizer.h | 16 +-
.../Instrumentation/ThreadSanitizer.cpp | 19 +-
25 files changed, 1276 insertions(+), 10 deletions(-)
create mode 100644 compiler-rt/lib/tsan_deadlock/CMakeLists.txt
create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
create mode 100644 compiler-rt/test/tsan_deadlock/CMakeLists.txt
create mode 100644 compiler-rt/test/tsan_deadlock/lit.cfg.py
create mode 100644 compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
create mode 100644 compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp
diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..fcb43dc31fcdc 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -79,6 +79,9 @@ SANITIZER("type", Type)
// ThreadSanitizer
SANITIZER("thread", Thread)
+// DeadlockSanitizer (ThreadSanitizer, with only the deadlock detection.)
+SANITIZER("thread-deadlock", ThreadDeadlock)
+
// Numerical stability sanitizer.
SANITIZER("numerical", NumericalStability)
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6a01b3e36d44c..7b7f738d91131 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -104,6 +104,9 @@ class SanitizerArgs {
}
bool needsTysanRt() const { return Sanitizers.has(SanitizerKind::Type); }
bool needsTsanRt() const { return Sanitizers.has(SanitizerKind::Thread); }
+ bool needsDeadlockRt() const {
+ return Sanitizers.has(SanitizerKind::ThreadDeadlock);
+ }
bool needsMsanRt() const { return Sanitizers.has(SanitizerKind::Memory); }
bool needsFuzzer() const { return Sanitizers.has(SanitizerKind::Fuzzer); }
bool needsLsanRt() const {
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 2b755fa916e55..ce9740554a348 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -741,6 +741,15 @@ static void addSanitizers(const Triple &TargetTriple,
MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass()));
}
+ if (LangOpts.Sanitize.has(SanitizerKind::ThreadDeadlock)) {
+ MPM.addPass(ModuleThreadSanitizerPass());
+ MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass(
+ ThreadSanitizerOptions{/*InstrumentMemoryAccesses=*/false,
+ /*InstrumentAtomics=*/false,
+ /*InstrumentMemIntrinsics=*/false,
+ /*AlwaysInstrumentFuncEntryExit=*/true})));
+ }
+
if (LangOpts.Sanitize.has(SanitizerKind::Type))
MPM.addPass(TypeSanitizerPass());
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index c77ba78122a81..3c3bc84eacd3f 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -40,7 +40,8 @@ static const SanitizerMask NotAllowedWithExecuteOnly =
SanitizerKind::Function | SanitizerKind::KCFI;
static const SanitizerMask NeedsUnwindTables =
SanitizerKind::Address | SanitizerKind::HWAddress | SanitizerKind::Type |
- SanitizerKind::Thread | SanitizerKind::Memory | SanitizerKind::DataFlow |
+ SanitizerKind::Thread | SanitizerKind::ThreadDeadlock |
+ SanitizerKind::Memory | SanitizerKind::DataFlow |
SanitizerKind::NumericalStability;
static const SanitizerMask SupportsCoverage =
SanitizerKind::Address | SanitizerKind::HWAddress |
@@ -707,6 +708,7 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
SanitizerKind::Memory | SanitizerKind::Leak |
SanitizerKind::Thread),
std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
+ std::make_pair(SanitizerKind::Thread, SanitizerKind::ThreadDeadlock),
std::make_pair(SanitizerKind::Leak,
SanitizerKind::Thread | SanitizerKind::Memory),
std::make_pair(SanitizerKind::KernelAddress,
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 08c06951cf220..d7acfacd17b05 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1655,6 +1655,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
}
if (SanArgs.needsTsanRt())
SharedRuntimes.push_back("tsan");
+ if (SanArgs.needsDeadlockRt())
+ SharedRuntimes.push_back("tsan_deadlock");
if (SanArgs.needsTysanRt())
SharedRuntimes.push_back("tysan");
if (SanArgs.needsHwasanRt()) {
@@ -1729,6 +1731,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
if (SanArgs.linkCXXRuntimes())
StaticRuntimes.push_back("tsan_cxx");
}
+ if (!SanArgs.needsSharedRt() && SanArgs.needsDeadlockRt())
+ StaticRuntimes.push_back("tsan_deadlock");
if (!SanArgs.needsSharedRt() && SanArgs.needsTysanRt())
StaticRuntimes.push_back("tysan");
if (!SanArgs.needsSharedRt() && SanArgs.needsUbsanRt()) {
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 6dca14d8bf0a8..ab9257362cea1 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -1001,6 +1001,7 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
IsLoongArch64 || IsRISCV64)
Res |= SanitizerKind::Thread;
+ Res |= SanitizerKind::ThreadDeadlock;
if (IsX86_64 || IsAArch64 || IsSystemZ || IsHexagon)
Res |= SanitizerKind::Type;
if (IsX86_64 || IsSystemZ || IsPowerPC64)
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 9c9874d94a1f2..5eebb98cda68a 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -52,6 +52,7 @@ set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32})
set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}
${S390X})
set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64} ${HEXAGON})
+set(ALL_TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_SANITIZER_COMMON_SUPPORTED_ARCH})
if(ANDROID)
set(OS_NAME "Android")
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index 083f1c98d0f16..d0ef92ed6d446 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -476,6 +476,7 @@ if(APPLE)
set(SANITIZER_COMMON_SUPPORTED_OS osx)
set(PROFILE_SUPPORTED_OS osx)
set(TSAN_SUPPORTED_OS osx)
+ set(TSAN_DEADLOCK_SUPPORTED_OS osx)
set(TYSAN_SUPPORTED_OS osx)
set(XRAY_SUPPORTED_OS osx)
set(FUZZER_SUPPORTED_OS osx)
@@ -575,6 +576,7 @@ if(APPLE)
list(APPEND SANITIZER_COMMON_SUPPORTED_OS ${platform}sim)
list(APPEND PROFILE_SUPPORTED_OS ${platform}sim)
list(APPEND TSAN_SUPPORTED_OS ${platform}sim)
+ list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform}sim)
list(APPEND FUZZER_SUPPORTED_OS ${platform}sim)
list(APPEND ORC_SUPPORTED_OS ${platform}sim)
list(APPEND UBSAN_SUPPORTED_OS ${platform}sim)
@@ -608,6 +610,7 @@ if(APPLE)
list_intersect(DARWIN_${platform}_TSAN_ARCHS DARWIN_${platform}_ARCHS ALL_TSAN_SUPPORTED_ARCH)
if(DARWIN_${platform}_TSAN_ARCHS)
list(APPEND TSAN_SUPPORTED_OS ${platform})
+ list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform})
endif()
list(APPEND FUZZER_SUPPORTED_OS ${platform})
list(APPEND ORC_SUPPORTED_OS ${platform})
@@ -677,6 +680,9 @@ if(APPLE)
list_intersect(TSAN_SUPPORTED_ARCH
ALL_TSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
+ list_intersect(TSAN_DEADLOCK_SUPPORTED_ARCH
+ ALL_TSAN_DEADLOCK_SUPPORTED_ARCH
+ SANITIZER_COMMON_SUPPORTED_ARCH)
list_intersect(UBSAN_SUPPORTED_ARCH
ALL_UBSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -726,6 +732,7 @@ else()
filter_available_targets(PROFILE_SUPPORTED_ARCH ${ALL_PROFILE_SUPPORTED_ARCH})
filter_available_targets(CTX_PROFILE_SUPPORTED_ARCH ${ALL_CTX_PROFILE_SUPPORTED_ARCH})
filter_available_targets(TSAN_SUPPORTED_ARCH ${ALL_TSAN_SUPPORTED_ARCH})
+ filter_available_targets(TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_TSAN_DEADLOCK_SUPPORTED_ARCH})
filter_available_targets(TYSAN_SUPPORTED_ARCH ${ALL_TYSAN_SUPPORTED_ARCH})
filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH})
filter_available_targets(SAFESTACK_SUPPORTED_ARCH
@@ -765,7 +772,7 @@ if(COMPILER_RT_SUPPORTED_ARCH)
endif()
message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}")
-set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi)
+set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tsan_deadlock;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi)
set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING
"sanitizers to build if supported on the target (all;${ALL_SANITIZERS})")
list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}")
@@ -879,6 +886,7 @@ if (COMPILER_RT_HAS_SANITIZER_COMMON AND TSAN_SUPPORTED_ARCH)
else()
set(COMPILER_RT_HAS_TSAN FALSE)
endif()
+set(COMPILER_RT_HAS_TSAN_DEADLOCK ${COMPILER_RT_HAS_TSAN})
if (OS_NAME MATCHES "Linux|FreeBSD|Windows|NetBSD|SunOS")
set(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME TRUE)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
index c694897b6556b..8d30a4e2f044c 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
@@ -500,5 +500,8 @@ using namespace __sanitizer;
namespace __memprof {
using namespace __sanitizer;
}
+namespace __tsan_deadlock {
+using namespace __sanitizer;
+}
#endif // SANITIZER_DEFS_H
diff --git a/compiler-rt/lib/tsan_deadlock/CMakeLists.txt b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt
new file mode 100644
index 0000000000000..74fc0f8bdb1cb
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt
@@ -0,0 +1,76 @@
+# Build for the DeadlockSanitizer runtime support library.
+
+include_directories(..)
+
+set(TSAN_DEADLOCK_SOURCES
+ tsan_deadlock_rtl.cpp
+ tsan_deadlock_interceptors.cpp
+ tsan_deadlock_interface.cpp)
+
+set(TSAN_DEADLOCK_HEADERS
+ tsan_deadlock_rtl.h)
+
+set(TSAN_DEADLOCK_CFLAGS ${SANITIZER_COMMON_CFLAGS})
+append_rtti_flag(OFF TSAN_DEADLOCK_CFLAGS)
+
+set(TSAN_DEADLOCK_DYNAMIC_LINK_LIBS
+ ${COMPILER_RT_UNWINDER_LINK_LIBS}
+ ${SANITIZER_CXX_ABI_LIBRARIES}
+ ${SANITIZER_COMMON_LINK_LIBS})
+
+append_list_if(COMPILER_RT_HAS_LIBDL dl TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+append_list_if(COMPILER_RT_HAS_LIBM m TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+append_list_if(COMPILER_RT_HAS_LIBPTHREAD pthread TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+
+add_compiler_rt_component(tsan_deadlock)
+
+if(APPLE)
+ add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
+
+ add_compiler_rt_runtime(clang_rt.tsan_deadlock
+ SHARED
+ OS ${TSAN_DEADLOCK_SUPPORTED_OS}
+ ARCHS ${TSAN_DEADLOCK_SUPPORTED_ARCH}
+ SOURCES ${TSAN_DEADLOCK_SOURCES}
+ ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+ OBJECT_LIBS RTInterception
+ RTSanitizerCommon
+ RTSanitizerCommonLibc
+ RTSanitizerCommonCoverage
+ RTSanitizerCommonSymbolizer
+ CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+ LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}
+ LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS}
+ PARENT_TARGET tsan_deadlock)
+else()
+ foreach(arch ${TSAN_DEADLOCK_SUPPORTED_ARCH})
+ add_compiler_rt_runtime(clang_rt.tsan_deadlock
+ STATIC
+ ARCHS ${arch}
+ SOURCES ${TSAN_DEADLOCK_SOURCES}
+ $<TARGET_OBJECTS:RTInterception.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
+ ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+ CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+ PARENT_TARGET tsan_deadlock)
+ add_compiler_rt_runtime(clang_rt.tsan_deadlock
+ SHARED
+ ARCHS ${arch}
+ SOURCES ${TSAN_DEADLOCK_SOURCES}
+ $<TARGET_OBJECTS:RTInterception.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
+ ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+ CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+ LINK_LIBS ${TSAN_DEADLOCK_DYNAMIC_LINK_LIBS}
+ LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS}
+ PARENT_TARGET tsan_deadlock)
+ endforeach()
+endif()
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
new file mode 100644
index 0000000000000..51ed3a22e2d9d
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
@@ -0,0 +1,398 @@
+//===-- tsan_deadlock_interceptors.cpp ------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include <pthread.h>
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_allocator_internal.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_glibc_version.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+#include "tsan_deadlock_rtl.h"
+
+using namespace __tsan_deadlock;
+
+__attribute__((tls_model("initial-exec"))) static __thread volatile int initing;
+static bool inited;
+
+static bool InitThread() {
+ if (initing)
+ return false;
+ if (cur_thread())
+ return true;
+ initing = true;
+ if (!inited) {
+ inited = true;
+ Initialize();
+ }
+ ThreadInit(&thr_tls);
+ initing = false;
+ return true;
+}
+
+struct ThreadArg {
+ void *(*fn)(void *);
+ void *arg;
+};
+
+static void *ThreadTrampoline(void *arg) {
+ ThreadArg *targ = static_cast<ThreadArg *>(arg);
+ void *(*fn)(void *) = targ->fn;
+ void *fn_arg = targ->arg;
+ InternalFree(targ);
+ ThreadInit(&thr_tls);
+ void *retval = fn(fn_arg);
+ // Also called from the pthread_exit interceptor; guard with is_inited to
+ // stay idempotent.
+ if (thr_tls.is_inited)
+ ThreadDestroy(&thr_tls);
+ return retval;
+}
+
+INTERCEPTOR(int, pthread_create, pthread_t *th, const pthread_attr_t *attr,
+ void *(*fn)(void *), void *arg) {
+ InitThread();
+ ThreadArg *targ = static_cast<ThreadArg *>(InternalAlloc(sizeof(ThreadArg)));
+ targ->fn = fn;
+ targ->arg = arg;
+ return REAL(pthread_create)(th, attr, ThreadTrampoline, targ);
+}
+
+INTERCEPTOR(int, pthread_join, pthread_t t, void **retval) {
+ InitThread();
+ return REAL(pthread_join)(t, retval);
+}
+
+INTERCEPTOR(void, pthread_exit, void *retval) {
+ if (thr_tls.is_inited)
+ ThreadDestroy(&thr_tls);
+ REAL(pthread_exit)(retval);
+}
+
+INTERCEPTOR(int, pthread_mutex_init, pthread_mutex_t *m,
+ const pthread_mutexattr_t *attr) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_mutex_init)(m, attr);
+ if (res == 0) {
+ bool reentrant = false;
+ if (attr) {
+ int type = 0;
+ if (pthread_mutexattr_gettype(attr, &type) == 0)
+ reentrant = (type == PTHREAD_MUTEX_RECURSIVE);
+ }
+ MutexInit(cur_thread(), (uptr)m, reentrant, pc);
+ }
+ return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_destroy, pthread_mutex_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_mutex_destroy)(m);
+ if (res == 0 || res == errno_EBUSY)
+ MutexDestroy(cur_thread(), (uptr)m, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_lock, pthread_mutex_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+ int res = REAL(pthread_mutex_lock)(m);
+ MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_trylock, pthread_mutex_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_mutex_trylock)(m);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_timedlock, pthread_mutex_t *m,
+ const struct timespec *abstime) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_mutex_timedlock)(m, abstime);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_unlock, pthread_mutex_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+ return REAL(pthread_mutex_unlock)(m);
+}
+
+INTERCEPTOR(int, pthread_spin_init, pthread_spinlock_t *m, int pshared) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_spin_init)(m, pshared);
+ if (res == 0)
+ MutexInit(cur_thread(), (uptr)m, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_spin_destroy, pthread_spinlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_spin_destroy)(m);
+ if (res == 0)
+ MutexDestroy(cur_thread(), (uptr)m, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_spin_lock, pthread_spinlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+ int res = REAL(pthread_spin_lock)(m);
+ MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_spin_trylock, pthread_spinlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_spin_trylock)(m);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_spin_unlock, pthread_spinlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+ return REAL(pthread_spin_unlock)(m);
+}
+
+INTERCEPTOR(int, pthread_rwlock_init, pthread_rwlock_t *m,
+ const pthread_rwlockattr_t *attr) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_rwlock_init)(m, attr);
+ if (res == 0)
+ MutexInit(cur_thread(), (uptr)m, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_destroy, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexDestroy(cur_thread(), (uptr)m, pc);
+ return REAL(pthread_rwlock_destroy)(m);
+}
+
+INTERCEPTOR(int, pthread_rwlock_rdlock, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeLock(cur_thread(), (uptr)m, false, pc);
+ int res = REAL(pthread_rwlock_rdlock)(m);
+ MutexAfterLock(cur_thread(), (uptr)m, false, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_tryrdlock, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_rwlock_tryrdlock)(m);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, false, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_timedrdlock, pthread_rwlock_t *m,
+ const timespec *abstime) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_rwlock_timedrdlock)(m, abstime);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, false, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_wrlock, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+ int res = REAL(pthread_rwlock_wrlock)(m);
+ MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_trywrlock, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_rwlock_trywrlock)(m);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_timedwrlock, pthread_rwlock_t *m,
+ const timespec *abstime) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ int res = REAL(pthread_rwlock_timedwrlock)(m, abstime);
+ if (res == 0)
+ MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_unlock, pthread_rwlock_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeUnlock(cur_thread(), (uptr)m, false, pc);
+ return REAL(pthread_rwlock_unlock)(m);
+}
+
+static pthread_cond_t *init_cond(pthread_cond_t *c, bool force = false) {
+ if (!common_flags()->legacy_pthread_cond)
+ return c;
+ atomic_uintptr_t *p = (atomic_uintptr_t *)c;
+ uptr cond = atomic_load(p, memory_order_acquire);
+ if (!force && cond != 0)
+ return (pthread_cond_t *)cond;
+ void *newcond = InternalAlloc(sizeof(pthread_cond_t));
+ internal_memset(newcond, 0, sizeof(pthread_cond_t));
+ if (atomic_compare_exchange_strong(p, &cond, (uptr)newcond,
+ memory_order_acq_rel))
+ return (pthread_cond_t *)newcond;
+ InternalFree(newcond);
+ return (pthread_cond_t *)cond;
+}
+
+INTERCEPTOR(int, pthread_cond_init, pthread_cond_t *c,
+ const pthread_condattr_t *a) {
+ InitThread();
+ return REAL(pthread_cond_init)(init_cond(c, true), a);
+}
+
+INTERCEPTOR(int, pthread_cond_wait, pthread_cond_t *c, pthread_mutex_t *m) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+ int res = REAL(pthread_cond_wait)(init_cond(c), m);
+ MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+ MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_cond_timedwait, pthread_cond_t *c, pthread_mutex_t *m,
+ const timespec *abstime) {
+ uptr pc = GET_CURRENT_PC();
+ InitThread();
+ MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+ int res = REAL(pthread_cond_timedwait)(init_cond(c), m, abstime);
+ MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+ MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+ return res;
+}
+
+INTERCEPTOR(int, pthread_cond_signal, pthread_cond_t *c) {
+ InitThread();
+ return REAL(pthread_cond_signal)(init_cond(c));
+}
+
+INTERCEPTOR(int, pthread_cond_broadcast, pthread_cond_t *c) {
+ InitThread();
+ return REAL(pthread_cond_broadcast)(init_cond(c));
+}
+
+INTERCEPTOR(int, pthread_cond_destroy, pthread_cond_t *c) {
+ InitThread();
+ pthread_cond_t *cond = init_cond(c);
+ int res = REAL(pthread_cond_destroy)(cond);
+ if (common_flags()->legacy_pthread_cond) {
+ InternalFree(cond);
+ atomic_store((atomic_uintptr_t *)c, 0, memory_order_relaxed);
+ }
+ return res;
+}
+
+INTERCEPTOR(char *, realpath, const char *path, char *resolved_path) {
+ InitThread();
+ return REAL(realpath)(path, resolved_path);
+}
+
+INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) {
+ InitThread();
+ return REAL(read)(fd, ptr, count);
+}
+
+INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) {
+ InitThread();
+ return REAL(pread)(fd, ptr, count, offset);
+}
+
+namespace __tsan_deadlock {
+
+void InitializeInterceptors() {
+ INTERCEPT_FUNCTION(pthread_create);
+ INTERCEPT_FUNCTION(pthread_join);
+ INTERCEPT_FUNCTION(pthread_exit);
+
+ INTERCEPT_FUNCTION(pthread_mutex_init);
+ INTERCEPT_FUNCTION(pthread_mutex_destroy);
+ INTERCEPT_FUNCTION(pthread_mutex_lock);
+ INTERCEPT_FUNCTION(pthread_mutex_trylock);
+ INTERCEPT_FUNCTION(pthread_mutex_timedlock);
+ INTERCEPT_FUNCTION(pthread_mutex_unlock);
+
+ INTERCEPT_FUNCTION(pthread_spin_init);
+ INTERCEPT_FUNCTION(pthread_spin_destroy);
+ INTERCEPT_FUNCTION(pthread_spin_lock);
+ INTERCEPT_FUNCTION(pthread_spin_trylock);
+ INTERCEPT_FUNCTION(pthread_spin_unlock);
+
+ INTERCEPT_FUNCTION(pthread_rwlock_init);
+ INTERCEPT_FUNCTION(pthread_rwlock_destroy);
+ INTERCEPT_FUNCTION(pthread_rwlock_rdlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_tryrdlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_timedrdlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_wrlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_trywrlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_timedwrlock);
+ INTERCEPT_FUNCTION(pthread_rwlock_unlock);
+
+ // See the comment in tsan_interceptors_posix.cpp.
+#if SANITIZER_GLIBC && !__GLIBC_PREREQ(2, 36) && \
+ (defined(__x86_64__) || defined(__mips__) || SANITIZER_PPC64V1 || \
+ defined(__s390x__))
+ INTERCEPT_FUNCTION_VER(pthread_cond_init, "GLIBC_2.3.2");
+ INTERCEPT_FUNCTION_VER(pthread_cond_signal, "GLIBC_2.3.2");
+ INTERCEPT_FUNCTION_VER(pthread_cond_broadcast, "GLIBC_2.3.2");
+ INTERCEPT_FUNCTION_VER(pthread_cond_wait, "GLIBC_2.3.2");
+ INTERCEPT_FUNCTION_VER(pthread_cond_timedwait, "GLIBC_2.3.2");
+ INTERCEPT_FUNCTION_VER(pthread_cond_destroy, "GLIBC_2.3.2");
+#else
+ INTERCEPT_FUNCTION(pthread_cond_init);
+ INTERCEPT_FUNCTION(pthread_cond_signal);
+ INTERCEPT_FUNCTION(pthread_cond_broadcast);
+ INTERCEPT_FUNCTION(pthread_cond_wait);
+ INTERCEPT_FUNCTION(pthread_cond_timedwait);
+ INTERCEPT_FUNCTION(pthread_cond_destroy);
+#endif
+
+ INTERCEPT_FUNCTION(realpath);
+ INTERCEPT_FUNCTION(read);
+ INTERCEPT_FUNCTION(pread);
+}
+
+} // namespace __tsan_deadlock
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
new file mode 100644
index 0000000000000..8a0d4c3295091
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
@@ -0,0 +1,36 @@
+//===-- tsan_deadlock_interface.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include "tsan_deadlock_rtl.h"
+
+using namespace __tsan_deadlock;
+
+extern "C" {
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_init() { Initialize(); }
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_entry(void *pc) {
+ if (UNLIKELY(!thr_tls.is_inited))
+ return;
+ DCHECK_LT(thr_tls.shadow_stack_pos, thr_tls.shadow_stack_end);
+ thr_tls.shadow_stack_pos[0] = (uptr)pc;
+ thr_tls.shadow_stack_pos++;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_exit() {
+ if (UNLIKELY(!thr_tls.is_inited))
+ return;
+ if (LIKELY(thr_tls.shadow_stack_pos > thr_tls.shadow_stack))
+ thr_tls.shadow_stack_pos--;
+}
+
+} // extern "C"
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
new file mode 100644
index 0000000000000..fd56bb81c4174
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
@@ -0,0 +1,342 @@
+//===-- tsan_deadlock_rtl.cpp ---------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include "tsan_deadlock_rtl.h"
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_placement_new.h"
+#include "sanitizer_common/sanitizer_report_decorator.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+namespace __tsan_deadlock {
+
+Flags tsan_deadlock_flags;
+
+static Context *ctx;
+
+static const uptr kShadowStackSize = 64 * 1024;
+
+__attribute__((tls_model("initial-exec"))) THREADLOCAL Thread thr_tls;
+
+Thread *cur_thread() { return thr_tls.is_inited ? &thr_tls : nullptr; }
+
+class Decorator : public __sanitizer::SanitizerCommonDecorator {
+public:
+ Decorator() : SanitizerCommonDecorator() {}
+ const char *Mutex() { return Magenta(); }
+ const char *ThreadDescription() { return Cyan(); }
+};
+
+static u32 CurrentStackId(Thread *thr, uptr pc) {
+ if (pc) {
+ DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end);
+ *thr->shadow_stack_pos = pc;
+ thr->shadow_stack_pos++;
+ }
+ u32 id = StackDepotPut(
+ StackTrace(thr->shadow_stack, thr->shadow_stack_pos - thr->shadow_stack));
+ if (pc)
+ thr->shadow_stack_pos--;
+ return id;
+}
+
+static void PrintStack(Thread *thr, u32 stk) {
+ if (!stk)
+ return;
+ StackTrace trace = StackDepotGet(stk);
+ thr->ignore_interceptors++;
+ trace.Print();
+ thr->ignore_interceptors--;
+}
+
+static void ReportDeadlock(Thread *thr, DDReport *rep) {
+ if (!rep)
+ return;
+ Decorator d;
+ Lock lock(&ctx->report_mutex);
+ Printf("==================\n");
+ Printf(
+ "%sWARNING: %s: lock-order-inversion (potential deadlock) (pid=%d)%s\n",
+ d.Warning(), SanitizerToolName, (int)internal_getpid(), d.Default());
+ Printf(" Cycle in lock order graph: ");
+ for (int i = 0; i < rep->n; i++)
+ Printf("%sM%d%s (0x%zx) => ", d.Mutex(), i, d.Default(),
+ (uptr)rep->loop[i].mtx_ctx0);
+ Printf("%sM0%s\n\n", d.Mutex(), d.Default());
+ for (int i = 0; i < rep->n; i++) {
+ int next = (i + 1) % rep->n;
+ Printf(" %sMutex M%d%s acquired here while holding %sMutex M%d%s"
+ " in %sthread T%lld%s:\n",
+ d.Mutex(), next, d.Default(), d.Mutex(), i, d.Default(),
+ d.ThreadDescription(), (s64)rep->loop[i].thr_ctx, d.Default());
+ PrintStack(thr, rep->loop[i].stk[0]);
+ if (flags()->second_deadlock_stack && rep->loop[i].stk[1]) {
+ Printf(" %sMutex M%d%s previously acquired by the same thread here:\n",
+ d.Mutex(), i, d.Default());
+ PrintStack(thr, rep->loop[i].stk[1]);
+ }
+ if (i == 0 && !flags()->second_deadlock_stack) {
+ Printf(
+ " HINT: use TSAN_DEADLOCK_OPTIONS=second_deadlock_stack=1 to get "
+ "more informative warning message\n\n");
+ }
+ }
+ Printf("==================\n");
+ StackTrace empty;
+ ReportErrorSummary("lock-order-inversion", &empty);
+ if (flags()->halt_on_error)
+ Die();
+}
+
+static void ReportMutexMisuse(Thread *thr, const char *what, uptr addr, uptr pc,
+ u32 creation_stk, u32 last_lock_stk = 0) {
+ Decorator d;
+ Lock lock(&ctx->report_mutex);
+ Printf("==================\n");
+ Printf("%sWARNING: %s: %s on %smutex 0x%zx%s (pid=%d)%s\n", d.Warning(),
+ SanitizerToolName, what, d.Mutex(), addr, d.Default(),
+ (int)internal_getpid(), d.Default());
+ PrintStack(thr, CurrentStackId(thr, pc));
+ if (last_lock_stk) {
+ Printf(" and:\n");
+ PrintStack(thr, last_lock_stk);
+ }
+ if (creation_stk) {
+ Printf("%s Mutex M0 (%p) created at:%s\n", d.Mutex(),
+ reinterpret_cast<void *>(addr), d.Default());
+ PrintStack(thr, creation_stk);
+ }
+ Printf("==================\n");
+ StackTrace empty;
+ ReportErrorSummary(what, &empty);
+ if (flags()->halt_on_error)
+ Die();
+}
+
+Callback::Callback(Thread *thr, uptr pc) : thr(thr), pc(pc) {
+ DDCallback::pt = thr->dd_pt;
+ DDCallback::lt = thr->dd_lt;
+}
+
+u32 Callback::Unwind() { return CurrentStackId(thr, pc); }
+
+int Callback::UniqueTid() { return thr->unique_id; }
+
+static void InitializeFlags() {
+ Flags *f = flags();
+ f->SetDefaults();
+ SetCommonFlagsDefaults();
+ {
+ CommonFlags cf;
+ cf.CopyFrom(*common_flags());
+ cf.allow_addr2line = true;
+ cf.exitcode = 66;
+ OverrideCommonFlags(cf);
+ }
+ FlagParser parser;
+ RegisterFlag(&parser, "second_deadlock_stack",
+ "Report where each mutex is locked in deadlock reports",
+ &f->second_deadlock_stack);
+ RegisterFlag(&parser, "report_destroy_locked",
+ "Report destruction of a locked mutex?",
+ &f->report_destroy_locked);
+ RegisterFlag(&parser, "report_mutex_bugs",
+ "Report incorrect usages of mutexes and mutex annotations?",
+ &f->report_mutex_bugs);
+ RegisterFlag(&parser, "report_bugs",
+ "Turns off bug reporting entirely (useful for benchmarking).",
+ &f->report_bugs);
+ RegisterFlag(&parser, "halt_on_error", "Exit after first reported error.",
+ &f->halt_on_error);
+ RegisterCommonFlags(&parser);
+ parser.ParseStringFromEnv("TSAN_DEADLOCK_OPTIONS");
+ if (!f->report_bugs) {
+ f->report_destroy_locked = false;
+ f->report_mutex_bugs = false;
+ }
+ SetVerbosity(common_flags()->verbosity);
+}
+
+void Initialize() {
+ static atomic_uint32_t initialized;
+ if (atomic_fetch_add(&initialized, 1, memory_order_relaxed) != 0)
+ return;
+ static u64 ctx_mem[sizeof(Context) / sizeof(u64) + 1];
+ ctx = new (ctx_mem) Context();
+ SanitizerToolName = "DeadlockSanitizer";
+ InitializeFlags();
+ ctx->dd = DDetector::Create(flags());
+ InitializeInterceptors();
+}
+
+void ThreadInit(Thread *thr) {
+ static atomic_uintptr_t id_gen;
+ uptr id = atomic_fetch_add(&id_gen, 1, memory_order_relaxed);
+ thr->unique_id = (int)id;
+ thr->dd_pt = ctx->dd->CreatePhysicalThread();
+ thr->dd_lt = ctx->dd->CreateLogicalThread(id);
+ thr->shadow_stack = static_cast<uptr *>(
+ MmapNoReserveOrDie(kShadowStackSize * sizeof(uptr), "shadow stack"));
+ thr->shadow_stack_pos = thr->shadow_stack;
+ thr->shadow_stack_end = thr->shadow_stack + kShadowStackSize;
+ thr->is_inited = true;
+}
+
+void ThreadDestroy(Thread *thr) {
+ thr->is_inited = false;
+ ctx->dd->DestroyLogicalThread(thr->dd_lt);
+ thr->dd_lt = nullptr;
+ UnmapOrDie(thr->shadow_stack, kShadowStackSize * sizeof(uptr));
+ thr->shadow_stack = nullptr;
+ thr->shadow_stack_pos = nullptr;
+ thr->shadow_stack_end = nullptr;
+ ctx->dd->DestroyPhysicalThread(thr->dd_pt);
+ thr->dd_pt = nullptr;
+}
+
+static void EnsureMutexInit(Callback *cb, MutexHashMap::Handle *h, uptr m,
+ uptr pc) {
+ if (h->created()) {
+ ctx->dd->MutexInit(cb, &(*h)->dd);
+ (*h)->dd.ctx = m;
+ (*h)->creation_stk = CurrentStackId(cb->thr, pc);
+ }
+}
+
+void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc) {
+ if (!thr || thr->ignore_interceptors)
+ return;
+ Callback cb(thr, pc);
+ MutexHashMap::Handle h(&ctx->mutex_map, m);
+ EnsureMutexInit(&cb, &h, m, pc);
+ h->reentrant = reentrant;
+}
+
+void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc) {
+ if (!thr || thr->ignore_interceptors)
+ return;
+ Callback cb(thr, pc);
+ bool report_double_lock = false;
+ {
+ MutexHashMap::Handle h(&ctx->mutex_map, m);
+ EnsureMutexInit(&cb, &h, m, pc);
+ if (writelock) {
+ uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+ if (owner == (uptr)thr->dd_lt && !h->reentrant)
+ report_double_lock = true;
+ else if (owner != (uptr)thr->dd_lt)
+ ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock);
+ } else {
+ ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock);
+ }
+ }
+ if (report_double_lock && flags()->report_mutex_bugs) {
+ MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+ /*create=*/false);
+ u32 cstk = h.exists() ? h->creation_stk : 0;
+ ReportMutexMisuse(thr, "double lock of a mutex", m, pc, cstk);
+ } else if (flags()->report_bugs) {
+ ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+ }
+}
+
+void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock,
+ uptr pc) {
+ if (!thr || thr->ignore_interceptors)
+ return;
+ Callback cb(thr, pc);
+ {
+ MutexHashMap::Handle h(&ctx->mutex_map, m);
+ EnsureMutexInit(&cb, &h, m, pc);
+ if (writelock) {
+ uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+ if (owner == (uptr)thr->dd_lt) {
+ DCHECK(h->reentrant);
+ h->recursion++;
+ } else {
+ DCHECK_EQ(owner, 0u);
+ CHECK_EQ(h->recursion, 0);
+ h->recursion = 1;
+ h->last_lock_stk = CurrentStackId(thr, pc);
+ atomic_store(&h->owner_tid, (uptr)thr->dd_lt, memory_order_relaxed);
+ ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock);
+ }
+ } else {
+ ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock);
+ }
+ }
+ if (flags()->report_bugs)
+ ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+}
+
+void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc) {
+ if (!thr || thr->ignore_interceptors)
+ return;
+ Callback cb(thr, pc);
+ bool report_bad_unlock = false;
+ {
+ MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+ /*create=*/false);
+ if (!h.exists())
+ return;
+ if (writelock) {
+ uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+ if (owner != (uptr)thr->dd_lt) {
+ report_bad_unlock = true;
+ } else {
+ h->recursion--;
+ if (h->recursion == 0) {
+ atomic_store(&h->owner_tid, 0, memory_order_relaxed);
+ ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock);
+ }
+ }
+ } else {
+ ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock);
+ }
+ }
+ if (report_bad_unlock && flags()->report_mutex_bugs) {
+ MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+ /*create=*/false);
+ u32 cstk = h.exists() ? h->creation_stk : 0;
+ ReportMutexMisuse(thr, "unlock of an unlocked mutex (or by a wrong thread)",
+ m, pc, cstk);
+ } else if (flags()->report_bugs) {
+ ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+ }
+}
+
+void MutexDestroy(Thread *thr, uptr m, uptr pc) {
+ if (!thr || thr->ignore_interceptors)
+ return;
+ Callback cb(thr, pc);
+ bool report_destroy_locked = false;
+ u32 creation_stk = 0;
+ u32 last_lock_stk = 0;
+ {
+ MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/true);
+ if (!h.exists())
+ return;
+ creation_stk = h->creation_stk;
+ last_lock_stk = h->last_lock_stk;
+ if (atomic_load(&h->owner_tid, memory_order_relaxed) != 0)
+ report_destroy_locked = true;
+ ctx->dd->MutexDestroy(&cb, &h->dd);
+ }
+ if (report_destroy_locked && flags()->report_destroy_locked)
+ ReportMutexMisuse(thr, "destroy of a locked mutex", m, pc, creation_stk,
+ last_lock_stk);
+}
+
+} // namespace __tsan_deadlock
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
new file mode 100644
index 0000000000000..ac9046c557c24
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
@@ -0,0 +1,102 @@
+//===-- tsan_deadlock_rtl.h -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DeadlockSanitizer.
+//
+// Main internal header.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TSAN_DEADLOCK_RTL_H
+#define TSAN_DEADLOCK_RTL_H
+
+#include "sanitizer_common/sanitizer_addrhashmap.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_deadlock_detector_interface.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_mutex.h"
+
+namespace __tsan_deadlock {
+
+struct Flags : DDFlags {
+ bool report_destroy_locked;
+ bool report_mutex_bugs;
+ bool report_bugs;
+ bool halt_on_error;
+
+ void SetDefaults() {
+ second_deadlock_stack = false;
+ report_destroy_locked = true;
+ report_mutex_bugs = true;
+ report_bugs = true;
+ halt_on_error = false;
+ }
+};
+
+struct UserMutex {
+ DDMutex dd;
+ atomic_uintptr_t owner_tid; // DDLogicalThread* cast to uptr; 0 = unlocked
+ u32 recursion;
+ u32 creation_stk;
+ u32 last_lock_stk;
+ bool reentrant;
+};
+
+// Stored in TLS; shadow_stack_pos is first for cache-line friendliness.
+struct Thread {
+ uptr *shadow_stack_pos;
+ uptr *shadow_stack;
+ uptr *shadow_stack_end;
+
+ DDPhysicalThread *dd_pt;
+ DDLogicalThread *dd_lt;
+
+ int ignore_interceptors;
+ int unique_id;
+ bool is_inited;
+};
+
+struct Callback final : public DDCallback {
+ Thread *thr;
+ uptr pc;
+
+ explicit Callback(Thread *thr, uptr pc);
+ u32 Unwind() override;
+ int UniqueTid() override;
+};
+
+typedef AddrHashMap<UserMutex, 31051> MutexHashMap;
+
+struct Context {
+ DDetector *dd;
+ Mutex report_mutex;
+ MutexHashMap mutex_map;
+};
+
+extern Flags tsan_deadlock_flags;
+inline Flags *flags() { return &tsan_deadlock_flags; }
+
+extern THREADLOCAL Thread thr_tls;
+
+Thread *cur_thread();
+
+void Initialize();
+void InitializeInterceptors();
+
+void ThreadInit(Thread *thr);
+void ThreadDestroy(Thread *thr);
+
+void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc = 0);
+void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc);
+void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock, uptr pc);
+void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc);
+void MutexDestroy(Thread *thr, uptr m, uptr pc);
+
+} // namespace __tsan_deadlock
+
+#endif // TSAN_DEADLOCK_RTL_H
diff --git a/compiler-rt/test/tsan_deadlock/CMakeLists.txt b/compiler-rt/test/tsan_deadlock/CMakeLists.txt
new file mode 100644
index 0000000000000..308780098af39
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(TSAN_DEADLOCK_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+set(TSAN_DEADLOCK_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+list(APPEND TSAN_DEADLOCK_TEST_DEPS tsan_deadlock)
+
+set(TSAN_DEADLOCK_TESTSUITES)
+set(TSAN_DEADLOCK_TEST_ARCH ${TSAN_DEADLOCK_SUPPORTED_ARCH})
+
+foreach(arch ${TSAN_DEADLOCK_TEST_ARCH})
+ set(TSAN_DEADLOCK_TEST_TARGET_ARCH ${arch})
+ string(TOLOWER "-${arch}" TSAN_DEADLOCK_TEST_CONFIG_SUFFIX)
+ get_test_cc_for_arch(${arch} TSAN_DEADLOCK_TEST_TARGET_CC TSAN_DEADLOCK_TEST_TARGET_CFLAGS)
+
+ string(TOUPPER ${arch} ARCH_UPPER_CASE)
+ set(CONFIG_NAME ${ARCH_UPPER_CASE}Config)
+
+ configure_lit_site_cfg(
+ ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
+ MAIN_CONFIG
+ ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py)
+
+ list(APPEND TSAN_DEADLOCK_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+endforeach()
+
+add_lit_testsuite(check-tsan_deadlock "Running DeadlockSanitizer tests"
+ ${TSAN_DEADLOCK_TESTSUITES}
+ DEPENDS ${TSAN_DEADLOCK_TEST_DEPS})
+set_target_properties(check-tsan_deadlock PROPERTIES FOLDER "Compiler-RT Tests")
diff --git a/compiler-rt/test/tsan_deadlock/lit.cfg.py b/compiler-rt/test/tsan_deadlock/lit.cfg.py
new file mode 100644
index 0000000000000..0e98737c391cb
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/lit.cfg.py
@@ -0,0 +1,52 @@
+# -*- Python -*-
+
+import os
+
+# Setup config name.
+config.name = "DeadlockSanitizer" + config.name_suffix
+
+# Setup source root.
+config.test_source_root = os.path.dirname(__file__)
+
+default_tsan_deadlock_opts = "atexit_sleep_ms=0"
+
+if config.target_os == "Darwin":
+ # On Darwin, we default to `abort_on_error=1`, which would make tests run
+ # much slower. Let's override this and run lit tests with 'abort_on_error=0'.
+ default_tsan_deadlock_opts += ":abort_on_error=0"
+
+if default_tsan_deadlock_opts:
+ config.environment["TSAN_DEADLOCK_OPTIONS"] = default_tsan_deadlock_opts
+ default_tsan_deadlock_opts += ":"
+config.substitutions.append(
+ (
+ "%env_tsan_deadlock_opts=",
+ "env TSAN_DEADLOCK_OPTIONS=" + default_tsan_deadlock_opts,
+ )
+)
+
+clang_tsan_deadlock_cflags = (
+ ["-fsanitize=thread-deadlock", "-Wall", "-pthread"]
+ + [config.target_cflags]
+ + config.debug_info_flags
+)
+clang_tsan_deadlock_cxxflags = (
+ config.cxx_mode_flags + clang_tsan_deadlock_cflags + ["-std=c++14"]
+)
+
+
+def build_invocation(compile_flags):
+ return " " + " ".join([config.clang] + compile_flags) + " "
+
+
+config.substitutions.append(
+ ("%clang_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cflags))
+)
+config.substitutions.append(
+ ("%clangxx_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cxxflags))
+)
+
+config.suffixes = [".c", ".cpp"]
+
+if config.target_os not in ["FreeBSD", "Linux", "Darwin", "NetBSD"]:
+ config.unsupported = True
diff --git a/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..2ba3b1af1c45e
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
@@ -0,0 +1,12 @@
+ at LIT_SITE_CFG_IN_HEADER@
+
+config.name_suffix = "@TSAN_DEADLOCK_TEST_CONFIG_SUFFIX@"
+config.tsan_deadlock_lit_source_dir = "@TSAN_DEADLOCK_LIT_SOURCE_DIR@"
+config.target_cflags = "@TSAN_DEADLOCK_TEST_TARGET_CFLAGS@"
+config.target_arch = "@TSAN_DEADLOCK_TEST_TARGET_ARCH@"
+
+# Load common config for all compiler-rt lit tests.
+lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
+
+# Load tool-specific config that would do the real work.
+lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py")
diff --git a/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
new file mode 100644
index 0000000000000..16723d89ff033
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
@@ -0,0 +1,26 @@
+// Adapted from tsan/mutex_bad_unlock.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+static pthread_mutex_t mu;
+
+static void *unlocker(void *) {
+ pthread_mutex_unlock(&mu);
+ return 0;
+}
+
+int main() {
+ pthread_mutex_init(&mu, 0);
+ pthread_mutex_lock(&mu);
+ pthread_t t;
+ pthread_create(&t, 0, unlocker, 0);
+ pthread_join(t, 0);
+ return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread)
+// CHECK: {{.*}} in pthread_mutex_unlock
+// CHECK: Mutex M0 (0x{{.*}}) created at:
+// CHECK: {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread)
diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
new file mode 100644
index 0000000000000..8b28da1175e97
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
@@ -0,0 +1,31 @@
+// Equivalent to tsan/mutex_cycle2.c
+// RUN: %clangxx_tsan_deadlock %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+// RUN: %env_tsan_deadlock_opts=report_bugs=0 %run %t 2>&1 | FileCheck %s --check-prefix=DISABLED
+#include <pthread.h>
+#include <stdio.h>
+
+int main() {
+ pthread_mutex_t mu1, mu2;
+ pthread_mutex_init(&mu1, NULL);
+ pthread_mutex_init(&mu2, NULL);
+
+ // mu1 => mu2
+ pthread_mutex_lock(&mu1);
+ pthread_mutex_lock(&mu2);
+ pthread_mutex_unlock(&mu2);
+ pthread_mutex_unlock(&mu1);
+
+ // mu2 => mu1
+ pthread_mutex_lock(&mu2);
+ pthread_mutex_lock(&mu1);
+ // CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock)
+ // DISABLED-NOT: DeadlockSanitizer
+ // DISABLED: PASS
+ pthread_mutex_unlock(&mu1);
+ pthread_mutex_unlock(&mu2);
+
+ pthread_mutex_destroy(&mu1);
+ pthread_mutex_destroy(&mu2);
+ fprintf(stderr, "PASS\n");
+}
diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
new file mode 100644
index 0000000000000..587a58e451342
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
@@ -0,0 +1,45 @@
+// Equivalent to tsan/mutex_cycle_long.c
+// RUN: %clangxx_tsan_deadlock %s -o %t
+// RUN: not %run %t 5 2>&1 | FileCheck %s
+// RUN: not %run %t 10 2>&1 | FileCheck %s
+// RUN: not %run %t 15 2>&1 | FileCheck %s
+// RUN: not %run %t 20 2>&1 | FileCheck %s
+// RUN: %run %t 30 2>&1 | FileCheck %s --check-prefix=CHECK-TOO-LONG-CYCLE
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[]) {
+ int num_mutexes = 5;
+ if (argc > 1)
+ num_mutexes = atoi(argv[1]);
+
+ pthread_mutex_t m[num_mutexes];
+ for (int i = 0; i < num_mutexes; ++i)
+ pthread_mutex_init(&m[i], NULL);
+
+ for (int i = 0; i < num_mutexes - 1; ++i) {
+ pthread_mutex_lock(&m[i]);
+ pthread_mutex_lock(&m[i + 1]);
+
+ pthread_mutex_unlock(&m[i]);
+ pthread_mutex_unlock(&m[i + 1]);
+ }
+
+ pthread_mutex_lock(&m[num_mutexes - 1]);
+ pthread_mutex_lock(&m[0]);
+
+ pthread_mutex_unlock(&m[num_mutexes - 1]);
+ pthread_mutex_unlock(&m[0]);
+
+ for (int i = 0; i < num_mutexes; ++i)
+ pthread_mutex_destroy(&m[i]);
+
+ fprintf(stderr, "PASS\n");
+}
+
+// CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock)
+// CHECK-TOO-LONG-CYCLE: WARNING: too long mutex cycle found
+// CHECK: PASS
+// CHECK-TOO-LONG-CYCLE: PASS
diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
new file mode 100644
index 0000000000000..96e813bebcd3f
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
@@ -0,0 +1,20 @@
+// Equivalent to tsan/mutex_destroy_locked.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+int main() {
+ pthread_mutex_t m;
+ pthread_mutex_init(&m, 0);
+ pthread_mutex_lock(&m);
+ pthread_mutex_destroy(&m);
+ return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex
+// CHECK: {{.*}} in pthread_mutex_destroy
+// CHECK: and:
+// CHECK: {{.*}} in pthread_mutex_lock
+// CHECK: Mutex M0 (0x{{.*}}) created at:
+// CHECK: {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex
diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
new file mode 100644
index 0000000000000..5aced7b4a7a0c
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
@@ -0,0 +1,27 @@
+// Equivalent to tsan/mutex_destroy_locked2.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+static void *thread(void *) {
+ pthread_mutex_t m;
+ pthread_mutex_init(&m, 0);
+ pthread_mutex_lock(&m);
+ pthread_mutex_destroy(&m);
+ return 0;
+}
+
+int main() {
+ pthread_t th;
+ pthread_create(&th, 0, thread, 0);
+ pthread_join(th, 0);
+ return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex
+// CHECK: {{.*}} in pthread_mutex_destroy
+// CHECK: and:
+// CHECK: {{.*}} in pthread_mutex_lock
+// CHECK: Mutex M0 (0x{{.*}}) created at:
+// CHECK: {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex
diff --git a/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp
new file mode 100644
index 0000000000000..7236ade4c33c3
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp
@@ -0,0 +1,17 @@
+// Adapted from tsan/mutex_double_lock.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+int main() {
+ pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&mu);
+ pthread_mutex_lock(&mu);
+ return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: double lock of a mutex
+// CHECK: {{.*}} in pthread_mutex_lock
+// CHECK: Mutex M0 (0x{{.*}}) created at:
+// CHECK: {{.*}} in pthread_mutex_lock
+// CHECK: SUMMARY: DeadlockSanitizer: double lock of a mutex
diff --git a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
index 39e09a458cba5..cad45cb4f8a4d 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
@@ -20,13 +20,27 @@ namespace llvm {
class Function;
class Module;
+struct ThreadSanitizerOptions {
+ bool InstrumentMemoryAccesses = true;
+ bool InstrumentAtomics = true;
+ bool InstrumentMemIntrinsics = true;
+ bool AlwaysInstrumentFuncEntryExit = false;
+};
+
/// A function pass for tsan instrumentation.
///
/// Instruments functions to detect race conditions reads. This function pass
/// inserts calls to runtime library functions. If the functions aren't declared
/// yet, the pass inserts the declarations. Otherwise the existing globals are
-struct ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> {
+class ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> {
+public:
+ LLVM_ABI
+ ThreadSanitizerPass(const ThreadSanitizerOptions &Options = {})
+ : Options(Options) {}
LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+
+private:
+ ThreadSanitizerOptions Options;
};
/// A module pass for tsan instrumentation.
diff --git a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
index f05efd863fb74..0a863aa3ae559 100644
--- a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
@@ -109,7 +109,7 @@ namespace {
/// ensures the __tsan_init function is in the list of global constructors for
/// the module.
struct ThreadSanitizer {
- ThreadSanitizer() {
+ ThreadSanitizer(ThreadSanitizerOptions Options) : Options(Options) {
// Check options and warn user.
if (ClInstrumentReadBeforeWrite && ClCompoundReadBeforeWrite) {
errs()
@@ -172,6 +172,7 @@ struct ThreadSanitizer {
FunctionCallee TsanVptrUpdate;
FunctionCallee TsanVptrLoad;
FunctionCallee MemmoveFn, MemcpyFn, MemsetFn;
+ ThreadSanitizerOptions Options;
};
void insertModuleCtor(Module &M) {
@@ -186,7 +187,7 @@ void insertModuleCtor(Module &M) {
PreservedAnalyses ThreadSanitizerPass::run(Function &F,
FunctionAnalysisManager &FAM) {
- ThreadSanitizer TSan;
+ ThreadSanitizer TSan(Options);
if (TSan.sanitizeFunction(F, FAM.getResult<TargetLibraryAnalysis>(F)))
return PreservedAnalyses::none();
return PreservedAnalyses::all();
@@ -550,19 +551,21 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
// (e.g. variables that do not escape, etc).
// Instrument memory accesses only if we want to report bugs in the function.
- if (ClInstrumentMemoryAccesses && SanitizeFunction)
+ if (ClInstrumentMemoryAccesses && Options.InstrumentMemoryAccesses &&
+ SanitizeFunction)
for (const auto &II : AllLoadsAndStores) {
Res |= instrumentLoadOrStore(II, DL);
}
// Instrument atomic memory accesses in any case (they can be used to
// implement synchronization).
- if (ClInstrumentAtomics)
+ if (ClInstrumentAtomics && Options.InstrumentAtomics)
for (auto *Inst : AtomicAccesses) {
Res |= instrumentAtomic(Inst, DL);
}
- if (ClInstrumentMemIntrinsics && SanitizeFunction)
+ if (ClInstrumentMemIntrinsics && Options.InstrumentMemIntrinsics &&
+ SanitizeFunction)
for (auto *Inst : MemIntrinCalls) {
Res |= instrumentMemIntrinsic(Inst);
}
@@ -573,8 +576,10 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
InsertRuntimeIgnores(F);
}
- // Instrument function entry/exit points if there were instrumented accesses.
- if ((Res || HasCalls) && ClInstrumentFuncEntryExit) {
+ // Instrument function entry/exit points if there were instrumented accesses,
+ // or if all functions should be tracked (e.g. for tsan_deadlock).
+ if ((Res || HasCalls || Options.AlwaysInstrumentFuncEntryExit) &&
+ ClInstrumentFuncEntryExit) {
InstrumentationIRBuilder IRB(&F.getEntryBlock(),
F.getEntryBlock().getFirstNonPHIIt());
auto ProgramAsPtrTy = PointerType::get(F.getParent()->getContext(),
More information about the cfe-commits
mailing list