[compiler-rt] [Darwin][TSan] Fix false positive race report when using dispatch_apply (PR #204866)
Dan Blackwell via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 26 05:21:35 PDT 2026
https://github.com/DanBlackwell updated https://github.com/llvm/llvm-project/pull/204866
>From c069106254f81bee61e41c49993fe6d08a04082e Mon Sep 17 00:00:00 2001
From: Dan Blackwell <dan_blackwell at apple.com>
Date: Fri, 19 Jun 2026 17:52:29 +0100
Subject: [PATCH] [Darwin][TSan] Fix false positive race report when using
dispatch_apply
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
---
.../rtl/tsan_interceptors_libdispatch.cpp | 18 ++++++++++-
.../test/tsan/Darwin/dispatch_apply.mm | 30 +++++++++++++++++++
2 files changed, 47 insertions(+), 1 deletion(-)
create mode 100644 compiler-rt/test/tsan/Darwin/dispatch_apply.mm
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