Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TSAN] Add __tsan_check_no_mutexes_held helper #71568

Merged
merged 1 commit into from
Nov 8, 2023

Conversation

kennyyu
Copy link
Contributor

@kennyyu kennyyu commented Nov 7, 2023

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).

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 llvm#69372 (was reverted because of build breakage).
This also includes the followup change llvm#71471 (to fix a land race).
@llvmbot
Copy link
Collaborator

llvmbot commented Nov 7, 2023

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Kenny Yu (kennyyu)

Changes

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).


Full diff: https://github.com/llvm/llvm-project/pull/71568.diff

8 Files Affected:

  • (modified) compiler-rt/include/sanitizer/tsan_interface.h (+4)
  • (modified) compiler-rt/lib/tsan/rtl/tsan.syms.extra (+1)
  • (modified) compiler-rt/lib/tsan/rtl/tsan_debugging.cpp (+3-1)
  • (modified) compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp (+22)
  • (modified) compiler-rt/lib/tsan/rtl/tsan_report.cpp (+3-1)
  • (modified) compiler-rt/lib/tsan/rtl/tsan_report.h (+2-1)
  • (modified) compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp (+1)
  • (added) compiler-rt/test/tsan/mutex_held_wrong_context.cpp (+34)
diff --git a/compiler-rt/include/sanitizer/tsan_interface.h b/compiler-rt/include/sanitizer/tsan_interface.h
index 3ef79ab81dd5312..e11a4175cd8ed9f 100644
--- a/compiler-rt/include/sanitizer/tsan_interface.h
+++ b/compiler-rt/include/sanitizer/tsan_interface.h
@@ -127,6 +127,10 @@ void SANITIZER_CDECL __tsan_mutex_post_signal(void *addr, unsigned flags);
 void SANITIZER_CDECL __tsan_mutex_pre_divert(void *addr, unsigned flags);
 void SANITIZER_CDECL __tsan_mutex_post_divert(void *addr, unsigned flags);
 
+// Check that the current thread does not hold any mutexes,
+// report a bug report otherwise.
+void SANITIZER_CDECL __tsan_check_no_mutexes_held();
+
 // External race detection API.
 // Can be used by non-instrumented libraries to detect when their objects are
 // being used in an unsafe manner.
diff --git a/compiler-rt/lib/tsan/rtl/tsan.syms.extra b/compiler-rt/lib/tsan/rtl/tsan.syms.extra
index a5bd17176b12bfa..6416e5d47fc4131 100644
--- a/compiler-rt/lib/tsan/rtl/tsan.syms.extra
+++ b/compiler-rt/lib/tsan/rtl/tsan.syms.extra
@@ -22,6 +22,7 @@ __tsan_mutex_pre_signal
 __tsan_mutex_post_signal
 __tsan_mutex_pre_divert
 __tsan_mutex_post_divert
+__tsan_check_no_mutexes_held
 __tsan_get_current_fiber
 __tsan_create_fiber
 __tsan_destroy_fiber
diff --git a/compiler-rt/lib/tsan/rtl/tsan_debugging.cpp b/compiler-rt/lib/tsan/rtl/tsan_debugging.cpp
index 1e61c31c5a970cc..41fa293dbaaad0b 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_debugging.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_debugging.cpp
@@ -35,7 +35,9 @@ static const char *ReportTypeDescription(ReportType typ) {
     case ReportTypeSignalUnsafe: return "signal-unsafe-call";
     case ReportTypeErrnoInSignal: return "errno-in-signal-handler";
     case ReportTypeDeadlock: return "lock-order-inversion";
-    // No default case so compiler warns us if we miss one
+    case ReportTypeMutexHeldWrongContext:
+      return "mutex-held-in-wrong-context";
+      // No default case so compiler warns us if we miss one
   }
   UNREACHABLE("missing case");
 }
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
index 6bd72e18d942580..5154662034c56d9 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
@@ -435,4 +435,26 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
   ThreadIgnoreBegin(thr, 0);
   ThreadIgnoreSyncBegin(thr, 0);
 }
+
+static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
+  ThreadRegistryLock l(&ctx->thread_registry);
+  ScopedReport rep(ReportTypeMutexHeldWrongContext);
+  for (uptr i = 0; i < thr->mset.Size(); ++i) {
+    MutexSet::Desc desc = thr->mset.Get(i);
+    rep.AddMutex(desc.addr, desc.stack_id);
+  }
+  VarSizeStackTrace trace;
+  ObtainCurrentStack(thr, pc, &trace);
+  rep.AddStack(trace, true);
+  OutputReport(thr, rep);
+}
+
+INTERFACE_ATTRIBUTE
+void __tsan_check_no_mutexes_held() {
+  SCOPED_ANNOTATION(__tsan_check_no_mutexes_held);
+  if (thr->mset.Size() == 0) {
+    return;
+  }
+  ReportMutexHeldWrongContext(thr, pc);
+}
 }  // extern "C"
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
index 4028d1810784095..35cb6710a54fa42 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
@@ -93,7 +93,9 @@ static const char *ReportTypeString(ReportType typ, uptr tag) {
       return "signal handler spoils errno";
     case ReportTypeDeadlock:
       return "lock-order-inversion (potential deadlock)";
-    // No default case so compiler warns us if we miss one
+    case ReportTypeMutexHeldWrongContext:
+      return "mutex held in the wrong context";
+      // No default case so compiler warns us if we miss one
   }
   UNREACHABLE("missing case");
 }
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.h b/compiler-rt/lib/tsan/rtl/tsan_report.h
index 3c88864af147704..bfe470797f8f77a 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.h
@@ -34,7 +34,8 @@ enum ReportType {
   ReportTypeMutexBadReadUnlock,
   ReportTypeSignalUnsafe,
   ReportTypeErrnoInSignal,
-  ReportTypeDeadlock
+  ReportTypeDeadlock,
+  ReportTypeMutexHeldWrongContext
 };
 
 struct ReportStack {
diff --git a/compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp b/compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp
index 9cdfa32a9343029..70642124990d7bd 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_suppressions.cpp
@@ -81,6 +81,7 @@ static const char *conv(ReportType typ) {
     case ReportTypeMutexBadUnlock:
     case ReportTypeMutexBadReadLock:
     case ReportTypeMutexBadReadUnlock:
+    case ReportTypeMutexHeldWrongContext:
       return kSuppressionMutex;
     case ReportTypeSignalUnsafe:
     case ReportTypeErrnoInSignal:
diff --git a/compiler-rt/test/tsan/mutex_held_wrong_context.cpp b/compiler-rt/test/tsan/mutex_held_wrong_context.cpp
new file mode 100644
index 000000000000000..68a67618917567b
--- /dev/null
+++ b/compiler-rt/test/tsan/mutex_held_wrong_context.cpp
@@ -0,0 +1,34 @@
+// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s
+#include "test.h"
+
+pthread_mutex_t mtx;
+
+__attribute__((noinline)) void Func1() {
+  pthread_mutex_lock(&mtx);
+  __tsan_check_no_mutexes_held();
+  pthread_mutex_unlock(&mtx);
+}
+
+__attribute__((noinline)) void Func2() {
+  pthread_mutex_lock(&mtx);
+  pthread_mutex_unlock(&mtx);
+  __tsan_check_no_mutexes_held();
+}
+
+int main() {
+  pthread_mutex_init(&mtx, NULL);
+  Func1();
+  Func2();
+  return 0;
+}
+
+// CHECK: WARNING: ThreadSanitizer: mutex held in the wrong context
+// CHECK:     {{.*}}__tsan_check_no_mutexes_held{{.*}}
+// CHECK:     {{.*}}Func1{{.*}}
+// CHECK:     {{.*}}main{{.*}}
+// CHECK:   Mutex {{.*}} created at:
+// CHECK:     {{.*}}pthread_mutex_init{{.*}}
+// CHECK:     {{.*}}main{{.*}}
+// CHECK: SUMMARY: ThreadSanitizer: mutex held in the wrong context {{.*}}mutex_held_wrong_context.cpp{{.*}}Func1
+
+// CHECK-NOT: SUMMARY: ThreadSanitizer: mutex held in the wrong context {{.*}}mutex_held_wrong_context.cpp{{.*}}Func2

@dvyukov dvyukov merged commit 1146d96 into llvm:main Nov 8, 2023
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants