[compiler-rt] [NFCI] ASAN/TSAN stack descriptions available independent of report printing (PR #200736)

Andrew Haberlandt via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 00:02:40 PDT 2026


https://github.com/ndrewh created https://github.com/llvm/llvm-project/pull/200736

This refactors the various functions for printing ASAN/TSAN report components into PrintX and DescribeX, where DescribeX can be used to obtain a (unformatted) string description of a report component e.g. the string `Location is heap block of size %zu at %p allocated by %s:`.

This will make it easier to do something like #200526 downstream.

>From 68714bc055759e0922befc0ac90dbcc8776dd7e9 Mon Sep 17 00:00:00 2001
From: Andrew Haberlandt <ahaberlandt at apple.com>
Date: Sun, 31 May 2026 23:51:06 -0700
Subject: [PATCH] [NFC] Make ASAN/TSAN stack descriptions independent of report
 printing

This refactors the various functions for printing ASAN/TSAN report
components into PrintX and DescribeX, where DescribeX can be used
to obtain a (unformatted) string description of a report component
e.g. the string `Location is heap block of size %zu at %p allocated by %s:`.

This will make it easier to do something like #200526 downstream.
---
 compiler-rt/lib/asan/asan_descriptions.cpp |  32 +++--
 compiler-rt/lib/tsan/rtl/tsan_report.cpp   | 140 +++++++++++++--------
 2 files changed, 110 insertions(+), 62 deletions(-)

diff --git a/compiler-rt/lib/asan/asan_descriptions.cpp b/compiler-rt/lib/asan/asan_descriptions.cpp
index 551b819a4c436..839f9a1ffae33 100644
--- a/compiler-rt/lib/asan/asan_descriptions.cpp
+++ b/compiler-rt/lib/asan/asan_descriptions.cpp
@@ -36,6 +36,25 @@ AsanThreadIdAndName::AsanThreadIdAndName(u32 tid)
   asanThreadRegistry().CheckLocked();
 }
 
+// Writes a color-free, single-line description of a heap allocation or
+// deallocation stack, e.g. "freed by thread T1 here:".
+static void DescribeAllocStack(const char *verb, AsanThreadContext *thread,
+                               InternalScopedString *s) {
+  s->AppendF("%s by thread %s here:", verb,
+             AsanThreadIdAndName(thread).c_str());
+}
+
+// Prints the description produced by DescribeAllocStack (decorated and followed
+// by a newline) and then the given stack trace.
+static void PrintAllocStack(const char *verb, AsanThreadContext *thread,
+                            const StackTrace &stack) {
+  Decorator d;
+  InternalScopedString s;
+  DescribeAllocStack(verb, thread, &s);
+  Printf("%s%s%s\n", d.Allocation(), s.data(), d.Default());
+  stack.Print();
+}
+
 // Prints this thread and, if flags()->print_full_thread_history, its ancestors
 void DescribeThread(AsanThreadContext *context) {
   while (true) {
@@ -424,21 +443,14 @@ void HeapAddressDescription::Print() const {
   AsanThreadContext *alloc_thread = GetThreadContextByTidLocked(alloc_tid);
   StackTrace alloc_stack = GetStackTraceFromId(alloc_stack_id);
 
-  Decorator d;
   AsanThreadContext *free_thread = nullptr;
   if (free_tid != kInvalidTid) {
     free_thread = GetThreadContextByTidLocked(free_tid);
-    Printf("%sfreed by thread %s here:%s\n", d.Allocation(),
-           AsanThreadIdAndName(free_thread).c_str(), d.Default());
-    StackTrace free_stack = GetStackTraceFromId(free_stack_id);
-    free_stack.Print();
-    Printf("%spreviously allocated by thread %s here:%s\n", d.Allocation(),
-           AsanThreadIdAndName(alloc_thread).c_str(), d.Default());
+    PrintAllocStack("freed", free_thread, GetStackTraceFromId(free_stack_id));
+    PrintAllocStack("previously allocated", alloc_thread, alloc_stack);
   } else {
-    Printf("%sallocated by thread %s here:%s\n", d.Allocation(),
-           AsanThreadIdAndName(alloc_thread).c_str(), d.Default());
+    PrintAllocStack("allocated", alloc_thread, alloc_stack);
   }
-  alloc_stack.Print();
   DescribeThread(GetCurrentThread());
   if (free_thread) DescribeThread(free_thread);
   DescribeThread(alloc_thread);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
index 17fd3064fb85a..c6f9c09cd2934 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_report.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_report.cpp
@@ -139,68 +139,88 @@ static const char *ExternalMopDesc(bool first, bool write) {
                : (write ? "Previous modifying" : "Previous read-only");
 }
 
-static void PrintMop(const ReportMop *mop, bool first) {
-  Decorator d;
+static void DescribeMop(const ReportMop *mop, bool first,
+                        InternalScopedString *s) {
   char thrbuf[kThreadBufSize];
-  Printf("%s", d.Access());
   if (mop->external_tag == kExternalTagNone) {
-    Printf("  %s of size %d at %p by %s",
-           MopDesc(first, mop->write, mop->atomic), mop->size,
-           (void *)mop->addr, thread_name(thrbuf, mop->tid));
+    s->AppendF("  %s of size %d at %p by %s",
+               MopDesc(first, mop->write, mop->atomic), mop->size,
+               (void *)mop->addr, thread_name(thrbuf, mop->tid));
   } else {
     const char *object_type = GetObjectTypeFromTag(mop->external_tag);
     if (object_type == nullptr)
         object_type = "external object";
-    Printf("  %s access of %s at %p by %s",
-           ExternalMopDesc(first, mop->write), object_type,
-           (void *)mop->addr, thread_name(thrbuf, mop->tid));
+    s->AppendF("  %s access of %s at %p by %s",
+               ExternalMopDesc(first, mop->write), object_type,
+               (void *)mop->addr, thread_name(thrbuf, mop->tid));
   }
+}
+
+static void PrintMop(const ReportMop *mop, bool first) {
+  Decorator d;
+  Printf("%s", d.Access());
+  InternalScopedString s;
+  DescribeMop(mop, first, &s);
+  Printf("%s", s.data());
   PrintMutexSet(mop->mset);
   Printf(":\n");
   Printf("%s", d.Default());
   PrintStack(mop->stack);
 }
 
-static void PrintLocation(const ReportLocation *loc) {
-  Decorator d;
+// Writes the (uncolored) location description and returns whether a stack
+// trace should follow (true only for Heap and FD locations).
+static bool DescribeLocation(const ReportLocation *loc,
+                             InternalScopedString *s) {
   char thrbuf[kThreadBufSize];
   bool print_stack = false;
-  Printf("%s", d.Location());
   if (loc->type == ReportLocationGlobal) {
     const DataInfo &global = loc->global;
     if (global.size != 0)
-      Printf("  Location is global '%s' of size %zu at %p (%s+0x%zx)\n\n",
-             global.name, global.size, reinterpret_cast<void *>(global.start),
-             StripModuleName(global.module), global.module_offset);
+      s->AppendF("  Location is global '%s' of size %zu at %p (%s+0x%zx)",
+                 global.name, global.size,
+                 reinterpret_cast<void *>(global.start),
+                 StripModuleName(global.module), global.module_offset);
     else
-      Printf("  Location is global '%s' at %p (%s+0x%zx)\n\n", global.name,
-             reinterpret_cast<void *>(global.start),
-             StripModuleName(global.module), global.module_offset);
+      s->AppendF("  Location is global '%s' at %p (%s+0x%zx)", global.name,
+                 reinterpret_cast<void *>(global.start),
+                 StripModuleName(global.module), global.module_offset);
   } else if (loc->type == ReportLocationHeap) {
-    char thrbuf[kThreadBufSize];
     const char *object_type = GetObjectTypeFromTag(loc->external_tag);
     if (!object_type) {
-      Printf("  Location is heap block of size %zu at %p allocated by %s:\n",
-             loc->heap_chunk_size,
-             reinterpret_cast<void *>(loc->heap_chunk_start),
-             thread_name(thrbuf, loc->tid));
+      s->AppendF("  Location is heap block of size %zu at %p allocated by %s:",
+                 loc->heap_chunk_size,
+                 reinterpret_cast<void *>(loc->heap_chunk_start),
+                 thread_name(thrbuf, loc->tid));
     } else {
-      Printf("  Location is %s of size %zu at %p allocated by %s:\n",
-             object_type, loc->heap_chunk_size,
-             reinterpret_cast<void *>(loc->heap_chunk_start),
-             thread_name(thrbuf, loc->tid));
+      s->AppendF("  Location is %s of size %zu at %p allocated by %s:",
+                 object_type, loc->heap_chunk_size,
+                 reinterpret_cast<void *>(loc->heap_chunk_start),
+                 thread_name(thrbuf, loc->tid));
     }
     print_stack = true;
   } else if (loc->type == ReportLocationStack) {
-    Printf("  Location is stack of %s.\n\n", thread_name(thrbuf, loc->tid));
+    s->AppendF("  Location is stack of %s.", thread_name(thrbuf, loc->tid));
   } else if (loc->type == ReportLocationTLS) {
-    Printf("  Location is TLS of %s.\n\n", thread_name(thrbuf, loc->tid));
+    s->AppendF("  Location is TLS of %s.", thread_name(thrbuf, loc->tid));
   } else if (loc->type == ReportLocationFD) {
-    Printf("  Location is file descriptor %d %s by %s at:\n", loc->fd,
-           loc->fd_closed ? "destroyed" : "created",
-           thread_name(thrbuf, loc->tid));
+    s->AppendF("  Location is file descriptor %d %s by %s at:", loc->fd,
+               loc->fd_closed ? "destroyed" : "created",
+               thread_name(thrbuf, loc->tid));
     print_stack = true;
   }
+  return print_stack;
+}
+
+static void PrintLocation(const ReportLocation *loc) {
+  Decorator d;
+  Printf("%s", d.Location());
+  InternalScopedString s;
+  bool print_stack = DescribeLocation(loc, &s);
+  Printf("%s", s.data());
+  // Locations with a following stack are separated from it by a single
+  // newline; the others provide their own trailing blank line.
+  Printf(print_stack ? "\n" : "\n\n");
   Printf("%s", d.Default());
   if (print_stack)
     PrintStack(loc->stack);
@@ -218,39 +238,55 @@ static void PrintMutexShortWithAddress(const ReportMutex *rm,
          reinterpret_cast<void *>(rm->addr), d.Default(), after);
 }
 
+static void DescribeMutex(const ReportMutex *rm, InternalScopedString *s) {
+  s->AppendF("  Mutex M%u (%p) created at:", rm->id,
+             reinterpret_cast<void *>(rm->addr));
+}
+
 static void PrintMutex(const ReportMutex *rm) {
   Decorator d;
   Printf("%s", d.Mutex());
-  Printf("  Mutex M%u (%p) created at:\n", rm->id,
-         reinterpret_cast<void *>(rm->addr));
+  InternalScopedString s;
+  DescribeMutex(rm, &s);
+  Printf("%s\n", s.data());
   Printf("%s", d.Default());
   PrintStack(rm->stack);
 }
 
-static void PrintThread(const ReportThread *rt) {
-  Decorator d;
-  if (rt->id == kMainTid)  // Little sense in describing the main thread.
-    return;
-  Printf("%s", d.ThreadDescription());
-  Printf("  Thread T%d", rt->id);
-  if (rt->name && rt->name[0] != '\0')
-    Printf(" '%s'", rt->name);
+// Writes the (uncolored) description for a non-main thread and returns whether
+// a stack trace should follow (false for GCD worker threads).
+static bool DescribeThread(const ReportThread *rt, InternalScopedString *s) {
   char thrbuf[kThreadBufSize];
+  s->AppendF("  Thread T%d", rt->id);
+  if (rt->name && rt->name[0] != '\0')
+    s->AppendF(" '%s'", rt->name);
   const char *thread_status = rt->running ? "running" : "finished";
   if (rt->thread_type == ThreadType::Worker) {
-    Printf(" (tid=%llu, %s) is a GCD worker thread\n", rt->os_id,
-           thread_status);
-    Printf("\n");
-    Printf("%s", d.Default());
-    return;
+    s->AppendF(" (tid=%llu, %s) is a GCD worker thread", rt->os_id,
+               thread_status);
+    return false;
   }
-  Printf(" (tid=%llu, %s) created by %s", rt->os_id, thread_status,
-         thread_name(thrbuf, rt->parent_tid));
+  s->AppendF(" (tid=%llu, %s) created by %s", rt->os_id, thread_status,
+             thread_name(thrbuf, rt->parent_tid));
   if (rt->stack)
-    Printf(" at:");
-  Printf("\n");
+    s->Append(" at:");
+  return true;
+}
+
+static void PrintThread(const ReportThread *rt) {
+  Decorator d;
+  if (rt->id == kMainTid)  // Little sense in describing the main thread.
+    return;
+  Printf("%s", d.ThreadDescription());
+  InternalScopedString s;
+  bool print_stack = DescribeThread(rt, &s);
+  Printf("%s", s.data());
+  // A thread with a following stack is separated from it by a single newline;
+  // otherwise we emit the trailing blank line here.
+  Printf(print_stack ? "\n" : "\n\n");
   Printf("%s", d.Default());
-  PrintStack(rt->stack);
+  if (print_stack)
+    PrintStack(rt->stack);
 }
 
 static void PrintSleep(const ReportStack *s) {



More information about the llvm-commits mailing list