blob: 5df132766cb44adb864034192c2601dbec119fb5 [file] [log] [blame]
//=-- lsan_test.cc --------------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer.
// Tests for leak checking functionality.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_platform.h"
#if SANITIZER_LINUX && defined(__x86_64__)
#include <dlfcn.h>
#include <pthread.h>
#include <string>
#include "gtest/gtest.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/tests/sanitizer_test_utils.h"
#include "lsan.h"
#include "lsan_common.h"
static char **global_argv;
namespace {
uptr kMagic = 0xBABABABABABABABA;
#define HIDE(p) ((void *)((uptr)(p) ^ kMagic))
#define PEEK(p) HIDE(p)
uptr kSmallAllocSize = 10;
// maxsize in primary allocator is always less than this.
uptr kLargeAllocSize = 1 << 25;
::testing::AssertionResult IsLeaked(void *hidden_p, uptr sources) {
InternalVector<void *> leaked(1);
__lsan::ReportLeaked(&leaked, sources);
for (uptr i = 0; i < leaked.size(); i++)
if (leaked[i] == PEEK(hidden_p))
return ::testing::AssertionSuccess() << PEEK(hidden_p) << " is leaked";
return ::testing::AssertionFailure() << PEEK(hidden_p) << " is not leaked";
}
#define EXPECT_LEAKED(p, sources) EXPECT_TRUE(IsLeaked(HIDE((p)), sources))
#define EXPECT_NOT_LEAKED(p, sources) EXPECT_FALSE(IsLeaked(HIDE((p)), sources))
// Tests for various sources of pointers.
// Stacks and registers are tricky: pointers sometimes get stuck in them and
// cause false negatives. This is not considered a bug in LSan, but we don't
// want it to interfere with our tests, so we disable those sources whenever we
// can. We don't disable globals though: if a stale pointer somehow makes it
// into global state and causes a false negative, we want to know.
uptr kAllButStackAndRegisters =
__lsan::kSourceAllAligned & ~__lsan::kSourceStacks &
~__lsan::kSourceRegisters;
void TestSource(void **p, uptr source) {
uptr baseline = kAllButStackAndRegisters | source;
*p = malloc(kSmallAllocSize);
EXPECT_NOT_LEAKED(*p, baseline);
EXPECT_LEAKED(*p, baseline & ~source);
// Check again, in case the first EXPECT_NOT_LEAKED was a false negative.
EXPECT_NOT_LEAKED(*p, baseline);
free((void *)*p);
*p = NULL;
}
void *data_var = (void*) 1;
TEST(LeakSanitizer, InitializedGlobals) {
TestSource(&data_var, __lsan::kSourceGlobals);
}
void *bss_var;
TEST(LeakSanitizer, UninitializedGlobals) {
TestSource(&bss_var, __lsan::kSourceGlobals);
}
TEST(LeakSanitizer, Stack) {
void *local_var;
TestSource(&local_var, __lsan::kSourceStacks);
}
THREADLOCAL void *tl_var;
TEST(LeakSanitizer, StaticTLS) {
TestSource(&tl_var, __lsan::kSourceTLS);
}
// Dynamically allocated TLS space.
TEST(LeakSanitizer, DynamicTLS) {
// Compute the path to our loadable DSO. We assume it's in the same
// directory.
char **argv = global_argv;
const std::string kLoadableSO = "liblsan_tls_loadable-x86_64.so";
std::string path = argv[0];
size_t last_slash = path.rfind('/');
ASSERT_NE(last_slash, std::string::npos);
path.erase(last_slash + 1);
path.append(kLoadableSO);
void *handle = dlopen(path.c_str(), RTLD_LAZY);
ASSERT_TRUE(handle != NULL) << "dlerror " << dlerror();
typedef void **(* store_t)(void *p);
store_t StoreToTLS = (store_t)dlsym(handle, "StoreToTLS");
ASSERT_EQ(0, dlerror());
void *p = malloc(kSmallAllocSize);
void **p_in_tls = StoreToTLS(p);
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
EXPECT_LEAKED(p, kAllButStackAndRegisters & ~__lsan::kSourceTLS);
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
free(p);
*p_in_tls = NULL;
}
// From glibc: this many keys are stored in the thread descriptor directly.
const uptr PTHREAD_KEY_2NDLEVEL_SIZE = 32;
// Thread-specific storage that is statically alocated in the thread descriptor.
TEST(LeakSanitizer, PthreadSpecificStatic) {
pthread_key_t key;
ASSERT_EQ(0, pthread_key_create(&key, NULL));
ASSERT_LT(key, PTHREAD_KEY_2NDLEVEL_SIZE);
void *p = malloc(kSmallAllocSize);
ASSERT_EQ(0, pthread_setspecific(key, p));
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
EXPECT_LEAKED(p, kAllButStackAndRegisters & ~__lsan::kSourceTLS);
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
ASSERT_EQ(0, pthread_setspecific(key, 0));
free(p);
}
// Dynamically allocated thread-specific storage.
TEST(LeakSanitizer, PthreadSpecificDynamic) {
static const uptr kDummyKeysCount = PTHREAD_KEY_2NDLEVEL_SIZE;
pthread_key_t dummy_keys[kDummyKeysCount];
for (uptr i = 0; i < kDummyKeysCount; i++)
ASSERT_EQ(0, pthread_key_create(&dummy_keys[i], NULL));
pthread_key_t key;
ASSERT_EQ(0, pthread_key_create(&key, NULL));
void *p = malloc(kSmallAllocSize);
ASSERT_EQ(0, pthread_setspecific(key, p));
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
EXPECT_LEAKED(p, kAllButStackAndRegisters & ~__lsan::kSourceTLS);
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters);
ASSERT_EQ(0, pthread_setspecific(key, NULL));
CHECK_EQ(0, pthread_key_delete(key));
for (uptr i = 0; i < kDummyKeysCount; i++)
CHECK_EQ(0, pthread_key_delete(dummy_keys[i]));
free(p);
}
// Put pointer far enough on the stack that LSan has space to run in without
// overwriting it.
NOINLINE uptr PutPointerOnStaleStack(void *p) {
void *locals[2048];
locals[0] = p;
break_optimization(&locals[0]);
// Hide the result, just to suppress the compiler warning.
return (uptr)HIDE(&locals[0]);
}
// Local variables that have gone out of scope should be ignored by LSan.
TEST(LeakSanitizer, StaleLocalsAreUnreachable) {
void *p = malloc(kSmallAllocSize);
void **stale_var = (void **)PEEK(PutPointerOnStaleStack(p));
p = HIDE(p);
EXPECT_LEAKED(PEEK(p), __lsan::kSourceAllAligned & ~__lsan::kSourceRegisters);
p = PEEK(p);
// Make sure LSan didn't overwrite the pointer at some point.
EXPECT_EQ(p, *stale_var);
free(p);
}
void *large_alloc;
// Make sure LargeMmapAllocator's chunks aren't reachable via some internal data
// structure.
TEST(LeakSanitizer, SimpleLargeAllocationLeaked) {
large_alloc = HIDE(malloc(kLargeAllocSize));
EXPECT_TRUE(IsLeaked((void *)large_alloc,
__lsan::kSourceAllAligned & ~__lsan::kSourceRegisters));
large_alloc = PEEK(large_alloc);
EXPECT_FALSE(IsLeaked((void *)large_alloc,
__lsan::kSourceAllAligned & ~__lsan::kSourceRegisters));
free((void *)large_alloc);
large_alloc = NULL;
}
// Multi-threaded tests.
struct ThreadArgument {
void sync_wait(uptr value) {
while (atomic_load(&sync, memory_order_seq_cst) != value)
pthread_yield();
}
void sync_store(uptr value) {
atomic_store(&sync, value, memory_order_seq_cst);
}
void *hidden_p;
atomic_uintptr_t sync;
};
void *StackThreadFunc(void *param) {
ThreadArgument *arg = reinterpret_cast<ThreadArgument *>(param);
void *p = malloc(kSmallAllocSize);
// Take p's address to ensure it's not optimized into a register.
void * volatile *pp = &p;
arg->hidden_p = HIDE(*pp);
arg->sync_store(1);
arg->sync_wait(2);
free(p);
return NULL;
}
void *RegistersThreadFunc(void *param) {
ThreadArgument *arg = reinterpret_cast<ThreadArgument *>(param);
// To store the pointer, choose a register which is unlikely to be reused by
// a function call.
#if defined(__i386__)
register void* p asm("esi");
#elif defined(__x86_64__)
register void* p asm("r15");
#else
register void* p;
#endif
p = malloc(kSmallAllocSize);
arg->hidden_p = HIDE(p);
arg->sync_store(1);
arg->sync_wait(2);
free(p);
return NULL;
}
void MultiThreadedTest(uptr source) {
uptr other_source;
void *(*func)(void *arg);
if (source == __lsan::kSourceStacks) {
func = StackThreadFunc;
other_source = __lsan::kSourceRegisters;
} else if (source == __lsan::kSourceRegisters) {
func = RegistersThreadFunc;
other_source = __lsan::kSourceStacks;
} else {
FAIL();
}
uptr baseline = __lsan::kSourceAllAligned & ~other_source;
ThreadArgument arg;
arg.sync_store(0);
pthread_t thread_id;
ASSERT_EQ(0, pthread_create(&thread_id, NULL, func, &arg));
arg.sync_wait(1);
EXPECT_NOT_LEAKED(PEEK(arg.hidden_p), baseline);
EXPECT_LEAKED(PEEK(arg.hidden_p), baseline & ~source);
EXPECT_NOT_LEAKED(PEEK(arg.hidden_p), baseline);
arg.sync_store(2);
ASSERT_EQ(0, pthread_join(thread_id, NULL));
}
TEST(LeakSanitizer, ThreadStacks) {
MultiThreadedTest(__lsan::kSourceStacks);
}
TEST(LeakSanitizer, ThreadRegisters) {
MultiThreadedTest(__lsan::kSourceRegisters);
}
// End of tests for pointer sources.
TEST(LeakSanitizer, UnalignedPointers) {
// Static so we can disable stack.
static uptr arr[2];
char *char_arr = (char *)arr;
void *p = malloc(kSmallAllocSize);
memcpy(char_arr + 1, &p, sizeof(uptr));
EXPECT_LEAKED(p, kAllButStackAndRegisters);
EXPECT_NOT_LEAKED(p, kAllButStackAndRegisters | __lsan::kSourceUnaligned);
EXPECT_LEAKED(p, kAllButStackAndRegisters);
free(p);
}
} // namespace
int main(int argc, char **argv) {
global_argv = argv;
__lsan::Init();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#endif // SANITIZER_LINUX && defined(__x86_64__)