Skip to content

Commit 1146d96

Browse files
authored
[TSAN] Add __tsan_check_no_mutexes_held helper (#71568)
This adds a new helper that can be called from application code to ensure that no mutexes are held on specific code paths. This is useful for multiple scenarios, including ensuring no locks are held: - at thread exit - in peformance-critical code - when a coroutine is suspended (can cause deadlocks) See this discourse thread for more discussion: https://discourse.llvm.org/t/add-threadsanitizer-check-to-prevent-coroutine-suspending-while-holding-a-lock-potential-deadlock/74051 This resubmits and fixes #69372 (was reverted because of build breakage). This also includes the followup change #71471 (to fix a land race).
1 parent 5f29555 commit 1146d96

File tree

8 files changed

+70
-3
lines changed

8 files changed

+70
-3
lines changed

compiler-rt/include/sanitizer/tsan_interface.h

+4
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ void SANITIZER_CDECL __tsan_mutex_post_signal(void *addr, unsigned flags);
127127
void SANITIZER_CDECL __tsan_mutex_pre_divert(void *addr, unsigned flags);
128128
void SANITIZER_CDECL __tsan_mutex_post_divert(void *addr, unsigned flags);
129129

130+
// Check that the current thread does not hold any mutexes,
131+
// report a bug report otherwise.
132+
void SANITIZER_CDECL __tsan_check_no_mutexes_held();
133+
130134
// External race detection API.
131135
// Can be used by non-instrumented libraries to detect when their objects are
132136
// being used in an unsafe manner.

compiler-rt/lib/tsan/rtl/tsan.syms.extra

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ __tsan_mutex_pre_signal
2222
__tsan_mutex_post_signal
2323
__tsan_mutex_pre_divert
2424
__tsan_mutex_post_divert
25+
__tsan_check_no_mutexes_held
2526
__tsan_get_current_fiber
2627
__tsan_create_fiber
2728
__tsan_destroy_fiber

compiler-rt/lib/tsan/rtl/tsan_debugging.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ static const char *ReportTypeDescription(ReportType typ) {
3535
case ReportTypeSignalUnsafe: return "signal-unsafe-call";
3636
case ReportTypeErrnoInSignal: return "errno-in-signal-handler";
3737
case ReportTypeDeadlock: return "lock-order-inversion";
38-
// No default case so compiler warns us if we miss one
38+
case ReportTypeMutexHeldWrongContext:
39+
return "mutex-held-in-wrong-context";
40+
// No default case so compiler warns us if we miss one
3941
}
4042
UNREACHABLE("missing case");
4143
}

compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,26 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
435435
ThreadIgnoreBegin(thr, 0);
436436
ThreadIgnoreSyncBegin(thr, 0);
437437
}
438+
439+
static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
440+
ThreadRegistryLock l(&ctx->thread_registry);
441+
ScopedReport rep(ReportTypeMutexHeldWrongContext);
442+
for (uptr i = 0; i < thr->mset.Size(); ++i) {
443+
MutexSet::Desc desc = thr->mset.Get(i);
444+
rep.AddMutex(desc.addr, desc.stack_id);
445+
}
446+
VarSizeStackTrace trace;
447+
ObtainCurrentStack(thr, pc, &trace);
448+
rep.AddStack(trace, true);
449+
OutputReport(thr, rep);
450+
}
451+
452+
INTERFACE_ATTRIBUTE
453+
void __tsan_check_no_mutexes_held() {
454+
SCOPED_ANNOTATION(__tsan_check_no_mutexes_held);
455+
if (thr->mset.Size() == 0) {
456+
return;
457+
}
458+
ReportMutexHeldWrongContext(thr, pc);
459+
}
438460
} // extern "C"

compiler-rt/lib/tsan/rtl/tsan_report.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ static const char *ReportTypeString(ReportType typ, uptr tag) {
9393
return "signal handler spoils errno";
9494
case ReportTypeDeadlock:
9595
return "lock-order-inversion (potential deadlock)";
96-
// No default case so compiler warns us if we miss one
96+
case ReportTypeMutexHeldWrongContext:
97+
return "mutex held in the wrong context";
98+
// No default case so compiler warns us if we miss one
9799
}
98100
UNREACHABLE("missing case");
99101
}

compiler-rt/lib/tsan/rtl/tsan_report.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ enum ReportType {
3434
ReportTypeMutexBadReadUnlock,
3535
ReportTypeSignalUnsafe,
3636
ReportTypeErrnoInSignal,
37-
ReportTypeDeadlock
37+
ReportTypeDeadlock,
38+
ReportTypeMutexHeldWrongContext
3839
};
3940

4041
struct ReportStack {

compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static const char *conv(ReportType typ) {
8181
case ReportTypeMutexBadUnlock:
8282
case ReportTypeMutexBadReadLock:
8383
case ReportTypeMutexBadReadUnlock:
84+
case ReportTypeMutexHeldWrongContext:
8485
return kSuppressionMutex;
8586
case ReportTypeSignalUnsafe:
8687
case ReportTypeErrnoInSignal:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s
2+
#include "test.h"
3+
4+
pthread_mutex_t mtx;
5+
6+
__attribute__((noinline)) void Func1() {
7+
pthread_mutex_lock(&mtx);
8+
__tsan_check_no_mutexes_held();
9+
pthread_mutex_unlock(&mtx);
10+
}
11+
12+
__attribute__((noinline)) void Func2() {
13+
pthread_mutex_lock(&mtx);
14+
pthread_mutex_unlock(&mtx);
15+
__tsan_check_no_mutexes_held();
16+
}
17+
18+
int main() {
19+
pthread_mutex_init(&mtx, NULL);
20+
Func1();
21+
Func2();
22+
return 0;
23+
}
24+
25+
// CHECK: WARNING: ThreadSanitizer: mutex held in the wrong context
26+
// CHECK: {{.*}}__tsan_check_no_mutexes_held{{.*}}
27+
// CHECK: {{.*}}Func1{{.*}}
28+
// CHECK: {{.*}}main{{.*}}
29+
// CHECK: Mutex {{.*}} created at:
30+
// CHECK: {{.*}}pthread_mutex_init{{.*}}
31+
// CHECK: {{.*}}main{{.*}}
32+
// CHECK: SUMMARY: ThreadSanitizer: mutex held in the wrong context {{.*}}mutex_held_wrong_context.cpp{{.*}}Func1
33+
34+
// CHECK-NOT: SUMMARY: ThreadSanitizer: mutex held in the wrong context {{.*}}mutex_held_wrong_context.cpp{{.*}}Func2

0 commit comments

Comments
 (0)