Index: lib/asan/asan_internal.h =================================================================== --- lib/asan/asan_internal.h +++ lib/asan/asan_internal.h @@ -98,6 +98,7 @@ void AsanTSDInit(void (*destructor)(void *tsd)); void *AsanTSDGet(); void AsanTSDSet(void *tsd); +void PlatformTSDDtor(void *tsd); void AppendToErrorMessageBuffer(const char *buffer); Index: lib/asan/asan_posix.cc =================================================================== --- lib/asan/asan_posix.cc +++ lib/asan/asan_posix.cc @@ -1,4 +1,4 @@ -//===-- asan_linux.cc -----------------------------------------------------===// +//===-- asan_posix.cc -----------------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -116,6 +116,15 @@ pthread_setspecific(tsd_key, tsd); } +void PlatformTSDDtor(void *tsd) { + AsanThreadContext *context = (AsanThreadContext*)tsd; + if (context->destructor_iterations > 1) { + context->destructor_iterations--; + CHECK_EQ(0, pthread_setspecific(tsd_key, tsd)); + return; + } + AsanThread::TSDDtor(tsd); +} } // namespace __asan #endif // SANITIZER_LINUX || SANITIZER_MAC Index: lib/asan/asan_rtl.cc =================================================================== --- lib/asan/asan_rtl.cc +++ lib/asan/asan_rtl.cc @@ -535,7 +535,7 @@ InstallSignalHandlers(); - AsanTSDInit(AsanThread::TSDDtor); + AsanTSDInit(PlatformTSDDtor); // Allocator should be initialized before starting external symbolizer, as // fork() on Mac locks the allocator. InitializeAllocator(); Index: lib/asan/asan_thread.h =================================================================== --- lib/asan/asan_thread.h +++ lib/asan/asan_thread.h @@ -19,6 +19,7 @@ #include "asan_fake_stack.h" #include "asan_stack.h" #include "asan_stats.h" +#include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_thread_registry.h" @@ -36,10 +37,12 @@ explicit AsanThreadContext(int tid) : ThreadContextBase(tid), announced(false), + destructor_iterations(kPthreadDestructorIterations), thread(0) { internal_memset(&stack, 0, sizeof(stack)); } bool announced; + int destructor_iterations; StackTrace stack; AsanThread *thread; Index: lib/asan/asan_thread.cc =================================================================== --- lib/asan/asan_thread.cc +++ lib/asan/asan_thread.cc @@ -165,7 +165,13 @@ malloc_storage().CommitBack(); if (flags()->use_sigaltstack) UnsetAlternateSignalStack(); - this->Destroy(); + // On POSIX systems we defer this to the TSD destructor. LSan will consider + // the thread's memory as non-live from the moment we call Destroy(), even + // though that memory might contain pointers to heap objects which will be + // cleaned up by a user-defined TSD destructor. Thus, calling Destroy() before + // the TSD destructors have run might cause false positives in LSan. + if (!SANITIZER_POSIX) + this->Destroy(); return res; } Index: lib/asan/asan_win.cc =================================================================== --- lib/asan/asan_win.cc +++ lib/asan/asan_win.cc @@ -60,6 +60,9 @@ fake_tsd = tsd; } +void PlatformTSDDtor(void *tsd) { + AsanThread::TSDDtor(tsd); +} // ---------------------- Various stuff ---------------- {{{1 void MaybeReexec() { // No need to re-exec on Windows. Index: lib/lsan/lit_tests/TestCases/cleanup_in_tsd_destructor.cc =================================================================== --- /dev/null +++ lib/lsan/lit_tests/TestCases/cleanup_in_tsd_destructor.cc @@ -0,0 +1,45 @@ +// Regression test for thread lifetime tracking. Thread data should be +// considered live during the thread's termination, at least until the +// user-installed TSD destructors have finished running (since they may contain +// additional cleanup tasks). LSan doesn't actually meet that goal 100%, but it +// makes its best effort. +// RUN: LSAN_BASE="report_objects=1:use_registers=0:use_stacks=0:use_globals=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:use_tls=1 %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:use_tls=0 not %t 2>&1 | FileCheck %s + +#include +#include +#include +#include + +#include "sanitizer/lsan_interface.h" + +pthread_key_t key; +__thread void *p; + +void key_destructor(void *arg) { + // Generally this may happen on a different thread. + __lsan_do_leak_check(); +} + +void *thread_func(void *arg) { + p = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + int res = pthread_setspecific(key, (void*)1); + assert(res == 0); + return 0; +} + +int main() { + int res = pthread_key_create(&key, &key_destructor); + assert(res == 0); + pthread_t thread_id; + res = pthread_create(&thread_id, 0, thread_func, 0); + assert(res == 0); + res = pthread_join(thread_id, 0); + assert(res == 0); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: leaked 1337 byte object at [[ADDR]]