[compiler-rt] TSAN: Report when thread is not live and referenced in pthread (PR #156921)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 4 09:37:34 PDT 2025


https://github.com/ghostway0 created https://github.com/llvm/llvm-project/pull/156921

fixes #146683 #156829

outputs something like:
==29712==ThreadSanitizer: pthread_detach was called on thread 0 but it is dead.
detach rc1=0 rc2=-22

>From 311b18745e707156a0ff0883265164f5c9bada07 Mon Sep 17 00:00:00 2001
From: ghostway <ghostway at tuta.io>
Date: Thu, 4 Sep 2025 19:26:31 +0300
Subject: [PATCH] TSAN: Report when thread is not live and referenced in
 pthread

---
 .../sanitizer_thread_registry.cpp             | 17 +++++++++----
 .../sanitizer_thread_registry.h               |  2 +-
 .../lib/tsan/rtl/tsan_interceptors_posix.cpp  | 24 +++++++++++++++----
 compiler-rt/lib/tsan/rtl/tsan_rtl.h           |  2 +-
 compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp  |  4 ++--
 5 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
index d726d282437ca..b8f32b52c64d8 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
@@ -274,6 +274,11 @@ void ThreadRegistry::JoinThread(u32 tid, void *arg) {
     {
       ThreadRegistryLock l(this);
       ThreadContextBase *tctx = threads_[tid];
+
+      if (!tctx) {
+        Report("%s: Tried to join thread %u, but it is not live.", SanitizerToolName, tid);
+      }
+
       CHECK_NE(tctx, 0);
       if (tctx->status == ThreadStatusInvalid) {
         Report("%s: Join of non-existent thread\n", SanitizerToolName);
@@ -357,17 +362,19 @@ ThreadContextBase *ThreadRegistry::QuarantinePop() {
   return tctx;
 }
 
-u32 ThreadRegistry::ConsumeThreadUserId(uptr user_id) {
+bool ThreadRegistry::ConsumeThreadUserId(uptr user_id, u32 *tid_out) {
   ThreadRegistryLock l(this);
-  u32 tid;
   auto *t = live_.find(user_id);
-  CHECK(t);
-  tid = t->second;
+  if (!t) {
+    return false;
+  }
+  u32 tid = t->second;
   live_.erase(t);
   auto *tctx = threads_[tid];
   CHECK_EQ(tctx->user_id, user_id);
   tctx->user_id = 0;
-  return tid;
+  *tid_out = tid;
+  return true;
 }
 
 void ThreadRegistry::SetThreadUserId(u32 tid, uptr user_id) {
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
index 8adc420c8cce4..83459685fcb53 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
@@ -135,7 +135,7 @@ class SANITIZER_MUTEX ThreadRegistry {
   // Finishes thread and returns previous status.
   ThreadStatus FinishThread(u32 tid);
   void StartThread(u32 tid, ThreadID os_id, ThreadType thread_type, void *arg);
-  u32 ConsumeThreadUserId(uptr user_id);
+  bool ConsumeThreadUserId(uptr user_id, u32 *tid_out);
   void SetThreadUserId(u32 tid, uptr user_id);
 
   // OnFork must be called in the child process after fork to purge old
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
index b46a81031258c..4eabea5addd2a 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -1130,7 +1130,11 @@ TSAN_INTERCEPTOR(int, pthread_create,
 
 TSAN_INTERCEPTOR(int, pthread_join, void *th, void **ret) {
   SCOPED_INTERCEPTOR_RAW(pthread_join, th, ret);
-  Tid tid = ThreadConsumeTid(thr, pc, (uptr)th);
+  Tid tid;
+  if (!ThreadConsumeTid(thr, pc, (uptr)th, &tid)) {
+    Report("ThreadSanitizer: pthread_join was called on thread %d but it is dead.\n", thr->tid);
+    return -errno_EINVAL;
+  }
   ThreadIgnoreBegin(thr, pc);
   int res = BLOCK_REAL(pthread_join)(th, ret);
   ThreadIgnoreEnd(thr);
@@ -1155,7 +1159,11 @@ int internal_pthread_join(void *th, void **ret) {
 
 TSAN_INTERCEPTOR(int, pthread_detach, void *th) {
   SCOPED_INTERCEPTOR_RAW(pthread_detach, th);
-  Tid tid = ThreadConsumeTid(thr, pc, (uptr)th);
+  Tid tid;
+  if (!ThreadConsumeTid(thr, pc, (uptr)th, &tid)) {
+    Report("ThreadSanitizer: pthread_detach was called on thread %d but it is dead.\n", thr->tid);
+    return -errno_EINVAL;
+  }
   int res = REAL(pthread_detach)(th);
   if (res == 0) {
     ThreadDetach(thr, pc, tid);
@@ -1176,7 +1184,11 @@ TSAN_INTERCEPTOR(void, pthread_exit, void *retval) {
 #if SANITIZER_LINUX
 TSAN_INTERCEPTOR(int, pthread_tryjoin_np, void *th, void **ret) {
   SCOPED_INTERCEPTOR_RAW(pthread_tryjoin_np, th, ret);
-  Tid tid = ThreadConsumeTid(thr, pc, (uptr)th);
+  Tid tid;
+  if (!ThreadConsumeTid(thr, pc, (uptr)th, &tid)) {
+    Report("ThreadSanitizer: pthread_tryjoin_np was called on thread %d but it is dead.\n", thr->tid);
+    return -errno_EINVAL;
+  }
   ThreadIgnoreBegin(thr, pc);
   int res = REAL(pthread_tryjoin_np)(th, ret);
   ThreadIgnoreEnd(thr);
@@ -1190,7 +1202,11 @@ TSAN_INTERCEPTOR(int, pthread_tryjoin_np, void *th, void **ret) {
 TSAN_INTERCEPTOR(int, pthread_timedjoin_np, void *th, void **ret,
                  const struct timespec *abstime) {
   SCOPED_INTERCEPTOR_RAW(pthread_timedjoin_np, th, ret, abstime);
-  Tid tid = ThreadConsumeTid(thr, pc, (uptr)th);
+  Tid tid;
+  if (!ThreadConsumeTid(thr, pc, (uptr)th, &tid)) {
+    Report("ThreadSanitizer: pthread_timedjoin_np was called on thread %d but it is dead.\n", thr->tid);
+    return -errno_EINVAL;
+  }
   ThreadIgnoreBegin(thr, pc);
   int res = BLOCK_REAL(pthread_timedjoin_np)(th, ret, abstime);
   ThreadIgnoreEnd(thr);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.h b/compiler-rt/lib/tsan/rtl/tsan_rtl.h
index 0b6d5f088b142..03bd4d8de2e28 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.h
@@ -563,7 +563,7 @@ Tid ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached);
 void ThreadStart(ThreadState *thr, Tid tid, ThreadID os_id,
                  ThreadType thread_type);
 void ThreadFinish(ThreadState *thr);
-Tid ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid);
+bool ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid, Tid *tid_out);
 void ThreadJoin(ThreadState *thr, uptr pc, Tid tid);
 void ThreadDetach(ThreadState *thr, uptr pc, Tid tid);
 void ThreadFinalize(ThreadState *thr);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
index b1464ccfddeb4..fd33dde7e48db 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
@@ -301,8 +301,8 @@ struct ConsumeThreadContext {
   ThreadContextBase *tctx;
 };
 
-Tid ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid) {
-  return ctx->thread_registry.ConsumeThreadUserId(uid);
+bool ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid, Tid *tid_out) {
+  return ctx->thread_registry.ConsumeThreadUserId(uid, tid_out);
 }
 
 struct JoinArg {



More information about the llvm-commits mailing list