| 1 | //===-- tsan_interceptors_libdispatch.cpp ---------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file is a part of ThreadSanitizer (TSan), a race detector. |
| 10 | // |
| 11 | // Support for intercepting libdispatch (GCD). |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "sanitizer_common/sanitizer_common.h" |
| 15 | #include "interception/interception.h" |
| 16 | #include "tsan_interceptors.h" |
| 17 | #include "tsan_rtl.h" |
| 18 | |
| 19 | #include "BlocksRuntime/Block.h" |
| 20 | #include "tsan_dispatch_defs.h" |
| 21 | |
| 22 | #if SANITIZER_APPLE |
| 23 | # include <Availability.h> |
| 24 | #endif |
| 25 | |
| 26 | namespace __tsan { |
| 27 | typedef u16 uint16_t; |
| 28 | |
| 29 | typedef struct { |
| 30 | dispatch_queue_t queue; |
| 31 | void *orig_context; |
| 32 | dispatch_function_t orig_work; |
| 33 | bool free_context_in_callback; |
| 34 | bool submitted_synchronously; |
| 35 | bool is_barrier_block; |
| 36 | uptr non_queue_sync_object; |
| 37 | } block_context_t; |
| 38 | |
| 39 | // The offsets of different fields of the dispatch_queue_t structure, exported |
| 40 | // by libdispatch.dylib. |
| 41 | extern "C" struct dispatch_queue_offsets_s { |
| 42 | const uint16_t dqo_version; |
| 43 | const uint16_t dqo_label; |
| 44 | const uint16_t dqo_label_size; |
| 45 | const uint16_t dqo_flags; |
| 46 | const uint16_t dqo_flags_size; |
| 47 | const uint16_t dqo_serialnum; |
| 48 | const uint16_t dqo_serialnum_size; |
| 49 | const uint16_t dqo_width; |
| 50 | const uint16_t dqo_width_size; |
| 51 | const uint16_t dqo_running; |
| 52 | const uint16_t dqo_running_size; |
| 53 | const uint16_t dqo_suspend_cnt; |
| 54 | const uint16_t dqo_suspend_cnt_size; |
| 55 | const uint16_t dqo_target_queue; |
| 56 | const uint16_t dqo_target_queue_size; |
| 57 | const uint16_t dqo_priority; |
| 58 | const uint16_t dqo_priority_size; |
| 59 | } dispatch_queue_offsets; |
| 60 | |
| 61 | static bool IsQueueSerial(dispatch_queue_t q) { |
| 62 | CHECK_EQ(dispatch_queue_offsets.dqo_width_size, 2); |
| 63 | uptr width = *(uint16_t *)(((uptr)q) + dispatch_queue_offsets.dqo_width); |
| 64 | CHECK_NE(width, 0); |
| 65 | return width == 1; |
| 66 | } |
| 67 | |
| 68 | static dispatch_queue_t GetTargetQueueFromQueue(dispatch_queue_t q) { |
| 69 | CHECK_EQ(dispatch_queue_offsets.dqo_target_queue_size, 8); |
| 70 | dispatch_queue_t tq = *( |
| 71 | dispatch_queue_t *)(((uptr)q) + dispatch_queue_offsets.dqo_target_queue); |
| 72 | return tq; |
| 73 | } |
| 74 | |
| 75 | static dispatch_queue_t GetTargetQueueFromSource(dispatch_source_t source) { |
| 76 | dispatch_queue_t tq = GetTargetQueueFromQueue(q: (dispatch_queue_t)source); |
| 77 | CHECK_NE(tq, 0); |
| 78 | return tq; |
| 79 | } |
| 80 | |
| 81 | static block_context_t *AllocContext(ThreadState *thr, uptr pc, |
| 82 | dispatch_queue_t queue, void *orig_context, |
| 83 | dispatch_function_t orig_work) { |
| 84 | block_context_t *new_context = |
| 85 | (block_context_t *)user_alloc_internal(thr, pc, sz: sizeof(block_context_t)); |
| 86 | new_context->queue = queue; |
| 87 | new_context->orig_context = orig_context; |
| 88 | new_context->orig_work = orig_work; |
| 89 | new_context->free_context_in_callback = true; |
| 90 | new_context->submitted_synchronously = false; |
| 91 | new_context->is_barrier_block = false; |
| 92 | new_context->non_queue_sync_object = 0; |
| 93 | return new_context; |
| 94 | } |
| 95 | |
| 96 | #define GET_QUEUE_SYNC_VARS(context, q) \ |
| 97 | bool is_queue_serial = q && IsQueueSerial(q); \ |
| 98 | uptr sync_ptr = (uptr)q ?: context->non_queue_sync_object; \ |
| 99 | uptr serial_sync = (uptr)sync_ptr; \ |
| 100 | uptr concurrent_sync = sync_ptr ? ((uptr)sync_ptr) + sizeof(uptr) : 0; \ |
| 101 | bool serial_task = context->is_barrier_block || is_queue_serial |
| 102 | |
| 103 | static void dispatch_sync_pre_execute(ThreadState *thr, uptr pc, |
| 104 | block_context_t *context) { |
| 105 | uptr submit_sync = (uptr)context; |
| 106 | Acquire(thr, pc, addr: submit_sync); |
| 107 | |
| 108 | dispatch_queue_t q = context->queue; |
| 109 | do { |
| 110 | GET_QUEUE_SYNC_VARS(context, q); |
| 111 | if (serial_sync) Acquire(thr, pc, addr: serial_sync); |
| 112 | if (serial_task && concurrent_sync) Acquire(thr, pc, addr: concurrent_sync); |
| 113 | |
| 114 | if (q) q = GetTargetQueueFromQueue(q); |
| 115 | } while (q); |
| 116 | } |
| 117 | |
| 118 | static void dispatch_sync_post_execute(ThreadState *thr, uptr pc, |
| 119 | block_context_t *context) { |
| 120 | uptr submit_sync = (uptr)context; |
| 121 | if (context->submitted_synchronously) Release(thr, pc, addr: submit_sync); |
| 122 | |
| 123 | dispatch_queue_t q = context->queue; |
| 124 | do { |
| 125 | GET_QUEUE_SYNC_VARS(context, q); |
| 126 | if (serial_task && serial_sync) Release(thr, pc, addr: serial_sync); |
| 127 | if (!serial_task && concurrent_sync) Release(thr, pc, addr: concurrent_sync); |
| 128 | |
| 129 | if (q) q = GetTargetQueueFromQueue(q); |
| 130 | } while (q); |
| 131 | } |
| 132 | |
| 133 | static void dispatch_callback_wrap(void *param) { |
| 134 | SCOPED_INTERCEPTOR_RAW(dispatch_callback_wrap); |
| 135 | block_context_t *context = (block_context_t *)param; |
| 136 | |
| 137 | dispatch_sync_pre_execute(thr, pc, context); |
| 138 | |
| 139 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 140 | context->orig_work(context->orig_context); |
| 141 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 142 | |
| 143 | dispatch_sync_post_execute(thr, pc, context); |
| 144 | |
| 145 | if (context->free_context_in_callback) user_free(thr, pc, p: context); |
| 146 | } |
| 147 | |
| 148 | static void invoke_block(void *param) { |
| 149 | dispatch_block_t block = (dispatch_block_t)param; |
| 150 | block(); |
| 151 | } |
| 152 | |
| 153 | static void invoke_and_release_block(void *param) { |
| 154 | dispatch_block_t block = (dispatch_block_t)param; |
| 155 | block(); |
| 156 | Block_release(block); |
| 157 | } |
| 158 | |
| 159 | #define DISPATCH_INTERCEPT_ASYNC_B(name, barrier) \ |
| 160 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, dispatch_block_t block) { \ |
| 161 | SCOPED_TSAN_INTERCEPTOR(name, q, block); \ |
| 162 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 163 | dispatch_block_t heap_block = Block_copy(block); \ |
| 164 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 165 | block_context_t *new_context = \ |
| 166 | AllocContext(thr, pc, q, heap_block, &invoke_and_release_block); \ |
| 167 | new_context->is_barrier_block = barrier; \ |
| 168 | Release(thr, pc, (uptr)new_context); \ |
| 169 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 170 | REAL(name##_f)(q, new_context, dispatch_callback_wrap); \ |
| 171 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 172 | } |
| 173 | |
| 174 | #define DISPATCH_INTERCEPT_SYNC_B(name, barrier) \ |
| 175 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, \ |
| 176 | DISPATCH_NOESCAPE dispatch_block_t block) { \ |
| 177 | SCOPED_TSAN_INTERCEPTOR(name, q, block); \ |
| 178 | block_context_t new_context = { \ |
| 179 | q, block, &invoke_block, false, true, barrier, 0}; \ |
| 180 | Release(thr, pc, (uptr)&new_context); \ |
| 181 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 182 | REAL(name##_f)(q, &new_context, dispatch_callback_wrap); \ |
| 183 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 184 | Acquire(thr, pc, (uptr)&new_context); \ |
| 185 | } |
| 186 | |
| 187 | #define DISPATCH_INTERCEPT_ASYNC_F(name, barrier) \ |
| 188 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, void *context, \ |
| 189 | dispatch_function_t work) { \ |
| 190 | SCOPED_TSAN_INTERCEPTOR(name, q, context, work); \ |
| 191 | block_context_t *new_context = \ |
| 192 | AllocContext(thr, pc, q, context, work); \ |
| 193 | new_context->is_barrier_block = barrier; \ |
| 194 | Release(thr, pc, (uptr)new_context); \ |
| 195 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 196 | REAL(name)(q, new_context, dispatch_callback_wrap); \ |
| 197 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 198 | } |
| 199 | |
| 200 | #define DISPATCH_INTERCEPT_SYNC_F(name, barrier) \ |
| 201 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, void *context, \ |
| 202 | dispatch_function_t work) { \ |
| 203 | SCOPED_TSAN_INTERCEPTOR(name, q, context, work); \ |
| 204 | block_context_t new_context = { \ |
| 205 | q, context, work, false, true, barrier, 0}; \ |
| 206 | Release(thr, pc, (uptr)&new_context); \ |
| 207 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 208 | REAL(name)(q, &new_context, dispatch_callback_wrap); \ |
| 209 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 210 | Acquire(thr, pc, (uptr)&new_context); \ |
| 211 | } |
| 212 | |
| 213 | #define DISPATCH_INTERCEPT(name, barrier) \ |
| 214 | DISPATCH_INTERCEPT_ASYNC_F(name##_async_f, barrier) \ |
| 215 | DISPATCH_INTERCEPT_ASYNC_B(name##_async, barrier) \ |
| 216 | DISPATCH_INTERCEPT_SYNC_F(name##_sync_f, barrier) \ |
| 217 | DISPATCH_INTERCEPT_SYNC_B(name##_sync, barrier) |
| 218 | |
| 219 | // We wrap dispatch_async, dispatch_sync and friends where we allocate a new |
| 220 | // context, which is used to synchronize (we release the context before |
| 221 | // submitting, and the callback acquires it before executing the original |
| 222 | // callback). |
| 223 | DISPATCH_INTERCEPT(dispatch, false) |
| 224 | DISPATCH_INTERCEPT(dispatch_barrier, true) |
| 225 | |
| 226 | // dispatch_async_and_wait() and friends were introduced in macOS 10.14. |
| 227 | // Linking of these interceptors fails when using an older SDK. |
| 228 | #if !SANITIZER_APPLE || defined(__MAC_10_14) |
| 229 | // macOS 10.14 is greater than our minimal deployment target. To ensure we |
| 230 | // generate a weak reference so the TSan dylib continues to work on older |
| 231 | // systems, we need to forward declare the intercepted functions as "weak |
| 232 | // imports". Note that this file is multi-platform, so we cannot include the |
| 233 | // actual header file (#include <dispatch/dispatch.h>). |
| 234 | SANITIZER_WEAK_IMPORT void dispatch_async_and_wait( |
| 235 | dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); |
| 236 | SANITIZER_WEAK_IMPORT void dispatch_async_and_wait_f( |
| 237 | dispatch_queue_t queue, void *context, dispatch_function_t work); |
| 238 | SANITIZER_WEAK_IMPORT void dispatch_barrier_async_and_wait( |
| 239 | dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); |
| 240 | SANITIZER_WEAK_IMPORT void dispatch_barrier_async_and_wait_f( |
| 241 | dispatch_queue_t queue, void *context, dispatch_function_t work); |
| 242 | |
| 243 | DISPATCH_INTERCEPT_SYNC_F(dispatch_async_and_wait_f, false) |
| 244 | DISPATCH_INTERCEPT_SYNC_B(dispatch_async_and_wait, false) |
| 245 | DISPATCH_INTERCEPT_SYNC_F(dispatch_barrier_async_and_wait_f, true) |
| 246 | DISPATCH_INTERCEPT_SYNC_B(dispatch_barrier_async_and_wait, true) |
| 247 | #endif |
| 248 | |
| 249 | |
| 250 | DECLARE_REAL(void, dispatch_after_f, dispatch_time_t when, |
| 251 | dispatch_queue_t queue, void *context, dispatch_function_t work) |
| 252 | |
| 253 | TSAN_INTERCEPTOR(void, dispatch_after, dispatch_time_t when, |
| 254 | dispatch_queue_t queue, dispatch_block_t block) { |
| 255 | SCOPED_TSAN_INTERCEPTOR(dispatch_after, when, queue, block); |
| 256 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 257 | dispatch_block_t heap_block = Block_copy(block); |
| 258 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 259 | block_context_t *new_context = |
| 260 | AllocContext(thr, pc, queue, orig_context: heap_block, orig_work: &invoke_and_release_block); |
| 261 | Release(thr, pc, addr: (uptr)new_context); |
| 262 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 263 | REAL(dispatch_after_f)(when, queue, new_context, dispatch_callback_wrap); |
| 264 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 265 | } |
| 266 | |
| 267 | TSAN_INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, |
| 268 | dispatch_queue_t queue, void *context, |
| 269 | dispatch_function_t work) { |
| 270 | SCOPED_TSAN_INTERCEPTOR(dispatch_after_f, when, queue, context, work); |
| 271 | WRAP(dispatch_after)(when, queue, block: ^(void) { |
| 272 | work(context); |
| 273 | }); |
| 274 | } |
| 275 | |
| 276 | // GCD's dispatch_once implementation has a fast path that contains a racy read |
| 277 | // and it's inlined into user's code. Furthermore, this fast path doesn't |
| 278 | // establish a proper happens-before relations between the initialization and |
| 279 | // code following the call to dispatch_once. We could deal with this in |
| 280 | // instrumented code, but there's not much we can do about it in system |
| 281 | // libraries. Let's disable the fast path (by never storing the value ~0 to |
| 282 | // predicate), so the interceptor is always called, and let's add proper release |
| 283 | // and acquire semantics. Since TSan does not see its own atomic stores, the |
| 284 | // race on predicate won't be reported - the only accesses to it that TSan sees |
| 285 | // are the loads on the fast path. Loads don't race. Secondly, dispatch_once is |
| 286 | // both a macro and a real function, we want to intercept the function, so we |
| 287 | // need to undefine the macro. |
| 288 | #undef dispatch_once |
| 289 | TSAN_INTERCEPTOR(void, dispatch_once, dispatch_once_t *predicate, |
| 290 | DISPATCH_NOESCAPE dispatch_block_t block) { |
| 291 | SCOPED_INTERCEPTOR_RAW(dispatch_once, predicate, block); |
| 292 | atomic_uint32_t *a = reinterpret_cast<atomic_uint32_t *>(predicate); |
| 293 | u32 v = atomic_load(a, mo: memory_order_acquire); |
| 294 | if (v == 0 && |
| 295 | atomic_compare_exchange_strong(a, cmp: &v, xchg: 1, mo: memory_order_relaxed)) { |
| 296 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 297 | block(); |
| 298 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 299 | Release(thr, pc, addr: (uptr)a); |
| 300 | atomic_store(a, v: 2, mo: memory_order_release); |
| 301 | } else { |
| 302 | while (v != 2) { |
| 303 | internal_sched_yield(); |
| 304 | v = atomic_load(a, mo: memory_order_acquire); |
| 305 | } |
| 306 | Acquire(thr, pc, addr: (uptr)a); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | #undef dispatch_once_f |
| 311 | TSAN_INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *predicate, |
| 312 | void *context, dispatch_function_t function) { |
| 313 | SCOPED_INTERCEPTOR_RAW(dispatch_once_f, predicate, context, function); |
| 314 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 315 | WRAP(dispatch_once)(predicate, block: ^(void) { |
| 316 | function(context); |
| 317 | }); |
| 318 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 319 | } |
| 320 | |
| 321 | TSAN_INTERCEPTOR(long_t, dispatch_semaphore_signal, |
| 322 | dispatch_semaphore_t dsema) { |
| 323 | SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_signal, dsema); |
| 324 | Release(thr, pc, addr: (uptr)dsema); |
| 325 | return REAL(dispatch_semaphore_signal)(dsema); |
| 326 | } |
| 327 | |
| 328 | TSAN_INTERCEPTOR(long_t, dispatch_semaphore_wait, dispatch_semaphore_t dsema, |
| 329 | dispatch_time_t timeout) { |
| 330 | SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_wait, dsema, timeout); |
| 331 | long_t result = REAL(dispatch_semaphore_wait)(dsema, timeout); |
| 332 | if (result == 0) Acquire(thr, pc, addr: (uptr)dsema); |
| 333 | return result; |
| 334 | } |
| 335 | |
| 336 | TSAN_INTERCEPTOR(long_t, dispatch_group_wait, dispatch_group_t group, |
| 337 | dispatch_time_t timeout) { |
| 338 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_wait, group, timeout); |
| 339 | long_t result = REAL(dispatch_group_wait)(group, timeout); |
| 340 | if (result == 0) Acquire(thr, pc, addr: (uptr)group); |
| 341 | return result; |
| 342 | } |
| 343 | |
| 344 | // Used, but not intercepted. |
| 345 | extern "C" void dispatch_group_enter(dispatch_group_t group); |
| 346 | |
| 347 | TSAN_INTERCEPTOR(void, dispatch_group_leave, dispatch_group_t group) { |
| 348 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_leave, group); |
| 349 | // Acquired in the group notification callback in dispatch_group_notify[_f]. |
| 350 | Release(thr, pc, addr: (uptr)group); |
| 351 | REAL(dispatch_group_leave)(group); |
| 352 | } |
| 353 | |
| 354 | TSAN_INTERCEPTOR(void, dispatch_group_async, dispatch_group_t group, |
| 355 | dispatch_queue_t queue, dispatch_block_t block) { |
| 356 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_async, group, queue, block); |
| 357 | dispatch_retain(object: group); |
| 358 | dispatch_group_enter(group); |
| 359 | __block dispatch_block_t block_copy = (dispatch_block_t)Block_copy(block); |
| 360 | WRAP(dispatch_async)(q: queue, block: ^(void) { |
| 361 | block_copy(); |
| 362 | Block_release(block_copy); |
| 363 | WRAP(dispatch_group_leave)(group); |
| 364 | dispatch_release(object: group); |
| 365 | }); |
| 366 | } |
| 367 | |
| 368 | TSAN_INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, |
| 369 | dispatch_queue_t queue, void *context, |
| 370 | dispatch_function_t work) { |
| 371 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_async_f, group, queue, context, work); |
| 372 | dispatch_retain(object: group); |
| 373 | dispatch_group_enter(group); |
| 374 | WRAP(dispatch_async)(q: queue, block: ^(void) { |
| 375 | work(context); |
| 376 | WRAP(dispatch_group_leave)(group); |
| 377 | dispatch_release(object: group); |
| 378 | }); |
| 379 | } |
| 380 | |
| 381 | DECLARE_REAL(void, dispatch_group_notify_f, dispatch_group_t group, |
| 382 | dispatch_queue_t q, void *context, dispatch_function_t work) |
| 383 | |
| 384 | TSAN_INTERCEPTOR(void, dispatch_group_notify, dispatch_group_t group, |
| 385 | dispatch_queue_t q, dispatch_block_t block) { |
| 386 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify, group, q, block); |
| 387 | |
| 388 | // To make sure the group is still available in the callback (otherwise |
| 389 | // it can be already destroyed). Will be released in the callback. |
| 390 | dispatch_retain(object: group); |
| 391 | |
| 392 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 393 | dispatch_block_t heap_block = Block_copy(^(void) { |
| 394 | { |
| 395 | SCOPED_INTERCEPTOR_RAW(dispatch_read_callback); |
| 396 | // Released when leaving the group (dispatch_group_leave). |
| 397 | Acquire(thr, pc, (uptr)group); |
| 398 | } |
| 399 | dispatch_release(group); |
| 400 | block(); |
| 401 | }); |
| 402 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 403 | block_context_t *new_context = |
| 404 | AllocContext(thr, pc, queue: q, orig_context: heap_block, orig_work: &invoke_and_release_block); |
| 405 | new_context->is_barrier_block = true; |
| 406 | Release(thr, pc, addr: (uptr)new_context); |
| 407 | REAL(dispatch_group_notify_f)(group, q, new_context, dispatch_callback_wrap); |
| 408 | } |
| 409 | |
| 410 | TSAN_INTERCEPTOR(void, dispatch_group_notify_f, dispatch_group_t group, |
| 411 | dispatch_queue_t q, void *context, dispatch_function_t work) { |
| 412 | WRAP(dispatch_group_notify)(group, q, block: ^(void) { work(context); }); |
| 413 | } |
| 414 | |
| 415 | TSAN_INTERCEPTOR(void, dispatch_source_set_event_handler, |
| 416 | dispatch_source_t source, dispatch_block_t handler) { |
| 417 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_event_handler, source, handler); |
| 418 | if (handler == nullptr) |
| 419 | return REAL(dispatch_source_set_event_handler)(source, nullptr); |
| 420 | dispatch_queue_t q = GetTargetQueueFromSource(source); |
| 421 | __block block_context_t new_context = { |
| 422 | q, handler, &invoke_block, false, false, false, 0 }; |
| 423 | dispatch_block_t new_handler = Block_copy(^(void) { |
| 424 | new_context.orig_context = handler; // To explicitly capture "handler". |
| 425 | dispatch_callback_wrap(&new_context); |
| 426 | }); |
| 427 | uptr submit_sync = (uptr)&new_context; |
| 428 | Release(thr, pc, addr: submit_sync); |
| 429 | REAL(dispatch_source_set_event_handler)(source, new_handler); |
| 430 | Block_release(new_handler); |
| 431 | } |
| 432 | |
| 433 | TSAN_INTERCEPTOR(void, dispatch_source_set_event_handler_f, |
| 434 | dispatch_source_t source, dispatch_function_t handler) { |
| 435 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_event_handler_f, source, handler); |
| 436 | if (handler == nullptr) |
| 437 | return REAL(dispatch_source_set_event_handler)(source, nullptr); |
| 438 | dispatch_block_t block = ^(void) { |
| 439 | handler(dispatch_get_context(object: source)); |
| 440 | }; |
| 441 | WRAP(dispatch_source_set_event_handler)(source, handler: block); |
| 442 | } |
| 443 | |
| 444 | TSAN_INTERCEPTOR(void, dispatch_source_set_cancel_handler, |
| 445 | dispatch_source_t source, dispatch_block_t handler) { |
| 446 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_cancel_handler, source, handler); |
| 447 | if (handler == nullptr) |
| 448 | return REAL(dispatch_source_set_cancel_handler)(source, nullptr); |
| 449 | dispatch_queue_t q = GetTargetQueueFromSource(source); |
| 450 | __block block_context_t new_context = { |
| 451 | q, handler, &invoke_block, false, false, false, 0}; |
| 452 | dispatch_block_t new_handler = Block_copy(^(void) { |
| 453 | new_context.orig_context = handler; // To explicitly capture "handler". |
| 454 | dispatch_callback_wrap(&new_context); |
| 455 | }); |
| 456 | uptr submit_sync = (uptr)&new_context; |
| 457 | Release(thr, pc, addr: submit_sync); |
| 458 | REAL(dispatch_source_set_cancel_handler)(source, new_handler); |
| 459 | Block_release(new_handler); |
| 460 | } |
| 461 | |
| 462 | TSAN_INTERCEPTOR(void, dispatch_source_set_cancel_handler_f, |
| 463 | dispatch_source_t source, dispatch_function_t handler) { |
| 464 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_cancel_handler_f, source, |
| 465 | handler); |
| 466 | if (handler == nullptr) |
| 467 | return REAL(dispatch_source_set_cancel_handler)(source, nullptr); |
| 468 | dispatch_block_t block = ^(void) { |
| 469 | handler(dispatch_get_context(object: source)); |
| 470 | }; |
| 471 | WRAP(dispatch_source_set_cancel_handler)(source, handler: block); |
| 472 | } |
| 473 | |
| 474 | TSAN_INTERCEPTOR(void, dispatch_source_set_registration_handler, |
| 475 | dispatch_source_t source, dispatch_block_t handler) { |
| 476 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_registration_handler, source, |
| 477 | handler); |
| 478 | if (handler == nullptr) |
| 479 | return REAL(dispatch_source_set_registration_handler)(source, nullptr); |
| 480 | dispatch_queue_t q = GetTargetQueueFromSource(source); |
| 481 | __block block_context_t new_context = { |
| 482 | q, handler, &invoke_block, false, false, false, 0}; |
| 483 | dispatch_block_t new_handler = Block_copy(^(void) { |
| 484 | new_context.orig_context = handler; // To explicitly capture "handler". |
| 485 | dispatch_callback_wrap(&new_context); |
| 486 | }); |
| 487 | uptr submit_sync = (uptr)&new_context; |
| 488 | Release(thr, pc, addr: submit_sync); |
| 489 | REAL(dispatch_source_set_registration_handler)(source, new_handler); |
| 490 | Block_release(new_handler); |
| 491 | } |
| 492 | |
| 493 | TSAN_INTERCEPTOR(void, dispatch_source_set_registration_handler_f, |
| 494 | dispatch_source_t source, dispatch_function_t handler) { |
| 495 | SCOPED_TSAN_INTERCEPTOR(dispatch_source_set_registration_handler_f, source, |
| 496 | handler); |
| 497 | if (handler == nullptr) |
| 498 | return REAL(dispatch_source_set_registration_handler)(source, nullptr); |
| 499 | dispatch_block_t block = ^(void) { |
| 500 | handler(dispatch_get_context(object: source)); |
| 501 | }; |
| 502 | WRAP(dispatch_source_set_registration_handler)(source, handler: block); |
| 503 | } |
| 504 | |
| 505 | TSAN_INTERCEPTOR(void, dispatch_apply, size_t iterations, |
| 506 | dispatch_queue_t queue, |
| 507 | DISPATCH_NOESCAPE void (^block)(size_t)) { |
| 508 | SCOPED_TSAN_INTERCEPTOR(dispatch_apply, iterations, queue, block); |
| 509 | |
| 510 | u8 sync1, sync2; |
| 511 | uptr parent_to_child_sync = (uptr)&sync1; |
| 512 | uptr child_to_parent_sync = (uptr)&sync2; |
| 513 | |
| 514 | Release(thr, pc, addr: parent_to_child_sync); |
| 515 | void (^new_block)(size_t) = ^(size_t iteration) { |
| 516 | SCOPED_INTERCEPTOR_RAW(dispatch_apply); |
| 517 | Acquire(thr, pc, addr: parent_to_child_sync); |
| 518 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 519 | block(iteration); |
| 520 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 521 | Release(thr, pc, addr: child_to_parent_sync); |
| 522 | }; |
| 523 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 524 | REAL(dispatch_apply)(iterations, queue, new_block); |
| 525 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 526 | Acquire(thr, pc, addr: child_to_parent_sync); |
| 527 | } |
| 528 | |
| 529 | static void invoke_block_iteration(void *param, size_t iteration) { |
| 530 | auto block = (void (^)(size_t)) param; |
| 531 | block(iteration); |
| 532 | } |
| 533 | |
| 534 | TSAN_INTERCEPTOR(void, dispatch_apply_f, size_t iterations, |
| 535 | dispatch_queue_t queue, void *context, |
| 536 | void (*work)(void *, size_t)) { |
| 537 | SCOPED_TSAN_INTERCEPTOR(dispatch_apply_f, iterations, queue, context, work); |
| 538 | |
| 539 | // Unfortunately, we cannot delegate to dispatch_apply, since libdispatch |
| 540 | // implements dispatch_apply in terms of dispatch_apply_f. |
| 541 | u8 sync1, sync2; |
| 542 | uptr parent_to_child_sync = (uptr)&sync1; |
| 543 | uptr child_to_parent_sync = (uptr)&sync2; |
| 544 | |
| 545 | Release(thr, pc, addr: parent_to_child_sync); |
| 546 | void (^new_block)(size_t) = ^(size_t iteration) { |
| 547 | SCOPED_INTERCEPTOR_RAW(dispatch_apply_f); |
| 548 | Acquire(thr, pc, addr: parent_to_child_sync); |
| 549 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 550 | work(context, iteration); |
| 551 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 552 | Release(thr, pc, addr: child_to_parent_sync); |
| 553 | }; |
| 554 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 555 | REAL(dispatch_apply_f)(iterations, queue, new_block, invoke_block_iteration); |
| 556 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 557 | Acquire(thr, pc, addr: child_to_parent_sync); |
| 558 | } |
| 559 | |
| 560 | DECLARE_REAL_AND_INTERCEPTOR(void, free, void *ptr) |
| 561 | DECLARE_REAL_AND_INTERCEPTOR(int, munmap, void *addr, SIZE_T sz) |
| 562 | |
| 563 | TSAN_INTERCEPTOR(dispatch_data_t, dispatch_data_create, const void *buffer, |
| 564 | size_t size, dispatch_queue_t q, dispatch_block_t destructor) { |
| 565 | SCOPED_TSAN_INTERCEPTOR(dispatch_data_create, buffer, size, q, destructor); |
| 566 | if ((q == nullptr) || (destructor == DISPATCH_DATA_DESTRUCTOR_DEFAULT)) |
| 567 | return REAL(dispatch_data_create)(buffer, size, q, destructor); |
| 568 | |
| 569 | if (destructor == DISPATCH_DATA_DESTRUCTOR_FREE) |
| 570 | destructor = ^(void) { WRAP(free)(ptr: (void *)(uintptr_t)buffer); }; |
| 571 | else if (destructor == DISPATCH_DATA_DESTRUCTOR_MUNMAP) |
| 572 | destructor = ^(void) { WRAP(munmap)(addr: (void *)(uintptr_t)buffer, sz: size); }; |
| 573 | |
| 574 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 575 | dispatch_block_t heap_block = Block_copy(destructor); |
| 576 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 577 | block_context_t *new_context = |
| 578 | AllocContext(thr, pc, queue: q, orig_context: heap_block, orig_work: &invoke_and_release_block); |
| 579 | uptr submit_sync = (uptr)new_context; |
| 580 | Release(thr, pc, addr: submit_sync); |
| 581 | return REAL(dispatch_data_create)(buffer, size, q, ^(void) { |
| 582 | dispatch_callback_wrap(param: new_context); |
| 583 | }); |
| 584 | } |
| 585 | |
| 586 | typedef void (^fd_handler_t)(dispatch_data_t data, int error); |
| 587 | typedef void (^cleanup_handler_t)(int error); |
| 588 | |
| 589 | TSAN_INTERCEPTOR(void, dispatch_read, dispatch_fd_t fd, size_t length, |
| 590 | dispatch_queue_t q, fd_handler_t h) { |
| 591 | SCOPED_TSAN_INTERCEPTOR(dispatch_read, fd, length, q, h); |
| 592 | __block block_context_t new_context = { |
| 593 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 594 | fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) { |
| 595 | new_context.orig_context = ^(void) { |
| 596 | h(data, error); |
| 597 | }; |
| 598 | dispatch_callback_wrap(&new_context); |
| 599 | }); |
| 600 | uptr submit_sync = (uptr)&new_context; |
| 601 | Release(thr, pc, addr: submit_sync); |
| 602 | REAL(dispatch_read)(fd, length, q, new_h); |
| 603 | Block_release(new_h); |
| 604 | } |
| 605 | |
| 606 | TSAN_INTERCEPTOR(void, dispatch_write, dispatch_fd_t fd, dispatch_data_t data, |
| 607 | dispatch_queue_t q, fd_handler_t h) { |
| 608 | SCOPED_TSAN_INTERCEPTOR(dispatch_write, fd, data, q, h); |
| 609 | __block block_context_t new_context = { |
| 610 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 611 | fd_handler_t new_h = Block_copy(^(dispatch_data_t data, int error) { |
| 612 | new_context.orig_context = ^(void) { |
| 613 | h(data, error); |
| 614 | }; |
| 615 | dispatch_callback_wrap(&new_context); |
| 616 | }); |
| 617 | uptr submit_sync = (uptr)&new_context; |
| 618 | Release(thr, pc, addr: submit_sync); |
| 619 | REAL(dispatch_write)(fd, data, q, new_h); |
| 620 | Block_release(new_h); |
| 621 | } |
| 622 | |
| 623 | TSAN_INTERCEPTOR(void, dispatch_io_read, dispatch_io_t channel, off_t offset, |
| 624 | size_t length, dispatch_queue_t q, dispatch_io_handler_t h) { |
| 625 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_read, channel, offset, length, q, h); |
| 626 | __block block_context_t new_context = { |
| 627 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 628 | dispatch_io_handler_t new_h = |
| 629 | Block_copy(^(bool done, dispatch_data_t data, int error) { |
| 630 | new_context.orig_context = ^(void) { |
| 631 | h(done, data, error); |
| 632 | }; |
| 633 | dispatch_callback_wrap(&new_context); |
| 634 | }); |
| 635 | uptr submit_sync = (uptr)&new_context; |
| 636 | Release(thr, pc, addr: submit_sync); |
| 637 | REAL(dispatch_io_read)(channel, offset, length, q, new_h); |
| 638 | Block_release(new_h); |
| 639 | } |
| 640 | |
| 641 | TSAN_INTERCEPTOR(void, dispatch_io_write, dispatch_io_t channel, off_t offset, |
| 642 | dispatch_data_t data, dispatch_queue_t q, |
| 643 | dispatch_io_handler_t h) { |
| 644 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_write, channel, offset, data, q, h); |
| 645 | __block block_context_t new_context = { |
| 646 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 647 | dispatch_io_handler_t new_h = |
| 648 | Block_copy(^(bool done, dispatch_data_t data, int error) { |
| 649 | new_context.orig_context = ^(void) { |
| 650 | h(done, data, error); |
| 651 | }; |
| 652 | dispatch_callback_wrap(&new_context); |
| 653 | }); |
| 654 | uptr submit_sync = (uptr)&new_context; |
| 655 | Release(thr, pc, addr: submit_sync); |
| 656 | REAL(dispatch_io_write)(channel, offset, data, q, new_h); |
| 657 | Block_release(new_h); |
| 658 | } |
| 659 | |
| 660 | TSAN_INTERCEPTOR(void, dispatch_io_barrier, dispatch_io_t channel, |
| 661 | dispatch_block_t barrier) { |
| 662 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_barrier, channel, barrier); |
| 663 | __block block_context_t new_context = { |
| 664 | nullptr, nullptr, &invoke_block, false, false, false, 0}; |
| 665 | new_context.non_queue_sync_object = (uptr)channel; |
| 666 | new_context.is_barrier_block = true; |
| 667 | dispatch_block_t new_block = Block_copy(^(void) { |
| 668 | new_context.orig_context = ^(void) { |
| 669 | barrier(); |
| 670 | }; |
| 671 | dispatch_callback_wrap(&new_context); |
| 672 | }); |
| 673 | uptr submit_sync = (uptr)&new_context; |
| 674 | Release(thr, pc, addr: submit_sync); |
| 675 | REAL(dispatch_io_barrier)(channel, new_block); |
| 676 | Block_release(new_block); |
| 677 | } |
| 678 | |
| 679 | TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create, dispatch_io_type_t type, |
| 680 | dispatch_fd_t fd, dispatch_queue_t q, cleanup_handler_t h) { |
| 681 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_create, type, fd, q, h); |
| 682 | __block dispatch_io_t new_channel = nullptr; |
| 683 | __block block_context_t new_context = { |
| 684 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 685 | cleanup_handler_t new_h = Block_copy(^(int error) { |
| 686 | { |
| 687 | SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback); |
| 688 | Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close. |
| 689 | } |
| 690 | new_context.orig_context = ^(void) { |
| 691 | h(error); |
| 692 | }; |
| 693 | dispatch_callback_wrap(&new_context); |
| 694 | }); |
| 695 | uptr submit_sync = (uptr)&new_context; |
| 696 | Release(thr, pc, addr: submit_sync); |
| 697 | new_channel = REAL(dispatch_io_create)(type, fd, q, new_h); |
| 698 | Block_release(new_h); |
| 699 | return new_channel; |
| 700 | } |
| 701 | |
| 702 | TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_path, |
| 703 | dispatch_io_type_t type, const char *path, int oflag, |
| 704 | mode_t mode, dispatch_queue_t q, cleanup_handler_t h) { |
| 705 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_path, type, path, oflag, mode, |
| 706 | q, h); |
| 707 | __block dispatch_io_t new_channel = nullptr; |
| 708 | __block block_context_t new_context = { |
| 709 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 710 | cleanup_handler_t new_h = Block_copy(^(int error) { |
| 711 | { |
| 712 | SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback); |
| 713 | Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close. |
| 714 | } |
| 715 | new_context.orig_context = ^(void) { |
| 716 | h(error); |
| 717 | }; |
| 718 | dispatch_callback_wrap(&new_context); |
| 719 | }); |
| 720 | uptr submit_sync = (uptr)&new_context; |
| 721 | Release(thr, pc, addr: submit_sync); |
| 722 | new_channel = |
| 723 | REAL(dispatch_io_create_with_path)(type, path, oflag, mode, q, new_h); |
| 724 | Block_release(new_h); |
| 725 | return new_channel; |
| 726 | } |
| 727 | |
| 728 | TSAN_INTERCEPTOR(dispatch_io_t, dispatch_io_create_with_io, |
| 729 | dispatch_io_type_t type, dispatch_io_t io, dispatch_queue_t q, |
| 730 | cleanup_handler_t h) { |
| 731 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_create_with_io, type, io, q, h); |
| 732 | __block dispatch_io_t new_channel = nullptr; |
| 733 | __block block_context_t new_context = { |
| 734 | q, nullptr, &invoke_block, false, false, false, 0}; |
| 735 | cleanup_handler_t new_h = Block_copy(^(int error) { |
| 736 | { |
| 737 | SCOPED_INTERCEPTOR_RAW(dispatch_io_create_callback); |
| 738 | Acquire(thr, pc, (uptr)new_channel); // Release() in dispatch_io_close. |
| 739 | } |
| 740 | new_context.orig_context = ^(void) { |
| 741 | h(error); |
| 742 | }; |
| 743 | dispatch_callback_wrap(&new_context); |
| 744 | }); |
| 745 | uptr submit_sync = (uptr)&new_context; |
| 746 | Release(thr, pc, addr: submit_sync); |
| 747 | new_channel = REAL(dispatch_io_create_with_io)(type, io, q, new_h); |
| 748 | Block_release(new_h); |
| 749 | return new_channel; |
| 750 | } |
| 751 | |
| 752 | TSAN_INTERCEPTOR(void, dispatch_io_close, dispatch_io_t channel, |
| 753 | dispatch_io_close_flags_t flags) { |
| 754 | SCOPED_TSAN_INTERCEPTOR(dispatch_io_close, channel, flags); |
| 755 | Release(thr, pc, addr: (uptr)channel); // Acquire() in dispatch_io_create[_*]. |
| 756 | return REAL(dispatch_io_close)(channel, flags); |
| 757 | } |
| 758 | |
| 759 | // Resuming a suspended queue needs to synchronize with all subsequent |
| 760 | // executions of blocks in that queue. |
| 761 | TSAN_INTERCEPTOR(void, dispatch_resume, dispatch_object_t o) { |
| 762 | SCOPED_TSAN_INTERCEPTOR(dispatch_resume, o); |
| 763 | Release(thr, pc, addr: (uptr)o); // Synchronizes with the Acquire() on serial_sync |
| 764 | // in dispatch_sync_pre_execute |
| 765 | return REAL(dispatch_resume)(o); |
| 766 | } |
| 767 | |
| 768 | void InitializeLibdispatchInterceptors() { |
| 769 | INTERCEPT_FUNCTION(dispatch_async); |
| 770 | INTERCEPT_FUNCTION(dispatch_async_f); |
| 771 | INTERCEPT_FUNCTION(dispatch_sync); |
| 772 | INTERCEPT_FUNCTION(dispatch_sync_f); |
| 773 | INTERCEPT_FUNCTION(dispatch_barrier_async); |
| 774 | INTERCEPT_FUNCTION(dispatch_barrier_async_f); |
| 775 | INTERCEPT_FUNCTION(dispatch_barrier_sync); |
| 776 | INTERCEPT_FUNCTION(dispatch_barrier_sync_f); |
| 777 | INTERCEPT_FUNCTION(dispatch_async_and_wait); |
| 778 | INTERCEPT_FUNCTION(dispatch_async_and_wait_f); |
| 779 | INTERCEPT_FUNCTION(dispatch_barrier_async_and_wait); |
| 780 | INTERCEPT_FUNCTION(dispatch_barrier_async_and_wait_f); |
| 781 | INTERCEPT_FUNCTION(dispatch_after); |
| 782 | INTERCEPT_FUNCTION(dispatch_after_f); |
| 783 | INTERCEPT_FUNCTION(dispatch_once); |
| 784 | INTERCEPT_FUNCTION(dispatch_once_f); |
| 785 | INTERCEPT_FUNCTION(dispatch_semaphore_signal); |
| 786 | INTERCEPT_FUNCTION(dispatch_semaphore_wait); |
| 787 | INTERCEPT_FUNCTION(dispatch_group_wait); |
| 788 | INTERCEPT_FUNCTION(dispatch_group_leave); |
| 789 | INTERCEPT_FUNCTION(dispatch_group_async); |
| 790 | INTERCEPT_FUNCTION(dispatch_group_async_f); |
| 791 | INTERCEPT_FUNCTION(dispatch_group_notify); |
| 792 | INTERCEPT_FUNCTION(dispatch_group_notify_f); |
| 793 | INTERCEPT_FUNCTION(dispatch_source_set_event_handler); |
| 794 | INTERCEPT_FUNCTION(dispatch_source_set_event_handler_f); |
| 795 | INTERCEPT_FUNCTION(dispatch_source_set_cancel_handler); |
| 796 | INTERCEPT_FUNCTION(dispatch_source_set_cancel_handler_f); |
| 797 | INTERCEPT_FUNCTION(dispatch_source_set_registration_handler); |
| 798 | INTERCEPT_FUNCTION(dispatch_source_set_registration_handler_f); |
| 799 | INTERCEPT_FUNCTION(dispatch_apply); |
| 800 | INTERCEPT_FUNCTION(dispatch_apply_f); |
| 801 | INTERCEPT_FUNCTION(dispatch_data_create); |
| 802 | INTERCEPT_FUNCTION(dispatch_read); |
| 803 | INTERCEPT_FUNCTION(dispatch_write); |
| 804 | INTERCEPT_FUNCTION(dispatch_io_read); |
| 805 | INTERCEPT_FUNCTION(dispatch_io_write); |
| 806 | INTERCEPT_FUNCTION(dispatch_io_barrier); |
| 807 | INTERCEPT_FUNCTION(dispatch_io_create); |
| 808 | INTERCEPT_FUNCTION(dispatch_io_create_with_path); |
| 809 | INTERCEPT_FUNCTION(dispatch_io_create_with_io); |
| 810 | INTERCEPT_FUNCTION(dispatch_io_close); |
| 811 | INTERCEPT_FUNCTION(dispatch_resume); |
| 812 | } |
| 813 | |
| 814 | } // namespace __tsan |
| 815 | |