[asan] Improve thread lifetime tracking on POSIX systems.
Call AsanThread::Destroy() from a late-running TSD destructor.
Previously we called it before any user-registered TSD destructors, which caused
false positives in LeakSanitizer.
git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@192585 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/asan/asan_internal.h b/lib/asan/asan_internal.h
index 7a4d744..70e55ea 100644
--- a/lib/asan/asan_internal.h
+++ b/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);
diff --git a/lib/asan/asan_posix.cc b/lib/asan/asan_posix.cc
index 5126a75..1926fcc 100644
--- a/lib/asan/asan_posix.cc
+++ b/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
diff --git a/lib/asan/asan_rtl.cc b/lib/asan/asan_rtl.cc
index f0b31f0..d93764c 100644
--- a/lib/asan/asan_rtl.cc
+++ b/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();
diff --git a/lib/asan/asan_thread.cc b/lib/asan/asan_thread.cc
index 5a81a5e..1949cd8 100644
--- a/lib/asan/asan_thread.cc
+++ b/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;
}
diff --git a/lib/asan/asan_thread.h b/lib/asan/asan_thread.h
index e77f16c..5d0a6c9 100644
--- a/lib/asan/asan_thread.h
+++ b/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;
diff --git a/lib/asan/asan_win.cc b/lib/asan/asan_win.cc
index 8f35e0b..9e66b34 100644
--- a/lib/asan/asan_win.cc
+++ b/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.
diff --git a/lib/lsan/lit_tests/TestCases/cleanup_in_tsd_destructor.cc b/lib/lsan/lit_tests/TestCases/cleanup_in_tsd_destructor.cc
new file mode 100644
index 0000000..ab36824
--- /dev/null
+++ b/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 <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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]]