[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 11:11:09 PDT 2025


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

>From f34691585ea2d0d59767f53de5547a5bee7b84ad 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             | 13 ++++---
 .../sanitizer_thread_registry.h               |  2 +-
 .../lib/tsan/rtl/tsan_interceptors_posix.cpp  | 36 ++++++++++++++++---
 compiler-rt/lib/tsan/rtl/tsan_rtl.h           |  2 +-
 compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp  |  4 +--
 compiler-rt/test/tsan/double_detach.c         | 18 ++++++++++
 compiler-rt/test/tsan/join_detach.c           | 18 ++++++++++
 7 files changed, 80 insertions(+), 13 deletions(-)
 create mode 100644 compiler-rt/test/tsan/double_detach.c
 create mode 100644 compiler-rt/test/tsan/join_detach.c

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
index d726d282437ca..076ab0ff10cc0 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
@@ -274,6 +274,7 @@ void ThreadRegistry::JoinThread(u32 tid, void *arg) {
     {
       ThreadRegistryLock l(this);
       ThreadContextBase *tctx = threads_[tid];
+
       CHECK_NE(tctx, 0);
       if (tctx->status == ThreadStatusInvalid) {
         Report("%s: Join of non-existent thread\n", SanitizerToolName);
@@ -357,17 +358,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..aa5f4e2d1c5d9 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -1130,7 +1130,14 @@ 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 +1162,14 @@ 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 +1190,14 @@ 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 +1211,14 @@ 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 {
diff --git a/compiler-rt/test/tsan/double_detach.c b/compiler-rt/test/tsan/double_detach.c
new file mode 100644
index 0000000000000..12fcf287f67b5
--- /dev/null
+++ b/compiler-rt/test/tsan/double_detach.c
@@ -0,0 +1,18 @@
+// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+#include <stdio.h>
+
+void *fn(void *) { return NULL; }
+
+int main() {
+  pthread_t th;
+  int rc = pthread_create(&th, 0, fn, 0);
+  if (rc)
+    return rc;
+  pthread_detach(th);
+  pthread_detach(th);
+  return 0;
+}
+
+// CHECK: ThreadSanitizer: pthread_detach was called on thread 0 but it is dead.
diff --git a/compiler-rt/test/tsan/join_detach.c b/compiler-rt/test/tsan/join_detach.c
new file mode 100644
index 0000000000000..ee2e335ce25bb
--- /dev/null
+++ b/compiler-rt/test/tsan/join_detach.c
@@ -0,0 +1,18 @@
+// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+#include <stdio.h>
+
+void *fn(void *) { return NULL; }
+
+int main() {
+  pthread_t th;
+  int rc = pthread_create(&th, 0, fn, 0);
+  if (rc)
+    return rc;
+  pthread_join(th, NULL);
+  pthread_detach(th);
+  return 0;
+}
+
+// CHECK: ThreadSanitizer: pthread_detach was called on thread 0 but it is dead.



More information about the llvm-commits mailing list