| 1 | //===-- lsan_mac.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 LeakSanitizer, a memory leak checker. |
| 10 | // |
| 11 | // Mac-specific details. |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "sanitizer_common/sanitizer_platform.h" |
| 15 | #if SANITIZER_APPLE |
| 16 | |
| 17 | #include "interception/interception.h" |
| 18 | #include "lsan.h" |
| 19 | #include "lsan_allocator.h" |
| 20 | #include "lsan_thread.h" |
| 21 | |
| 22 | #include <pthread.h> |
| 23 | |
| 24 | namespace __lsan { |
| 25 | // Support for the following functions from libdispatch on Mac OS: |
| 26 | // dispatch_async_f() |
| 27 | // dispatch_async() |
| 28 | // dispatch_sync_f() |
| 29 | // dispatch_sync() |
| 30 | // dispatch_after_f() |
| 31 | // dispatch_after() |
| 32 | // dispatch_group_async_f() |
| 33 | // dispatch_group_async() |
| 34 | // TODO(glider): libdispatch API contains other functions that we don't support |
| 35 | // yet. |
| 36 | // |
| 37 | // dispatch_sync() and dispatch_sync_f() are synchronous, although chances are |
| 38 | // they can cause jobs to run on a thread different from the current one. |
| 39 | // TODO(glider): if so, we need a test for this (otherwise we should remove |
| 40 | // them). |
| 41 | // |
| 42 | // The following functions use dispatch_barrier_async_f() (which isn't a library |
| 43 | // function but is exported) and are thus supported: |
| 44 | // dispatch_source_set_cancel_handler_f() |
| 45 | // dispatch_source_set_cancel_handler() |
| 46 | // dispatch_source_set_event_handler_f() |
| 47 | // dispatch_source_set_event_handler() |
| 48 | // |
| 49 | // The reference manual for Grand Central Dispatch is available at |
| 50 | // http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html |
| 51 | // The implementation details are at |
| 52 | // http://libdispatch.macosforge.org/trac/browser/trunk/src/queue.c |
| 53 | |
| 54 | typedef void *dispatch_group_t; |
| 55 | typedef void *dispatch_queue_t; |
| 56 | typedef void *dispatch_source_t; |
| 57 | typedef u64 dispatch_time_t; |
| 58 | typedef void (*dispatch_function_t)(void *block); |
| 59 | typedef void *(*worker_t)(void *block); |
| 60 | |
| 61 | // A wrapper for the ObjC blocks used to support libdispatch. |
| 62 | typedef struct { |
| 63 | void *block; |
| 64 | dispatch_function_t func; |
| 65 | u32 parent_tid; |
| 66 | } lsan_block_context_t; |
| 67 | |
| 68 | ALWAYS_INLINE |
| 69 | void lsan_register_worker_thread(int parent_tid) { |
| 70 | if (GetCurrentThreadId() == kInvalidTid) { |
| 71 | u32 tid = ThreadCreate(parent_tid, true); |
| 72 | ThreadStart(tid, GetTid()); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | // For use by only those functions that allocated the context via |
| 77 | // alloc_lsan_context(). |
| 78 | extern "C" void lsan_dispatch_call_block_and_release(void *block) { |
| 79 | lsan_block_context_t *context = (lsan_block_context_t *)block; |
| 80 | VReport(2, |
| 81 | "lsan_dispatch_call_block_and_release(): " |
| 82 | "context: %p, pthread_self: %p\n" , |
| 83 | block, (void*)pthread_self()); |
| 84 | lsan_register_worker_thread(context->parent_tid); |
| 85 | // Call the original dispatcher for the block. |
| 86 | context->func(context->block); |
| 87 | lsan_free(context); |
| 88 | } |
| 89 | |
| 90 | } // namespace __lsan |
| 91 | |
| 92 | using namespace __lsan; |
| 93 | |
| 94 | // Wrap |ctxt| and |func| into an lsan_block_context_t. |
| 95 | // The caller retains control of the allocated context. |
| 96 | extern "C" lsan_block_context_t *alloc_lsan_context(void *ctxt, |
| 97 | dispatch_function_t func) { |
| 98 | GET_STACK_TRACE_THREAD; |
| 99 | lsan_block_context_t *lsan_ctxt = |
| 100 | (lsan_block_context_t *)lsan_malloc(sizeof(lsan_block_context_t), stack); |
| 101 | lsan_ctxt->block = ctxt; |
| 102 | lsan_ctxt->func = func; |
| 103 | lsan_ctxt->parent_tid = GetCurrentThreadId(); |
| 104 | return lsan_ctxt; |
| 105 | } |
| 106 | |
| 107 | // Define interceptor for dispatch_*_f function with the three most common |
| 108 | // parameters: dispatch_queue_t, context, dispatch_function_t. |
| 109 | #define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \ |
| 110 | INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void *ctxt, \ |
| 111 | dispatch_function_t func) { \ |
| 112 | lsan_block_context_t *lsan_ctxt = alloc_lsan_context(ctxt, func); \ |
| 113 | return REAL(dispatch_x_f)(dq, (void *)lsan_ctxt, \ |
| 114 | lsan_dispatch_call_block_and_release); \ |
| 115 | } |
| 116 | |
| 117 | INTERCEPT_DISPATCH_X_F_3(dispatch_async_f) |
| 118 | INTERCEPT_DISPATCH_X_F_3(dispatch_sync_f) |
| 119 | INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f) |
| 120 | |
| 121 | INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq, |
| 122 | void *ctxt, dispatch_function_t func) { |
| 123 | lsan_block_context_t *lsan_ctxt = alloc_lsan_context(ctxt, func); |
| 124 | return REAL(dispatch_after_f)(when, dq, (void *)lsan_ctxt, |
| 125 | lsan_dispatch_call_block_and_release); |
| 126 | } |
| 127 | |
| 128 | INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, |
| 129 | dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { |
| 130 | lsan_block_context_t *lsan_ctxt = alloc_lsan_context(ctxt, func); |
| 131 | REAL(dispatch_group_async_f) |
| 132 | (group, dq, (void *)lsan_ctxt, lsan_dispatch_call_block_and_release); |
| 133 | } |
| 134 | |
| 135 | #if !defined(MISSING_BLOCKS_SUPPORT) |
| 136 | extern "C" { |
| 137 | void dispatch_async(dispatch_queue_t dq, void (^work)(void)); |
| 138 | void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, |
| 139 | void (^work)(void)); |
| 140 | void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, |
| 141 | void (^work)(void)); |
| 142 | void dispatch_source_set_cancel_handler(dispatch_source_t ds, |
| 143 | void (^work)(void)); |
| 144 | void dispatch_source_set_event_handler(dispatch_source_t ds, |
| 145 | void (^work)(void)); |
| 146 | } |
| 147 | |
| 148 | # define GET_LSAN_BLOCK(work) \ |
| 149 | void (^lsan_block)(void); \ |
| 150 | int parent_tid = GetCurrentThreadId(); \ |
| 151 | lsan_block = ^(void) { \ |
| 152 | lsan_register_worker_thread(parent_tid); \ |
| 153 | work(); \ |
| 154 | } |
| 155 | |
| 156 | INTERCEPTOR(void, dispatch_async, dispatch_queue_t dq, void (^work)(void)) { |
| 157 | GET_LSAN_BLOCK(work); |
| 158 | REAL(dispatch_async)(dq, lsan_block); |
| 159 | } |
| 160 | |
| 161 | INTERCEPTOR(void, dispatch_group_async, dispatch_group_t dg, |
| 162 | dispatch_queue_t dq, void (^work)(void)) { |
| 163 | GET_LSAN_BLOCK(work); |
| 164 | REAL(dispatch_group_async)(dg, dq, lsan_block); |
| 165 | } |
| 166 | |
| 167 | INTERCEPTOR(void, dispatch_after, dispatch_time_t when, dispatch_queue_t queue, |
| 168 | void (^work)(void)) { |
| 169 | GET_LSAN_BLOCK(work); |
| 170 | REAL(dispatch_after)(when, queue, lsan_block); |
| 171 | } |
| 172 | |
| 173 | INTERCEPTOR(void, dispatch_source_set_cancel_handler, dispatch_source_t ds, |
| 174 | void (^work)(void)) { |
| 175 | if (!work) { |
| 176 | REAL(dispatch_source_set_cancel_handler)(ds, work); |
| 177 | return; |
| 178 | } |
| 179 | GET_LSAN_BLOCK(work); |
| 180 | REAL(dispatch_source_set_cancel_handler)(ds, lsan_block); |
| 181 | } |
| 182 | |
| 183 | INTERCEPTOR(void, dispatch_source_set_event_handler, dispatch_source_t ds, |
| 184 | void (^work)(void)) { |
| 185 | GET_LSAN_BLOCK(work); |
| 186 | REAL(dispatch_source_set_event_handler)(ds, lsan_block); |
| 187 | } |
| 188 | #endif |
| 189 | |
| 190 | #endif // SANITIZER_APPLE |
| 191 | |