[compiler-rt] [ASan][Darwin][GCD] Add interceptor for dispatch_apply (PR #149238)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 17 09:23:06 PDT 2025


https://github.com/thetruestblue updated https://github.com/llvm/llvm-project/pull/149238

>From 267f96a25db872d26d90f5317c7444e855cfc9d4 Mon Sep 17 00:00:00 2001
From: thetruestblue <bgaston2 at apple.com>
Date: Wed, 16 Jul 2025 14:21:57 -0700
Subject: [PATCH 1/2] [ASan][Darwin][GCD] Add interceptor for dispatch_apply

ASan had a gap in covarage for wqthreads submitted by dispatch_apply

This adds interceptor for dispatch_apply and adds a test that a failure in a dispatch apply block contains thread and stack info.

rdar://139660648
---
 compiler-rt/lib/asan/asan_mac.cpp             | 19 ++++++++++++-
 compiler-rt/lib/asan/tests/asan_mac_test.cpp  |  6 +++++
 compiler-rt/lib/asan/tests/asan_mac_test.h    |  1 +
 .../lib/asan/tests/asan_mac_test_helpers.mm   | 10 +++++++
 .../Darwin/dispatch_apply_threadno.c          | 27 +++++++++++++++++++
 5 files changed, 62 insertions(+), 1 deletion(-)
 create mode 100644 compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c

diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp
index be513a03ed5cd..30c81ec64f024 100644
--- a/compiler-rt/lib/asan/asan_mac.cpp
+++ b/compiler-rt/lib/asan/asan_mac.cpp
@@ -103,6 +103,7 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) {
 //   dispatch_after()
 //   dispatch_group_async_f()
 //   dispatch_group_async()
+//   dispatch_apply()
 // TODO(glider): libdispatch API contains other functions that we don't support
 // yet.
 //
@@ -255,6 +256,8 @@ void dispatch_source_set_cancel_handler(dispatch_source_t ds,
 void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void));
 dispatch_mach_t dispatch_mach_create(const char *label, dispatch_queue_t queue,
                                      dispatch_mach_handler_t handler);
+void dispatch_apply(size_t iterations, dispatch_queue_t queue,
+                    void (^block)(size_t iteration));
 }
 
 #define GET_ASAN_BLOCK(work) \
@@ -332,6 +335,20 @@ INTERCEPTOR(void *, dispatch_mach_create_f, const char *label,
       });
 }
 
-#endif
+INTERCEPTOR(void, dispatch_apply, size_t iterations, dispatch_queue_t queue,
+            void (^block)(size_t iteration)) {
+  ENABLE_FRAME_POINTER;
+  int parent_tid = GetCurrentTidOrInvalid();
+
+  void (^asan_block)(size_t) = ^(size_t iteration) {
+    GET_STACK_TRACE_THREAD;
+    asan_register_worker_thread(parent_tid, &stack);
+    block(iteration);
+  };
+
+  REAL(dispatch_apply)(iterations, queue, asan_block);
+}
+
+#  endif
 
 #endif  // SANITIZER_APPLE
diff --git a/compiler-rt/lib/asan/tests/asan_mac_test.cpp b/compiler-rt/lib/asan/tests/asan_mac_test.cpp
index bd36089991deb..4b21f12f81eac 100644
--- a/compiler-rt/lib/asan/tests/asan_mac_test.cpp
+++ b/compiler-rt/lib/asan/tests/asan_mac_test.cpp
@@ -116,6 +116,12 @@ TEST(AddressSanitizerMac, GCDDispatchAfter) {
   EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte legend");
 }
 
+TEST(AddressSanitizerMac, GCDDispatchApply) {
+  // Make sure the whole ASan report is printed, i.e. that we don't die
+  // on a CHECK.
+  EXPECT_DEATH(TestGCDDispatchApply(), "Shadow byte legend");
+}
+
 TEST(AddressSanitizerMac, GCDSourceEvent) {
   // Make sure the whole ASan report is printed, i.e. that we don't die
   // on a CHECK.
diff --git a/compiler-rt/lib/asan/tests/asan_mac_test.h b/compiler-rt/lib/asan/tests/asan_mac_test.h
index 441547a5a3dcb..ec71546a3989b 100644
--- a/compiler-rt/lib/asan/tests/asan_mac_test.h
+++ b/compiler-rt/lib/asan/tests/asan_mac_test.h
@@ -9,6 +9,7 @@ extern "C" {
   void TestGCDReuseWqthreadsAsync();
   void TestGCDReuseWqthreadsSync();
   void TestGCDDispatchAfter();
+  void TestGCDDispatchApply();
   void TestGCDInTSDDestructor();
   void TestGCDSourceEvent();
   void TestGCDSourceCancel();
diff --git a/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm b/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm
index 3f8fa26d95b8d..ddb50f894639d 100644
--- a/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm
+++ b/compiler-rt/lib/asan/tests/asan_mac_test_helpers.mm
@@ -148,6 +148,16 @@ void TestGCDDispatchAfter() {
   wait_forever();
 }
 
+void TestGCDDispatchApply() {
+  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
+  __block char *buffer = (char *)malloc(4);
+  dispatch_apply(8, queue, ^(size_t i) {
+    access_memory(&buffer[i]);
+  });
+
+  free(buffer);  // not reached
+}
+
 void worker_do_deallocate(void *ptr) {
   free(ptr);
 }
diff --git a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c
new file mode 100644
index 0000000000000..8dfd0942bf656
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c
@@ -0,0 +1,27 @@
+// Bugs caught within missing GCD dispatch blocks result in thread being reported as T-1
+// with an empty stack.
+// This tests that dispatch_apply blocks can capture valid thread number and stack.
+
+// RUN: %clang_asan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <dispatch/dispatch.h>
+#include <stdlib.h>
+
+__attribute__((noinline)) void access_memory_frame(char *x) { *x = 0; }
+
+__attribute__((noinline)) void test_dispatch_apply() {
+  char *x = (char *)malloc(4);
+  dispatch_apply(8, dispatch_get_global_queue(0, 0), ^(size_t i) {
+    access_memory_frame(&x[i]);
+  });
+}
+
+int main(int argc, const char *argv[]) {
+  test_dispatch_apply();
+  return 0;
+}
+
+// CHECK: ERROR: AddressSanitizer: heap-buffer-overflow
+// CHECK: #0 0x{{.*}} in {{.*}}access_memory_frame
+// CHECK-NOT: T-1
\ No newline at end of file

>From 92a25c0ab786dee0db03a37209eb9d0705314214 Mon Sep 17 00:00:00 2001
From: thetruestblue <bgaston2 at apple.com>
Date: Thu, 17 Jul 2025 09:22:15 -0700
Subject: [PATCH 2/2] Add dispatch_apply_f interceptor

---
 compiler-rt/lib/asan/asan_mac.cpp             | 23 +++++++++++++++++++
 .../Darwin/dispatch_apply_threadno.c          | 22 ++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp
index 30c81ec64f024..37cebb180acf2 100644
--- a/compiler-rt/lib/asan/asan_mac.cpp
+++ b/compiler-rt/lib/asan/asan_mac.cpp
@@ -244,6 +244,29 @@ INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
                                asan_dispatch_call_block_and_release);
 }
 
+extern "C"
+void asan_dispatch_apply_f_block(void *context, size_t iteration) {
+  GET_STACK_TRACE_THREAD;
+  asan_block_context_t *asan_ctxt = (asan_block_context_t *)context;
+  asan_register_worker_thread(asan_ctxt->parent_tid, &stack);
+  ((void (*)(void *, size_t))asan_ctxt->func)(asan_ctxt->block, iteration);
+}
+
+INTERCEPTOR(void, dispatch_apply_f,
+            size_t iterations,
+            dispatch_queue_t queue,
+            void *ctxt,
+            void (*work)(void *, size_t)) {
+
+  GET_STACK_TRACE_THREAD;
+  asan_block_context_t *asan_ctxt =
+      alloc_asan_context(ctxt, (dispatch_function_t)work, &stack);
+
+  REAL(dispatch_apply_f)(iterations, queue,
+                         (void *)asan_ctxt,
+                         asan_dispatch_apply_f_block);
+}
+
 #if !defined(MISSING_BLOCKS_SUPPORT)
 extern "C" {
 void dispatch_async(dispatch_queue_t dq, void(^work)(void));
diff --git a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c
index 8dfd0942bf656..c58792e111daf 100644
--- a/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c
+++ b/compiler-rt/test/asan/TestCases/Darwin/dispatch_apply_threadno.c
@@ -2,6 +2,9 @@
 // with an empty stack.
 // This tests that dispatch_apply blocks can capture valid thread number and stack.
 
+// RUN: %clang_asan -DDISPATCH_APPLY_F %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
 // RUN: %clang_asan %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
@@ -17,8 +20,27 @@ __attribute__((noinline)) void test_dispatch_apply() {
   });
 }
 
+typedef struct {
+  char *data;
+} Context;
+
+void da_func(void *ctx, size_t i) {
+  Context *c = (Context *)ctx;
+  access_memory_frame(&c->data[i]);
+}
+
+__attribute__((noinline)) void test_dispatch_apply_f() {
+  Context *ctx = (Context *)malloc(sizeof(Context));
+  ctx->data = (char *)malloc(4);
+  dispatch_apply_f(8, dispatch_get_global_queue(0,0), ctx, da_func);
+}
+
 int main(int argc, const char *argv[]) {
+#if DISPATCH_APPLY_F
+  test_dispatch_apply_f();
+#else
   test_dispatch_apply();
+#endif
   return 0;
 }
 



More information about the llvm-commits mailing list