[compiler-rt] 59b26ab - [TSan, SanitizerBinaryMetadata] Analyze the capture status for `alloca` rather than arbitrary `Addr` (#132756)

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 24 01:48:10 PDT 2025


Author: Camsyn
Date: 2025-04-24T10:48:07+02:00
New Revision: 59b26abbbe89994c2ffd50a933654be247b68aaf

URL: https://github.com/llvm/llvm-project/commit/59b26abbbe89994c2ffd50a933654be247b68aaf
DIFF: https://github.com/llvm/llvm-project/commit/59b26abbbe89994c2ffd50a933654be247b68aaf.diff

LOG: [TSan, SanitizerBinaryMetadata] Analyze the capture status for `alloca` rather than arbitrary `Addr` (#132756)

This PR is based on my last PR #132752 (the first commit of this PR),
but addressing a different issue.

This commit addresses the limitation in `PointerMayBeCaptured` analysis
when dealing with derived pointers (e.g. arr+1) as described in issue
#132739.

The current implementation of `PointerMayBeCaptured` may miss captures
of the underlying `alloca` when analyzing derived pointers, leading to
some FNs in TSan, as follows:
```cpp
void *Thread(void *a) {
  ((int*)a)[1] = 43;
  return 0;
}

int main() {
  int Arr[2] = {41, 42};
  pthread_t t;
  pthread_create(&t, 0, Thread, &Arr[0]);
  // Missed instrumentation here due to the FN of PointerMayBeCaptured
  Arr[1] = 43;
  barrier_wait(&barrier);
  pthread_join(t, 0);
}
```
Refer to this [godbolt page](https://godbolt.org/z/n67GrxdcE) to get the
compilation result of TSan.

Even when `PointerMayBeCaptured` working correctly, it should backtrack
to the original `alloca` firstly during analysis, causing redundancy to
the outer's `findAllocaForValue`.
```cpp
    const AllocaInst *AI = findAllocaForValue(Addr);
    // Instead of Addr, we should check whether its base pointer is captured.
    if (AI && !PointerMayBeCaptured(Addr, true)) ...
```

Key changes:
Directly analyze the capture status of the underlying `alloca` instead
of derived pointers to ensure accurate capture detection
```cpp
    const AllocaInst *AI = findAllocaForValue(Addr);
    // Instead of Addr, we should check whether its base pointer is captured.
    if (AI && !PointerMayBeCaptured(AI, true)) ...
```

Added: 
    compiler-rt/test/tsan/stack_race3.cpp

Modified: 
    llvm/lib/Transforms/Instrumentation/SanitizerBinaryMetadata.cpp
    llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
    llvm/test/Instrumentation/ThreadSanitizer/capture.ll

Removed: 
    


################################################################################
diff  --git a/compiler-rt/test/tsan/stack_race3.cpp b/compiler-rt/test/tsan/stack_race3.cpp
new file mode 100644
index 0000000000000..c50f4ad4c35cc
--- /dev/null
+++ b/compiler-rt/test/tsan/stack_race3.cpp
@@ -0,0 +1,21 @@
+// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s
+#include "test.h"
+
+void *Thread(void *a) {
+  barrier_wait(&barrier);
+  ((int *)a)[1] = 43;
+  return 0;
+}
+
+int main() {
+  barrier_init(&barrier, 2);
+  int Arr[2] = {41, 42};
+  pthread_t t;
+  pthread_create(&t, 0, Thread, &Arr[0]);
+  Arr[1] = 43;
+  barrier_wait(&barrier);
+  pthread_join(t, 0);
+}
+
+// CHECK: WARNING: ThreadSanitizer: data race
+// CHECK:   Location is stack of main thread.

diff  --git a/llvm/lib/Transforms/Instrumentation/SanitizerBinaryMetadata.cpp b/llvm/lib/Transforms/Instrumentation/SanitizerBinaryMetadata.cpp
index b6787fd8cb21e..4801ac75f8572 100644
--- a/llvm/lib/Transforms/Instrumentation/SanitizerBinaryMetadata.cpp
+++ b/llvm/lib/Transforms/Instrumentation/SanitizerBinaryMetadata.cpp
@@ -393,7 +393,7 @@ bool maybeSharedMutable(const Value *Addr) {
     return true;
 
   const AllocaInst *AI = findAllocaForValue(Addr);
-  if (AI && !PointerMayBeCaptured(Addr, /*ReturnCaptures=*/true))
+  if (AI && !PointerMayBeCaptured(AI, /*ReturnCaptures=*/true))
     return false; // Object is on stack but does not escape.
 
   Addr = Addr->stripInBoundsOffsets();

diff  --git a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
index 7f846d25b8133..ec9f78edfeb1c 100644
--- a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
@@ -449,7 +449,8 @@ void ThreadSanitizer::chooseInstructionsToInstrument(
     }
 
     const AllocaInst *AI = findAllocaForValue(Addr);
-    if (AI && !PointerMayBeCaptured(Addr, /*ReturnCaptures=*/true)) {
+    // Instead of Addr, we should check whether its base pointer is captured.
+    if (AI && !PointerMayBeCaptured(AI, /*ReturnCaptures=*/true)) {
       // The variable is addressable but not captured, so it cannot be
       // referenced from a 
diff erent thread and participate in a data race
       // (see llvm/Analysis/CaptureTracking.h for details).

diff  --git a/llvm/test/Instrumentation/ThreadSanitizer/capture.ll b/llvm/test/Instrumentation/ThreadSanitizer/capture.ll
index 9cd5d77f4753e..e1b9e03b88446 100644
--- a/llvm/test/Instrumentation/ThreadSanitizer/capture.ll
+++ b/llvm/test/Instrumentation/ThreadSanitizer/capture.ll
@@ -47,6 +47,21 @@ entry:
 ; CHECK: __tsan_write
 ; CHECK: ret void
 
+define void @captured3() nounwind uwtable sanitize_thread {
+entry:
+  %stkobj = alloca [2 x i32], align 8
+  ; escapes due to store into global
+  store ptr %stkobj, ptr @sink, align 8
+  ; derived is captured as its base object is captured
+  %derived = getelementptr inbounds i32, ptr %stkobj, i64 1
+  store i32 42, ptr %derived, align 4
+  ret void
+}
+; CHECK-LABEL: define void @captured3
+; CHECK: __tsan_write
+; CHECK: __tsan_write
+; CHECK: ret void
+
 define void @notcaptured0() nounwind uwtable sanitize_thread {
 entry:
   %ptr = alloca i32, align 4


        


More information about the llvm-commits mailing list