Index: lib/asan/asan_mac.cc =================================================================== --- lib/asan/asan_mac.cc +++ lib/asan/asan_mac.cc @@ -20,6 +20,7 @@ #include "asan_mapping.h" #include "asan_stack.h" #include "asan_thread.h" +#include "lsan/lsan_common.h" #include "sanitizer_common/sanitizer_atomic.h" #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_mac.h" @@ -122,6 +123,7 @@ typedef void* dispatch_queue_t; typedef void* dispatch_source_t; typedef u64 dispatch_time_t; +typedef long dispatch_once_t; // NOLINT typedef void (*dispatch_function_t)(void *block); typedef void* (*worker_t)(void *block); @@ -130,6 +132,9 @@ void *block; dispatch_function_t func; u32 parent_tid; +#if CAN_SANITIZE_LEAKS + bool disable_leak_checks; +#endif } asan_block_context_t; ALWAYS_INLINE @@ -156,8 +161,14 @@ "context: %p, pthread_self: %p\n", block, pthread_self()); asan_register_worker_thread(context->parent_tid, &stack); +#if CAN_SANITIZE_LEAKS + if (context->disable_leak_checks) __lsan::DisableInThisThread(); +#endif // Call the original dispatcher for the block. context->func(context->block); +#if CAN_SANITIZE_LEAKS + if (context->disable_leak_checks) __lsan::EnableInThisThread(); +#endif asan_free(context, &stack, FROM_MALLOC); } @@ -175,6 +186,9 @@ asan_ctxt->block = ctxt; asan_ctxt->func = func; asan_ctxt->parent_tid = GetCurrentTidOrInvalid(); +#if CAN_SANITIZE_LEAKS + asan_ctxt->disable_leak_checks = false; +#endif return asan_ctxt; } @@ -225,6 +239,22 @@ asan_dispatch_call_block_and_release); } +INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *pred, void *ctxt, + dispatch_function_t func) { + GET_STACK_TRACE_THREAD; + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); + if (Verbosity() >= 2) { + Report("dispatch_once_f(): context: %p, pthread_self: %p\n", asan_ctxt, + pthread_self()); + PRINT_CURRENT_STACK(); + } +#if CAN_SANITIZE_LEAKS + asan_ctxt->disable_leak_checks = __lsan::flags()->use_globals; +#endif + REAL(dispatch_once_f) + (pred, (void *)asan_ctxt, asan_dispatch_call_block_and_release); +} + #if !defined(MISSING_BLOCKS_SUPPORT) extern "C" { void dispatch_async(dispatch_queue_t dq, void(^work)(void)); @@ -235,6 +265,7 @@ void dispatch_source_set_cancel_handler(dispatch_source_t ds, void(^work)(void)); void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void)); +void dispatch_once(dispatch_once_t *pred, void (^work)(void)); } #define GET_ASAN_BLOCK(work) \ @@ -284,6 +315,24 @@ GET_ASAN_BLOCK(work); REAL(dispatch_source_set_event_handler)(ds, asan_block); } + +// allocations inside dispatch_once blocks are often used to initialize program +// singletons, and can be treated as global allocations, as they are only +// executed once per program. +INTERCEPTOR(void, dispatch_once, dispatch_once_t *pred, void (^work)(void)) { + ENABLE_FRAME_POINTER; + void (^global_block)(void) = work; +#if CAN_SANITIZE_LEAKS + if (__lsan::flags()->use_globals) { + global_block = ^(void) { + __lsan::ScopedInterceptorDisabler disabler; + work(); + }; + } +#endif + GET_ASAN_BLOCK(global_block); + REAL(dispatch_once)(pred, asan_block); +} #endif #endif // SANITIZER_MAC Index: lib/lsan/lsan_mac.cc =================================================================== --- lib/lsan/lsan_mac.cc +++ lib/lsan/lsan_mac.cc @@ -18,6 +18,7 @@ #include "interception/interception.h" #include "lsan.h" #include "lsan_allocator.h" +#include "lsan_common.h" #include "lsan_thread.h" #include @@ -56,6 +57,7 @@ typedef void *dispatch_queue_t; typedef void *dispatch_source_t; typedef u64 dispatch_time_t; +typedef long dispatch_once_t; // NOLINT typedef void (*dispatch_function_t)(void *block); typedef void *(*worker_t)(void *block); @@ -64,6 +66,7 @@ void *block; dispatch_function_t func; u32 parent_tid; + bool disable_leak_checks; } lsan_block_context_t; ALWAYS_INLINE @@ -84,8 +87,10 @@ "context: %p, pthread_self: %p\n", block, pthread_self()); lsan_register_worker_thread(context->parent_tid); + if (context->disable_leak_checks) DisableInThisThread(); // Call the original dispatcher for the block. context->func(context->block); + if (context->disable_leak_checks) EnableInThisThread(); lsan_free(context); } @@ -103,6 +108,7 @@ lsan_ctxt->block = ctxt; lsan_ctxt->func = func; lsan_ctxt->parent_tid = GetCurrentThread(); + lsan_ctxt->disable_leak_checks = false; return lsan_ctxt; } @@ -134,6 +140,14 @@ (group, dq, (void *)lsan_ctxt, lsan_dispatch_call_block_and_release); } +INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *pred, void *ctxt, + dispatch_function_t func) { + lsan_block_context_t *lsan_ctxt = alloc_lsan_context(ctxt, func); + lsan_ctxt->disable_leak_checks = flags()->use_globals; + return REAL(dispatch_once_f)(pred, (void *)lsan_ctxt, + lsan_dispatch_call_block_and_release); +} + #if !defined(MISSING_BLOCKS_SUPPORT) extern "C" { void dispatch_async(dispatch_queue_t dq, void (^work)(void)); @@ -145,6 +159,7 @@ void (^work)(void)); void dispatch_source_set_event_handler(dispatch_source_t ds, void (^work)(void)); +void dispatch_once(dispatch_once_t *pred, void (^work)(void)); } #define GET_LSAN_BLOCK(work) \ @@ -187,6 +202,23 @@ GET_LSAN_BLOCK(work); REAL(dispatch_source_set_event_handler)(ds, lsan_block); } + +// allocations inside dispatch_once blocks are often used to initialize program +// singletons, and can be treated as global allocations, as they are only +// executed once per program. +INTERCEPTOR(void, dispatch_once, dispatch_once_t *pred, void (^work)(void)) { + void (^global_block)(void); + if (flags()->use_globals) { + global_block = ^(void) { + ScopedInterceptorDisabler disabler; + work(); + }; + } else { + global_block = work; + } + GET_LSAN_BLOCK(global_block); + REAL(dispatch_once)(pred, lsan_block); +} #endif #endif // SANITIZER_MAC Index: test/lsan/TestCases/Darwin/blocks.mm =================================================================== --- /dev/null +++ test/lsan/TestCases/Darwin/blocks.mm @@ -0,0 +1,31 @@ +// Test for dispatch_once leaks +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t -framework Foundation +// RUN: %env_lsan_opts=$LSAN_BASE:use_globals=0 not %run %t 2>&1 | FileCheck %s +// RUN: %env_lsan_opts=$LSAN_BASE:use_globals=1 %run %t 2>&1 + +#include +#include + +#include "sanitizer_common/print_address.h" + +#define NUM_LOOPS 5 + +int main() { + __block void *singleton; + static dispatch_once_t once_token = 0; + dispatch_block_t block = ^{ + singleton = malloc(1337); + print_address("Test alloc: ", 1, singleton); + }; + + for (size_t i = 0; i < NUM_LOOPS; ++i) { + dispatch_once(&once_token, block); + } + return 0; +} + +// CHECK: Test alloc: [[addr:0x[0-9,a-f]+]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: [[addr]] (1337 bytes) +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s)