1 //===-- tsan_libdispatch_mac.cc -------------------------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file is a part of ThreadSanitizer (TSan), a race detector.
11 //
12 // Mac-specific libdispatch (GCD) support.
13 //===----------------------------------------------------------------------===//
14 
15 #include "sanitizer_common/sanitizer_platform.h"
16 #if SANITIZER_MAC
17 
18 #include "sanitizer_common/sanitizer_common.h"
19 #include "interception/interception.h"
20 #include "tsan_interceptors.h"
21 #include "tsan_platform.h"
22 #include "tsan_rtl.h"
23 
24 #include <Block.h>
25 #include <dispatch/dispatch.h>
26 #include <pthread.h>
27 
28 typedef long long_t;  // NOLINT
29 
30 namespace __tsan {
31 
32 typedef struct {
33   dispatch_queue_t queue;
34   void *orig_context;
35   dispatch_function_t orig_work;
36   uptr object_to_acquire;
37   dispatch_object_t object_to_release;
38 } tsan_block_context_t;
39 
40 // The offsets of different fields of the dispatch_queue_t structure, exported
41 // by libdispatch.dylib.
42 extern "C" struct dispatch_queue_offsets_s {
43   const uint16_t dqo_version;
44   const uint16_t dqo_label;
45   const uint16_t dqo_label_size;
46   const uint16_t dqo_flags;
47   const uint16_t dqo_flags_size;
48   const uint16_t dqo_serialnum;
49   const uint16_t dqo_serialnum_size;
50   const uint16_t dqo_width;
51   const uint16_t dqo_width_size;
52   const uint16_t dqo_running;
53   const uint16_t dqo_running_size;
54   const uint16_t dqo_suspend_cnt;
55   const uint16_t dqo_suspend_cnt_size;
56   const uint16_t dqo_target_queue;
57   const uint16_t dqo_target_queue_size;
58   const uint16_t dqo_priority;
59   const uint16_t dqo_priority_size;
60 } dispatch_queue_offsets;
61 
IsQueueSerial(dispatch_queue_t q)62 static bool IsQueueSerial(dispatch_queue_t q) {
63   CHECK_EQ(dispatch_queue_offsets.dqo_width_size, 2);
64   uptr width = *(uint16_t *)(((uptr)q) + dispatch_queue_offsets.dqo_width);
65   CHECK_NE(width, 0);
66   return width == 1;
67 }
68 
AllocContext(ThreadState * thr,uptr pc,dispatch_queue_t queue,void * orig_context,dispatch_function_t orig_work)69 static tsan_block_context_t *AllocContext(ThreadState *thr, uptr pc,
70                                           dispatch_queue_t queue,
71                                           void *orig_context,
72                                           dispatch_function_t orig_work) {
73   tsan_block_context_t *new_context =
74       (tsan_block_context_t *)user_alloc(thr, pc, sizeof(tsan_block_context_t));
75   new_context->queue = queue;
76   new_context->orig_context = orig_context;
77   new_context->orig_work = orig_work;
78   new_context->object_to_acquire = (uptr)new_context;
79   new_context->object_to_release = nullptr;
80   return new_context;
81 }
82 
dispatch_callback_wrap_acquire(void * param)83 static void dispatch_callback_wrap_acquire(void *param) {
84   SCOPED_INTERCEPTOR_RAW(dispatch_async_f_callback_wrap);
85   tsan_block_context_t *context = (tsan_block_context_t *)param;
86   Acquire(thr, pc, context->object_to_acquire);
87 
88   // Extra retain/release is required for dispatch groups. We use the group
89   // itself to synchronize, but in a notification (dispatch_group_notify
90   // callback), it may be disposed already. To solve this, we retain the group
91   // and release it here.
92   if (context->object_to_release) dispatch_release(context->object_to_release);
93 
94   // In serial queues, work items can be executed on different threads, we need
95   // to explicitly synchronize on the queue itself.
96   if (IsQueueSerial(context->queue)) Acquire(thr, pc, (uptr)context->queue);
97   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
98   context->orig_work(context->orig_context);
99   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
100   if (IsQueueSerial(context->queue)) Release(thr, pc, (uptr)context->queue);
101   user_free(thr, pc, context);
102 }
103 
invoke_and_release_block(void * param)104 static void invoke_and_release_block(void *param) {
105   dispatch_block_t block = (dispatch_block_t)param;
106   block();
107   Block_release(block);
108 }
109 
110 #define DISPATCH_INTERCEPT_B(name)                                           \
111   TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, dispatch_block_t block) { \
112     SCOPED_TSAN_INTERCEPTOR(name, q, block);                                 \
113     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
114     dispatch_block_t heap_block = Block_copy(block);                         \
115     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
116     tsan_block_context_t *new_context =                                      \
117         AllocContext(thr, pc, q, heap_block, &invoke_and_release_block);     \
118     Release(thr, pc, (uptr)new_context);                                     \
119     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
120     REAL(name##_f)(q, new_context, dispatch_callback_wrap_acquire);          \
121     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
122   }
123 
124 #define DISPATCH_INTERCEPT_F(name)                                \
125   TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, void *context, \
126                    dispatch_function_t work) {                    \
127     SCOPED_TSAN_INTERCEPTOR(name, q, context, work);              \
128     tsan_block_context_t *new_context =                           \
129         AllocContext(thr, pc, q, context, work);                  \
130     Release(thr, pc, (uptr)new_context);                          \
131     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
132     REAL(name)(q, new_context, dispatch_callback_wrap_acquire);   \
133     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
134   }
135 
136 // We wrap dispatch_async, dispatch_sync and friends where we allocate a new
137 // context, which is used to synchronize (we release the context before
138 // submitting, and the callback acquires it before executing the original
139 // callback).
140 DISPATCH_INTERCEPT_B(dispatch_async)
DISPATCH_INTERCEPT_B(dispatch_barrier_async)141 DISPATCH_INTERCEPT_B(dispatch_barrier_async)
142 DISPATCH_INTERCEPT_F(dispatch_async_f)
143 DISPATCH_INTERCEPT_F(dispatch_barrier_async_f)
144 DISPATCH_INTERCEPT_B(dispatch_sync)
145 DISPATCH_INTERCEPT_B(dispatch_barrier_sync)
146 DISPATCH_INTERCEPT_F(dispatch_sync_f)
147 DISPATCH_INTERCEPT_F(dispatch_barrier_sync_f)
148 
149 // GCD's dispatch_once implementation has a fast path that contains a racy read
150 // and it's inlined into user's code. Furthermore, this fast path doesn't
151 // establish a proper happens-before relations between the initialization and
152 // code following the call to dispatch_once. We could deal with this in
153 // instrumented code, but there's not much we can do about it in system
154 // libraries. Let's disable the fast path (by never storing the value ~0 to
155 // predicate), so the interceptor is always called, and let's add proper release
156 // and acquire semantics. Since TSan does not see its own atomic stores, the
157 // race on predicate won't be reported - the only accesses to it that TSan sees
158 // are the loads on the fast path. Loads don't race. Secondly, dispatch_once is
159 // both a macro and a real function, we want to intercept the function, so we
160 // need to undefine the macro.
161 #undef dispatch_once
162 TSAN_INTERCEPTOR(void, dispatch_once, dispatch_once_t *predicate,
163                  dispatch_block_t block) {
164   SCOPED_TSAN_INTERCEPTOR(dispatch_once, predicate, block);
165   atomic_uint32_t *a = reinterpret_cast<atomic_uint32_t *>(predicate);
166   u32 v = atomic_load(a, memory_order_acquire);
167   if (v == 0 &&
168       atomic_compare_exchange_strong(a, &v, 1, memory_order_relaxed)) {
169     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
170     block();
171     SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
172     Release(thr, pc, (uptr)a);
173     atomic_store(a, 2, memory_order_release);
174   } else {
175     while (v != 2) {
176       internal_sched_yield();
177       v = atomic_load(a, memory_order_acquire);
178     }
179     Acquire(thr, pc, (uptr)a);
180   }
181 }
182 
183 #undef dispatch_once_f
TSAN_INTERCEPTOR(void,dispatch_once_f,dispatch_once_t * predicate,void * context,dispatch_function_t function)184 TSAN_INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *predicate,
185                  void *context, dispatch_function_t function) {
186   SCOPED_TSAN_INTERCEPTOR(dispatch_once_f, predicate, context, function);
187   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
188   WRAP(dispatch_once)(predicate, ^(void) {
189     function(context);
190   });
191   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
192 }
193 
TSAN_INTERCEPTOR(long_t,dispatch_semaphore_signal,dispatch_semaphore_t dsema)194 TSAN_INTERCEPTOR(long_t, dispatch_semaphore_signal,
195                  dispatch_semaphore_t dsema) {
196   SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_signal, dsema);
197   Release(thr, pc, (uptr)dsema);
198   return REAL(dispatch_semaphore_signal)(dsema);
199 }
200 
TSAN_INTERCEPTOR(long_t,dispatch_semaphore_wait,dispatch_semaphore_t dsema,dispatch_time_t timeout)201 TSAN_INTERCEPTOR(long_t, dispatch_semaphore_wait, dispatch_semaphore_t dsema,
202                  dispatch_time_t timeout) {
203   SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_wait, dsema, timeout);
204   long_t result = REAL(dispatch_semaphore_wait)(dsema, timeout);
205   if (result == 0) Acquire(thr, pc, (uptr)dsema);
206   return result;
207 }
208 
TSAN_INTERCEPTOR(long_t,dispatch_group_wait,dispatch_group_t group,dispatch_time_t timeout)209 TSAN_INTERCEPTOR(long_t, dispatch_group_wait, dispatch_group_t group,
210                  dispatch_time_t timeout) {
211   SCOPED_TSAN_INTERCEPTOR(dispatch_group_wait, group, timeout);
212   long_t result = REAL(dispatch_group_wait)(group, timeout);
213   if (result == 0) Acquire(thr, pc, (uptr)group);
214   return result;
215 }
216 
TSAN_INTERCEPTOR(void,dispatch_group_leave,dispatch_group_t group)217 TSAN_INTERCEPTOR(void, dispatch_group_leave, dispatch_group_t group) {
218   SCOPED_TSAN_INTERCEPTOR(dispatch_group_leave, group);
219   Release(thr, pc, (uptr)group);
220   REAL(dispatch_group_leave)(group);
221 }
222 
TSAN_INTERCEPTOR(void,dispatch_group_async,dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block)223 TSAN_INTERCEPTOR(void, dispatch_group_async, dispatch_group_t group,
224                  dispatch_queue_t queue, dispatch_block_t block) {
225   SCOPED_TSAN_INTERCEPTOR(dispatch_group_async, group, queue, block);
226   dispatch_retain(group);
227   dispatch_group_enter(group);
228   WRAP(dispatch_async)(queue, ^(void) {
229     block();
230     WRAP(dispatch_group_leave)(group);
231     dispatch_release(group);
232   });
233 }
234 
TSAN_INTERCEPTOR(void,dispatch_group_async_f,dispatch_group_t group,dispatch_queue_t queue,void * context,dispatch_function_t work)235 TSAN_INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
236                  dispatch_queue_t queue, void *context,
237                  dispatch_function_t work) {
238   SCOPED_TSAN_INTERCEPTOR(dispatch_group_async_f, group, queue, context, work);
239   dispatch_retain(group);
240   dispatch_group_enter(group);
241   WRAP(dispatch_async)(queue, ^(void) {
242     work(context);
243     WRAP(dispatch_group_leave)(group);
244     dispatch_release(group);
245   });
246 }
247 
TSAN_INTERCEPTOR(void,dispatch_group_notify,dispatch_group_t group,dispatch_queue_t q,dispatch_block_t block)248 TSAN_INTERCEPTOR(void, dispatch_group_notify, dispatch_group_t group,
249                  dispatch_queue_t q, dispatch_block_t block) {
250   SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify, group, q, block);
251   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
252   dispatch_block_t heap_block = Block_copy(block);
253   SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
254   tsan_block_context_t *new_context =
255       AllocContext(thr, pc, q, heap_block, &invoke_and_release_block);
256   new_context->object_to_acquire = (uptr)group;
257 
258   // Will be released in dispatch_callback_wrap_acquire.
259   new_context->object_to_release = group;
260   dispatch_retain(group);
261 
262   Release(thr, pc, (uptr)group);
263   REAL(dispatch_group_notify_f)(group, q, new_context,
264                                 dispatch_callback_wrap_acquire);
265 }
266 
TSAN_INTERCEPTOR(void,dispatch_group_notify_f,dispatch_group_t group,dispatch_queue_t q,void * context,dispatch_function_t work)267 TSAN_INTERCEPTOR(void, dispatch_group_notify_f, dispatch_group_t group,
268                  dispatch_queue_t q, void *context, dispatch_function_t work) {
269   SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify_f, group, q, context, work);
270   tsan_block_context_t *new_context = AllocContext(thr, pc, q, context, work);
271   new_context->object_to_acquire = (uptr)group;
272 
273   // Will be released in dispatch_callback_wrap_acquire.
274   new_context->object_to_release = group;
275   dispatch_retain(group);
276 
277   Release(thr, pc, (uptr)group);
278   REAL(dispatch_group_notify_f)(group, q, new_context,
279                                 dispatch_callback_wrap_acquire);
280 }
281 
282 }  // namespace __tsan
283 
284 #endif  // SANITIZER_MAC
285