[compiler-rt] 00617c9 - [Darwin][TSan] Fix false positive race report when using dispatch_apply (#204866)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 06:21:04 PDT 2026


Author: Dan Blackwell
Date: 2026-06-30T14:20:59+01:00
New Revision: 00617c92e10fb55be86e6fcf60ef2f5717d8eb92

URL: https://github.com/llvm/llvm-project/commit/00617c92e10fb55be86e6fcf60ef2f5717d8eb92
DIFF: https://github.com/llvm/llvm-project/commit/00617c92e10fb55be86e6fcf60ef2f5717d8eb92.diff

LOG: [Darwin][TSan] Fix false positive race report when using dispatch_apply (#204866)

Currently I have seen some false positive race reports caused by
instrumented block copy code that writes after the release-annotation.
When the block runs on another thread and reads the copied data, it
incorrectly reports this as a race.

This patch casts the block to a `void *` in order to avoid the block
copy - thus avoiding the reported race. This is safe because
`dispatch_apply` runs synchronously, so the block lives beyond all
invocations.

rdar://92286127

Added: 
    compiler-rt/test/tsan/Darwin/dispatch_apply.mm

Modified: 
    compiler-rt/lib/tsan/rtl/tsan_interceptors_libdispatch.cpp

Removed: 
    


################################################################################
diff  --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_libdispatch.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_libdispatch.cpp
index 2104fe7fd059f..64d766c993a86 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_libdispatch.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_libdispatch.cpp
@@ -511,12 +511,28 @@ TSAN_INTERCEPTOR(void, dispatch_apply, size_t iterations,
   uptr parent_to_child_sync = (uptr)&sync1;
   uptr child_to_parent_sync = (uptr)&sync2;
 
+  // Capturing `block` inside `new_block` (below) causes `new_block`'s copy
+  // helper to `_Block_copy` `block`. Any captured objects must be `retain`ed
+  // and their (retained) pointers stored into the heap Block_layout. This
+  // happens on the calling thread inside `REAL(dispatch_apply)` below, after
+  // the `Release(..., parent_to_child_sync)` call.
+  //
+  // When `block` gets invoked in `new_block` on a worker thread, it reads
+  // those same captures. The worker does not see a happens-before edge
+  // between the writes during the copy and these reads, resulting in a
+  // false positive race report.
+  //
+  // We avoid this copy by taking a raw pointer to the block and directly
+  // invoking that. This is safe because `dispatch_apply` will not return
+  // until all iterations have completed - thus `block` will live past the
+  // end of all uses of `block_ptr`.
+  void* block_ptr = (void*)block;
   Release(thr, pc, parent_to_child_sync);
   void (^new_block)(size_t) = ^(size_t iteration) {
     SCOPED_INTERCEPTOR_RAW(dispatch_apply);
     Acquire(thr, pc, parent_to_child_sync);
     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
-    block(iteration);
+    ((void (^)(size_t))block_ptr)(iteration);
     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
     Release(thr, pc, child_to_parent_sync);
   };

diff  --git a/compiler-rt/test/tsan/Darwin/dispatch_apply.mm b/compiler-rt/test/tsan/Darwin/dispatch_apply.mm
new file mode 100644
index 0000000000000..2daa877962ea2
--- /dev/null
+++ b/compiler-rt/test/tsan/Darwin/dispatch_apply.mm
@@ -0,0 +1,30 @@
+// Check that dispatch_apply() does not report a false data race when the
+// dispatched block's copy helper retains the captures into the heap block.
+// NOTE: this test may spuriously pass, but is a best effort to reproduce
+//       the problem, which relies on OS thread scheduling mechanics.
+
+// RUN: %clang_tsan %s -o %t -framework Foundation
+// RUN: %run %t 2>&1 | FileCheck %s
+
+#import <Foundation/Foundation.h>
+#import <dispatch/dispatch.h>
+
+int main() {
+  // Strong capture -> non-trivial copy helper. Enough iterations that
+  // libdispatch fans out to worker threads (captures read off-thread).
+  const size_t n = 1024;
+  NSMutableArray *items = [NSMutableArray array];
+  for (size_t i = 0; i < n; i++)
+    [items addObject:@(i)];
+
+  dispatch_apply(n, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
+                 ^(size_t idx) {
+                   (void)[items objectAtIndex:idx];
+                 });
+
+  NSLog(@"Done.");
+  return 0;
+}
+
+// CHECK-NOT: WARNING: ThreadSanitizer
+// CHECK: Done.


        


More information about the llvm-commits mailing list