[compiler-rt] [TSan] Defer symbolization in AddStack and AddSleep to SymbolizeStackElems (PR #228795)
Vitaly Buka via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 5 22:59:56 PDT 2026
https://github.com/vitalybuka updated https://github.com/llvm/llvm-project/pull/228795
>From 3b85900dbdb9032f73b6ee8db76a1ffcc63ab0fe Mon Sep 17 00:00:00 2001
From: Vitaly Buka <vitalybuka at google.com>
Date: Sat, 3 Oct 2026 15:32:58 -0700
Subject: [PATCH 1/5] [NFC][TSan] Use in-class member initializers in
tsan_report.h
Use in-class member initializers for all structs and classes in
tsan_report.h and default constructors and destructor in
tsan_report.cpp.
Assisted-by: Gemini
Pull Request: https://github.com/llvm/llvm-project/pull/228780
---
compiler-rt/lib/tsan/rtl/tsan_report.cpp | 22 +++------
compiler-rt/lib/tsan/rtl/tsan_report.h | 57 ++++++++++++------------
2 files changed, 34 insertions(+), 45 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
index 17fd3064fb85a..ca04f9e1faf07 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
@@ -29,25 +29,13 @@ class Decorator: public __sanitizer::SanitizerCommonDecorator {
const char *Mutex() { return Magenta(); }
};
-ReportDesc::ReportDesc()
- : tag(kExternalTagNone)
- , stacks()
- , mops()
- , locs()
- , mutexes()
- , threads()
- , unique_tids()
- , sleep()
- , count() {
-}
+ReportDesc::ReportDesc() = default;
-ReportMop::ReportMop()
- : mset() {
-}
+ReportMop::ReportMop() = default;
+ReportMop::~ReportMop() = default;
-ReportDesc::~ReportDesc() {
- // FIXME(dvyukov): it must be leaking a lot of memory.
-}
+// FIXME(dvyukov): it must be leaking a lot of memory.
+ReportDesc::~ReportDesc() = default;
#if !SANITIZER_GO
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.h b/compiler-rt/lib/tsan/rtl/tsan_report.h
index 53bb21964dbba..a664bc5d49332 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.h
@@ -46,22 +46,23 @@ struct ReportStack {
};
struct ReportMopMutex {
- int id;
- bool write;
+ int id = 0;
+ bool write = false;
};
struct ReportMop {
- int tid;
- uptr addr;
- int size;
- bool write;
- bool atomic;
- uptr external_tag;
+ int tid = kInvalidTid;
+ uptr addr = 0;
+ int size = 0;
+ bool write = false;
+ bool atomic = false;
+ uptr external_tag = 0;
Vector<ReportMopMutex> mset;
StackTrace stack_trace;
- ReportStack *stack;
+ ReportStack* stack = nullptr;
ReportMop();
+ ~ReportMop();
};
enum ReportLocationType {
@@ -87,33 +88,33 @@ struct ReportLocation {
};
struct ReportThread {
- Tid id;
- ThreadID os_id;
- bool running;
- ThreadType thread_type;
- char *name;
- Tid parent_tid;
- StackID stack_id;
- ReportStack *stack;
- bool suppressable;
+ Tid id = kInvalidTid;
+ ThreadID os_id = 0;
+ bool running = false;
+ ThreadType thread_type = ThreadType::Regular;
+ char* name = nullptr;
+ Tid parent_tid = kInvalidTid;
+ StackID stack_id = 0;
+ ReportStack* stack = nullptr;
+ bool suppressable = false;
};
struct ReportMutex {
- int id;
- uptr addr;
- StackID stack_id;
- ReportStack *stack;
+ int id = 0;
+ uptr addr = 0;
+ StackID stack_id = 0;
+ ReportStack* stack = nullptr;
};
struct AddedLocationAddr {
- uptr addr;
- usize locs_idx;
+ uptr addr = 0;
+ usize locs_idx = 0;
};
class ReportDesc {
public:
- ReportType typ;
- uptr tag;
+ ReportType typ = ReportTypeRace;
+ uptr tag = kExternalTagNone;
Vector<ReportStack*> stacks;
Vector<ReportMop*> mops;
Vector<ReportLocation*> locs;
@@ -121,8 +122,8 @@ class ReportDesc {
Vector<ReportMutex*> mutexes;
Vector<ReportThread*> threads;
Vector<Tid> unique_tids;
- ReportStack *sleep;
- int count;
+ ReportStack* sleep = nullptr;
+ int count = 0;
int signum = 0;
ReportDesc();
>From 8d88d4f44e6af09bd593d65915a206d0beb792b7 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <vitalybuka at google.com>
Date: Sat, 3 Oct 2026 19:00:54 -0700
Subject: [PATCH 2/5] [NFC][TSan] Move ObtainCurrentStack out of
ThreadRegistryLock scope
ObtainCurrentStack only reads the current thread's shadow stack and
allocates a VarSizeStackTrace buffer, which does not require
ThreadRegistryLock (and already runs outside ThreadRegistryLock in
ReportRace, SignalUnsafeCall, and ReportErrnoSpoiling).
Move ObtainCurrentStack (and dummy_pc in ReportDeadlock) before
ScopedReport in ReportMutexHeldWrongContext, ReportMutexMisuse,
ReportDeadlock, and ReportDestroyLocked so the stack trace buffers also
outlive ScopedReport and OutputReport.
Assisted-by: Gemini
Pull Request: https://github.com/llvm/llvm-project/pull/228794
---
compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp | 4 ++--
compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp | 11 ++++++-----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
index 526dc5a753c24..e520ead1d36aa 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
@@ -441,6 +441,8 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
}
static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
+ VarSizeStackTrace trace;
+ ObtainCurrentStack(thr, pc, &trace);
// Use alloca, because malloc during signal handling deadlocks
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
// Release locks before symbolizing and outputting the report to avoid
@@ -452,8 +454,6 @@ static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
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);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
index 271412f4c4088..71f77596c8ae2 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
@@ -56,6 +56,8 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
return;
if (!ShouldReport(thr, typ))
return;
+ VarSizeStackTrace trace;
+ ObtainCurrentStack(thr, pc, &trace);
// Use alloca, because malloc during signal handling deadlocks
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
// Release locks before symbolizing and outputting the report to avoid
@@ -64,8 +66,6 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(typ);
rep->AddMutex(addr, creation_stack_id);
- VarSizeStackTrace trace;
- ObtainCurrentStack(thr, pc, &trace);
rep->AddStack(trace, true);
rep->AddLocation(addr, 1);
}
@@ -538,6 +538,7 @@ void AfterSleep(ThreadState *thr, uptr pc) {
void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock))
return;
+ uptr dummy_pc = 0x42;
// Use alloca, because malloc during signal handling deadlocks
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
// Release locks before symbolizing and outputting the report to avoid
@@ -550,7 +551,6 @@ void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
rep->AddUniqueTid((int)r->loop[i].thr_ctx);
rep->AddThread((int)r->loop[i].thr_ctx);
}
- uptr dummy_pc = 0x42;
for (int i = 0; i < r->n; i++) {
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
u32 stk = r->loop[i].stk[j];
@@ -590,6 +590,9 @@ void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
return;
}
+ VarSizeStackTrace trace;
+ ObtainCurrentStack(thr, pc, &trace);
+
// Use alloca, because malloc during signal handling deadlocks
ScopedReport* rep = (ScopedReport*)__builtin_alloca(sizeof(ScopedReport));
// Release locks before symbolizing and outputting the report to avoid
@@ -598,8 +601,6 @@ void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
ThreadRegistryLock l0(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeMutexDestroyLocked);
rep->AddMutex(addr, creation_stack_id);
- VarSizeStackTrace trace;
- ObtainCurrentStack(thr, pc, &trace);
rep->AddStack(trace, true);
rep->AddStack(last_lock_stack, true);
rep->AddLocation(addr, 1);
>From cfcffae34fca381b73264b7887b5239dbabba03d Mon Sep 17 00:00:00 2001
From: Vitaly Buka <vitalybuka at google.com>
Date: Sat, 3 Oct 2026 19:00:56 -0700
Subject: [PATCH 3/5] [TSan] Lock ScopedErrorReportLock before slot and
thread_registry locks
OutputReport runs while ScopedErrorReportLock is held after slot_mtx and
thread_registry have been unlocked. Because code executed during
OutputReport (symbolizer, callbacks, or signal handlers) can acquire
slot_mtx or thread_registry, ScopedErrorReportLock must precede slot and
thread_registry locks in the lock hierarchy to avoid AB-BA deadlocks
between concurrent reports or fork().
- Move ScopedErrorReportLock::Lock() before slot.mtx, thread_registry,
and slot_mtx in ForkBefore (and unlock in reverse order in ForkAfter).
- Replace ctx->thread_registry.CheckLocked() in ScopedReportBase's
constructor with CheckedMutex::CheckNoLocks(), and add CheckLocked() to
AddThread(const ThreadContext *) and CheckNoLocks() to OutputReport.
- Construct ScopedReport before acquiring ThreadRegistryLock across all
reporting functions, and close the RestoreStack lock scope before
constructing ScopedReport in ReportRace.
Assisted-by: Gemini
Pull Request: https://github.com/llvm/llvm-project/pull/228614
---
.../lib/tsan/rtl/tsan_interceptors_posix.cpp | 2 +-
.../lib/tsan/rtl/tsan_interface_ann.cpp | 2 +-
compiler-rt/lib/tsan/rtl/tsan_mman.cpp | 2 +-
compiler-rt/lib/tsan/rtl/tsan_rtl.cpp | 4 +-
compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp | 6 +--
compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp | 39 +++++++++++--------
compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp | 2 +-
7 files changed, 31 insertions(+), 26 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
index b2ba666741e89..a8d502966bcca 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -2182,8 +2182,8 @@ static void ReportErrnoSpoiling(ThreadState *thr, uptr pc, int sig) {
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeErrnoInSignal);
+ ThreadRegistryLock l(&ctx->thread_registry);
rep->SetSigNum(sig);
suppressed = IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack);
if (!suppressed)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
index e520ead1d36aa..acd5066dee07b 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
@@ -448,8 +448,8 @@ static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeMutexHeldWrongContext);
+ ThreadRegistryLock l(&ctx->thread_registry);
for (uptr i = 0; i < thr->mset.Size(); ++i) {
MutexSet::Desc desc = thr->mset.Get(i);
rep->AddMutex(desc.addr, desc.stack_id);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
index cea833054551d..88cdf58af8d58 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
@@ -187,8 +187,8 @@ static void SignalUnsafeCall(ThreadState *thr, uptr pc) {
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeSignalUnsafe);
+ ThreadRegistryLock l(&ctx->thread_registry);
rep->AddStack(stack, true);
}
OutputReport(thr, *rep);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp
index c6e7dce5ecb18..7328caab0a918 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp
@@ -841,10 +841,10 @@ void ForkBefore(ThreadState* thr, uptr pc) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
// Detaching from the slot makes OnUserFree skip writing to the shadow.
// The slot will be locked so any attempts to use it will deadlock anyway.
SlotDetach(thr);
+ ScopedErrorReportLock::Lock();
for (auto& slot : ctx->slots) slot.mtx.Lock();
ctx->thread_registry.Lock();
ctx->slot_mtx.Lock();
- ScopedErrorReportLock::Lock();
AllocatorLockBeforeFork();
// Suppress all reports in the pthread_atfork callbacks.
// Reports may deadlock.
@@ -871,10 +871,10 @@ static void ForkAfter(ThreadState* thr,
thr->ignore_interceptors--;
thr->ignore_reads_and_writes--;
AllocatorUnlockAfterFork(child);
- ScopedErrorReportLock::Unlock();
ctx->slot_mtx.Unlock();
ctx->thread_registry.Unlock();
for (auto& slot : ctx->slots) slot.mtx.Unlock();
+ ScopedErrorReportLock::Unlock();
SlotAttachAndLock(thr);
SlotUnlock(thr);
GlobalProcessorUnlock();
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
index 71f77596c8ae2..f0261b71f7009 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
@@ -63,8 +63,8 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(typ);
+ ThreadRegistryLock l(&ctx->thread_registry);
rep->AddMutex(addr, creation_stack_id);
rep->AddStack(trace, true);
rep->AddLocation(addr, 1);
@@ -544,8 +544,8 @@ void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeDeadlock);
+ ThreadRegistryLock l(&ctx->thread_registry);
for (int i = 0; i < r->n; i++) {
rep->AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
rep->AddUniqueTid((int)r->loop[i].thr_ctx);
@@ -598,8 +598,8 @@ void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l0(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeMutexDestroyLocked);
+ ThreadRegistryLock l0(&ctx->thread_registry);
rep->AddMutex(addr, creation_stack_id);
rep->AddStack(trace, true);
rep->AddStack(last_lock_stack, true);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
index be444eec3a4b7..7b86e955300de 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
@@ -164,7 +164,7 @@ bool ShouldReport(ThreadState *thr, ReportType typ) {
}
ScopedReport::ScopedReport(ReportType typ, uptr tag) {
- ctx->thread_registry.CheckLocked();
+ CheckedMutex::CheckNoLocks();
rep_ = New<ReportDesc>();
rep_->typ = typ;
rep_->tag = tag;
@@ -256,6 +256,7 @@ void ScopedReport::AddUniqueTid(Tid unique_tid) {
}
void ScopedReport::AddThread(const ThreadContext* tctx, bool suppressable) {
+ ctx->thread_registry.CheckLocked();
for (uptr i = 0; i < rep_->threads.Size(); i++) {
if ((u32)rep_->threads[i]->id == tctx->tid)
return;
@@ -671,6 +672,7 @@ static bool HandleRacyStacks(ThreadState *thr, VarSizeStackTrace traces[2]) {
}
bool OutputReport(ThreadState *thr, ScopedReport &srep) {
+ CheckedMutex::CheckNoLocks();
// These should have been checked in ShouldReport.
// It's too late to check them here, we have already taken locks.
CHECK(flags()->report_bugs);
@@ -804,10 +806,6 @@ void ReportRace(ThreadState *thr, RawShadow *shadow_mem, Shadow cur, Shadow old,
DynamicMutexSet mset1;
MutexSet *mset[kMop] = {&thr->mset, mset1};
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
- // Release locks before symbolizing and outputting the report to avoid
- // deadlocks.
{
// We need to lock the slot during RestoreStack because it protects
// the slot journal.
@@ -821,24 +819,31 @@ void ReportRace(ThreadState *thr, RawShadow *shadow_mem, Shadow cur, Shadow old,
StoreShadow(&ctx->last_spurious_race, old.raw());
return;
}
+ }
- if (IsFiredSuppression(ctx, rep_typ, traces[1]))
- return;
+ if (IsFiredSuppression(ctx, rep_typ, traces[1]))
+ return;
- if (HandleRacyStacks(thr, traces))
- return;
+ if (HandleRacyStacks(thr, traces))
+ return;
- // If any of the accesses has a tag, treat this as an "external" race.
- uptr tag = kExternalTagNone;
- for (uptr i = 0; i < kMop; i++) {
- if (tags[i] != kExternalTagNone) {
- rep_typ = ReportTypeExternalRace;
- tag = tags[i];
- break;
- }
+ // If any of the accesses has a tag, treat this as an "external" race.
+ uptr tag = kExternalTagNone;
+ for (uptr i = 0; i < kMop; i++) {
+ if (tags[i] != kExternalTagNone) {
+ rep_typ = ReportTypeExternalRace;
+ tag = tags[i];
+ break;
}
+ }
+ // Use alloca, because malloc during signal handling deadlocks
+ ScopedReport* rep = (ScopedReport*)__builtin_alloca(sizeof(ScopedReport));
+ // Release locks before symbolizing and outputting the report to avoid
+ // deadlocks.
+ {
new (rep) ScopedReport(rep_typ, tag);
+ ThreadRegistryLock l0(&ctx->thread_registry);
for (uptr i = 0; i < kMop; i++)
rep->AddMemoryAccess(addr, tags[i], s[i], tids[i], traces[i], mset[i]);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
index afc6b8d4a0b1c..c408024e77e60 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
@@ -101,8 +101,8 @@ void ThreadFinalize(ThreadState *thr) {
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeThreadLeak);
+ ThreadRegistryLock l(&ctx->thread_registry);
rep->AddThread(leaks[i].tctx, true);
rep->SetCount(leaks[i].count);
}
>From e03bdae605f3b20a915a509af92233a1a254b13c Mon Sep 17 00:00:00 2001
From: Vitaly Buka <vitalybuka at google.com>
Date: Sat, 3 Oct 2026 19:00:58 -0700
Subject: [PATCH 4/5] [NFC][TSan] Allocate ScopedReport as a stack variable
Now that ScopedReport is constructed before acquiring ThreadRegistryLock
or slot locks across all reporting functions, it no longer needs to be
constructed inside the lock scope via placement new on __builtin_alloca
storage.
Declare ScopedReport as a normal stack variable before the lock scope
and remove the manual destructor calls.
Assisted-by: Gemini
Pull Request: https://github.com/llvm/llvm-project/pull/228637
---
.../lib/tsan/rtl/tsan_interceptors_posix.cpp | 13 ++---
.../lib/tsan/rtl/tsan_interface_ann.cpp | 13 ++---
compiler-rt/lib/tsan/rtl/tsan_mman.cpp | 11 ++--
compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp | 50 +++++++------------
compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp | 21 +++-----
compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp | 13 ++---
6 files changed, 40 insertions(+), 81 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
index a8d502966bcca..233985c419be1 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
@@ -2176,24 +2176,19 @@ static void ReportErrnoSpoiling(ThreadState *thr, uptr pc, int sig) {
// StackTrace::GetNestInstructionPc(pc) is used because return address is
// expected, OutputReport() will undo this.
ObtainCurrentStack(thr, StackTrace::GetNextInstructionPc(pc), &stack);
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeErrnoInSignal);
bool suppressed;
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeErrnoInSignal);
ThreadRegistryLock l(&ctx->thread_registry);
- rep->SetSigNum(sig);
+ rep.SetSigNum(sig);
suppressed = IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack);
if (!suppressed)
- rep->AddStack(stack, true);
+ rep.AddStack(stack, true);
}
if (!suppressed)
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
static void CallUserSignalHandler(ThreadState *thr, bool sync, bool acquire,
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
index acd5066dee07b..11311ce8e901f 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
@@ -443,23 +443,18 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeMutexHeldWrongContext);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeMutexHeldWrongContext);
ThreadRegistryLock l(&ctx->thread_registry);
for (uptr i = 0; i < thr->mset.Size(); ++i) {
MutexSet::Desc desc = thr->mset.Get(i);
- rep->AddMutex(desc.addr, desc.stack_id);
+ rep.AddMutex(desc.addr, desc.stack_id);
}
- rep->AddStack(trace, true);
+ rep.AddStack(trace, true);
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
INTERFACE_ATTRIBUTE
diff --git a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
index 88cdf58af8d58..7a39b8a71a0a7 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp
@@ -182,19 +182,14 @@ static void SignalUnsafeCall(ThreadState *thr, uptr pc) {
ObtainCurrentStack(thr, pc, &stack);
if (IsFiredSuppression(ctx, ReportTypeSignalUnsafe, stack))
return;
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeSignalUnsafe);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeSignalUnsafe);
ThreadRegistryLock l(&ctx->thread_registry);
- rep->AddStack(stack, true);
+ rep.AddStack(stack, true);
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
index f0261b71f7009..8ae9c1f4b20e9 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
@@ -11,7 +11,6 @@
//===----------------------------------------------------------------------===//
#include <sanitizer_common/sanitizer_deadlock_detector_interface.h>
-#include <sanitizer_common/sanitizer_placement_new.h>
#include <sanitizer_common/sanitizer_stackdepot.h>
#include "tsan_flags.h"
@@ -58,21 +57,16 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
return;
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(typ);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(typ);
ThreadRegistryLock l(&ctx->thread_registry);
- rep->AddMutex(addr, creation_stack_id);
- rep->AddStack(trace, true);
- rep->AddLocation(addr, 1);
+ rep.AddMutex(addr, creation_stack_id);
+ rep.AddStack(trace, true);
+ rep.AddLocation(addr, 1);
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
static void RecordMutexLock(ThreadState *thr, uptr pc, uptr addr,
@@ -539,17 +533,15 @@ void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock))
return;
uptr dummy_pc = 0x42;
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeDeadlock);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeDeadlock);
ThreadRegistryLock l(&ctx->thread_registry);
for (int i = 0; i < r->n; i++) {
- rep->AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
- rep->AddUniqueTid((int)r->loop[i].thr_ctx);
- rep->AddThread((int)r->loop[i].thr_ctx);
+ rep.AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
+ rep.AddUniqueTid((int)r->loop[i].thr_ctx);
+ rep.AddThread((int)r->loop[i].thr_ctx);
}
for (int i = 0; i < r->n; i++) {
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
@@ -562,14 +554,11 @@ void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
// but we should still produce some stack trace in the report.
stack = StackTrace(&dummy_pc, 1);
}
- rep->AddStack(stack, true);
+ rep.AddStack(stack, true);
}
}
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
@@ -593,22 +582,17 @@ void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport* rep = (ScopedReport*)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeMutexDestroyLocked);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeMutexDestroyLocked);
ThreadRegistryLock l0(&ctx->thread_registry);
- rep->AddMutex(addr, creation_stack_id);
- rep->AddStack(trace, true);
- rep->AddStack(last_lock_stack, true);
- rep->AddLocation(addr, 1);
+ rep.AddMutex(addr, creation_stack_id);
+ rep.AddStack(trace, true);
+ rep.AddStack(last_lock_stack, true);
+ rep.AddLocation(addr, 1);
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
} // namespace __tsan
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
index 7b86e955300de..52a462617ecb4 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
@@ -837,46 +837,41 @@ void ReportRace(ThreadState *thr, RawShadow *shadow_mem, Shadow cur, Shadow old,
}
}
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport* rep = (ScopedReport*)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(rep_typ, tag);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(rep_typ, tag);
ThreadRegistryLock l0(&ctx->thread_registry);
for (uptr i = 0; i < kMop; i++)
- rep->AddMemoryAccess(addr, tags[i], s[i], tids[i], traces[i], mset[i]);
+ rep.AddMemoryAccess(addr, tags[i], s[i], tids[i], traces[i], mset[i]);
for (uptr i = 0; i < kMop; i++) {
ThreadContext *tctx = static_cast<ThreadContext *>(
ctx->thread_registry.GetThreadLocked(tids[i]));
- rep->AddThread(tctx);
+ rep.AddThread(tctx);
}
- rep->AddLocation(addr_min, addr_max - addr_min);
+ rep.AddLocation(addr_min, addr_max - addr_min);
if (flags()->print_full_thread_history) {
- const ReportDesc *rep_desc = rep->GetReport();
+ const ReportDesc* rep_desc = rep.GetReport();
for (uptr i = 0; i < rep_desc->threads.Size(); i++) {
Tid parent_tid = rep_desc->threads[i]->parent_tid;
if (parent_tid == kMainTid || parent_tid == kInvalidTid)
continue;
ThreadContext *parent_tctx = static_cast<ThreadContext *>(
ctx->thread_registry.GetThreadLocked(parent_tid));
- rep->AddThread(parent_tctx);
+ rep.AddThread(parent_tctx);
}
}
#if !SANITIZER_GO
if (!((typ0 | typ1) & kAccessFree) &&
s[1].epoch() <= thr->last_sleep_clock.Get(s[1].sid()))
- rep->AddSleep(thr->last_sleep_stack_id);
+ rep.AddSleep(thr->last_sleep_stack_id);
#endif
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
void PrintCurrentStack(ThreadState *thr, uptr pc) {
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
index c408024e77e60..5edf0c16fdd0d 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
@@ -96,20 +96,15 @@ void ThreadFinalize(ThreadState *thr) {
}
for (uptr i = 0; i < leaks.Size(); i++) {
- // Use alloca, because malloc during signal handling deadlocks
- ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
+ ScopedReport rep(ReportTypeThreadLeak);
// Release locks before symbolizing and outputting the report to avoid
// deadlocks.
{
- new (rep) ScopedReport(ReportTypeThreadLeak);
ThreadRegistryLock l(&ctx->thread_registry);
- rep->AddThread(leaks[i].tctx, true);
- rep->SetCount(leaks[i].count);
+ rep.AddThread(leaks[i].tctx, true);
+ rep.SetCount(leaks[i].count);
}
- OutputReport(thr, *rep);
-
- // Need to manually destroy this because we used placement new to allocate
- rep->~ScopedReport();
+ OutputReport(thr, rep);
}
#endif
}
>From 61105abfe9f7b959b62725744c1c4920add05246 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <vitalybuka at google.com>
Date: Sat, 3 Oct 2026 19:01:00 -0700
Subject: [PATCH 5/5] [TSan] Defer symbolization in AddStack and AddSleep to
SymbolizeStackElems
PR #151495 delayed symbolization for memory accesses, locations,
threads, and mutexes until OutputReport (after ThreadRegistryLock is
released), but missed ScopedReport::AddStack and ScopedReport::AddSleep.
As a result, ReportRace (via AddSleep) and ReportMutexMisuse,
ReportDeadlock, and ReportDestroyLocked (via AddStack) still invoked the
symbolizer while holding ThreadRegistryLock.
Store the unsymbolized stack traces and sleep stack ID in ReportDesc and
symbolize them in ScopedReport::SymbolizeStackElems().
Assisted-by: Gemini
Pull Request: https://github.com/llvm/llvm-project/pull/228795
---
compiler-rt/lib/tsan/rtl/tsan_report.h | 7 +++++++
compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp | 19 +++++++++++++++----
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.h b/compiler-rt/lib/tsan/rtl/tsan_report.h
index a664bc5d49332..13171aa270684 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.h
@@ -111,11 +111,17 @@ struct AddedLocationAddr {
usize locs_idx = 0;
};
+struct AddedStack {
+ StackTrace stack_trace;
+ bool suppressable = false;
+};
+
class ReportDesc {
public:
ReportType typ = ReportTypeRace;
uptr tag = kExternalTagNone;
Vector<ReportStack*> stacks;
+ Vector<AddedStack> added_stacks;
Vector<ReportMop*> mops;
Vector<ReportLocation*> locs;
Vector<AddedLocationAddr> added_location_addrs;
@@ -123,6 +129,7 @@ class ReportDesc {
Vector<ReportThread*> threads;
Vector<Tid> unique_tids;
ReportStack* sleep = nullptr;
+ StackID sleep_stack_id = 0;
int count = 0;
int signum = 0;
diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
index 52a462617ecb4..5644456bb2864 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp
@@ -173,9 +173,7 @@ ScopedReport::ScopedReport(ReportType typ, uptr tag) {
ScopedReport::~ScopedReport() { DestroyAndFree(rep_); }
void ScopedReport::AddStack(StackTrace stack, bool suppressable) {
- ReportStack **rs = rep_->stacks.PushBack();
- *rs = SymbolizeStack(stack);
- (*rs)->suppressable = suppressable;
+ rep_->added_stacks.PushBack({stack, suppressable});
}
void ScopedReport::AddMemoryAccess(uptr addr, uptr external_tag, Shadow s,
@@ -202,6 +200,15 @@ void ScopedReport::AddMemoryAccess(uptr addr, uptr external_tag, Shadow s,
}
void ScopedReport::SymbolizeStackElems() {
+ // symbolize stacks
+ for (usize i = 0, size = rep_->added_stacks.Size(); i < size; i++) {
+ AddedStack& as = rep_->added_stacks[i];
+ ReportStack* rs = SymbolizeStack(as.stack_trace);
+ if (rs)
+ rs->suppressable = as.suppressable;
+ rep_->stacks.PushBack(rs);
+ }
+
// symbolize memory ops
for (usize i = 0, size = rep_->mops.Size(); i < size; i++) {
ReportMop *mop = rep_->mops[i];
@@ -249,6 +256,10 @@ void ScopedReport::SymbolizeStackElems() {
ReportMutex *rm = rep_->mutexes[i];
rm->stack = SymbolizeStackId(rm->stack_id);
}
+
+#if !SANITIZER_GO
+ rep_->sleep = SymbolizeStackId(rep_->sleep_stack_id);
+#endif
}
void ScopedReport::AddUniqueTid(Tid unique_tid) {
@@ -381,7 +392,7 @@ void ScopedReport::AddLocation(uptr addr, uptr size) {
#if !SANITIZER_GO
void ScopedReport::AddSleep(StackID stack_id) {
- rep_->sleep = SymbolizeStackId(stack_id);
+ rep_->sleep_stack_id = stack_id;
}
#endif
More information about the llvm-commits
mailing list