diff options
Diffstat (limited to 'lib/lsan')
57 files changed, 1677 insertions, 449 deletions
diff --git a/lib/lsan/CMakeLists.txt b/lib/lsan/CMakeLists.txt index 378f08199218..3018a06622ee 100644 --- a/lib/lsan/CMakeLists.txt +++ b/lib/lsan/CMakeLists.txt @@ -1,24 +1,26 @@ include_directories(..) set(LSAN_CFLAGS - ${SANITIZER_COMMON_CFLAGS}) + ${SANITIZER_COMMON_CFLAGS} + -fno-rtti) set(LSAN_COMMON_SOURCES lsan_common.cc lsan_common_linux.cc) set(LSAN_SOURCES - lsan_interceptors.cc + lsan.cc lsan_allocator.cc - lsan_thread.cc - lsan.cc) + lsan_interceptors.cc + lsan_preinit.cc + lsan_thread.cc) set(LSAN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # The common files need to build on every arch supported by ASan. # (Even if they build into dummy object files.) filter_available_targets(LSAN_COMMON_SUPPORTED_ARCH - x86_64 i386 powerpc64 powerpc) + x86_64 i386 powerpc64) # Architectures supported by the standalone LSan. filter_available_targets(LSAN_SUPPORTED_ARCH @@ -26,7 +28,14 @@ filter_available_targets(LSAN_SUPPORTED_ARCH set(LSAN_RUNTIME_LIBRARIES) -if (NOT APPLE AND NOT ANDROID) +if(APPLE) + foreach(os ${SANITIZER_COMMON_SUPPORTED_DARWIN_OS}) + add_compiler_rt_darwin_object_library(RTLSanCommon ${os} + ARCH ${LSAN_COMMON_SUPPORTED_ARCH} + SOURCES ${LSAN_COMMON_SOURCES} + CFLAGS ${LSAN_CFLAGS}) + endforeach() +elseif(NOT ANDROID) foreach(arch ${LSAN_COMMON_SUPPORTED_ARCH}) add_compiler_rt_object_library(RTLSanCommon ${arch} SOURCES ${LSAN_COMMON_SOURCES} diff --git a/lib/lsan/Makefile.mk b/lib/lsan/Makefile.mk index aae5c32fd32f..2a6b41c98e2c 100644 --- a/lib/lsan/Makefile.mk +++ b/lib/lsan/Makefile.mk @@ -7,17 +7,22 @@ # #===------------------------------------------------------------------------===# -ModuleName := lsan_common +ModuleName := lsan SubDirs := -Sources := $(foreach file,$(wildcard $(Dir)/lsan_common*.cc),$(notdir $(file))) +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) ObjNames := $(Sources:%.cc=%.o) Implementation := Generic # FIXME: use automatic dependencies? Dependencies := $(wildcard $(Dir)/*.h) +Dependencies += $(wildcard $(Dir)/../interception/*.h) Dependencies += $(wildcard $(Dir)/../sanitizer_common/*.h) -# Define a convenience variable for all the asan functions. -LsanCommonFunctions := $(Sources:%.cc=%) +# Define a convenience variable for all the lsan functions. +LsanFunctions := $(Sources:%.cc=%) + +# lsan functions used in another sanitizers. +LsanCommonSources := $(foreach file,$(wildcard $(Dir)/lsan_common*.cc),$(notdir $(file))) +LsanCommonFunctions := $(LsanCommonSources:%.cc=%) diff --git a/lib/lsan/lit_tests/AsanConfig/lit.cfg b/lib/lsan/lit_tests/AsanConfig/lit.cfg new file mode 100644 index 000000000000..ae9198173ffc --- /dev/null +++ b/lib/lsan/lit_tests/AsanConfig/lit.cfg @@ -0,0 +1,32 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit_config.fatal( + "No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +lsan_lit_src_root = get_required_attr(config, "lsan_lit_src_root") +lsan_lit_cfg = os.path.join(lsan_lit_src_root, "lit.common.cfg") +if not os.path.exists(lsan_lit_cfg): + lit_config.fatal("Can't find common LSan lit config at: %r" % lsan_lit_cfg) +lit_config.load_config(config, lsan_lit_cfg) + +config.name = 'LeakSanitizer-AddressSanitizer' + +clang_lsan_cxxflags = config.clang_cxxflags + " -fsanitize=address " + +config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + + clang_lsan_cxxflags + " ")) ) + +clang_lsan_cflags = config.clang_cflags + " -fsanitize=address " + +config.substitutions.append( ("%clang_lsan ", (" " + config.clang + " " + + clang_lsan_cflags + " ")) ) + +config.environment['ASAN_OPTIONS'] = 'detect_leaks=1' diff --git a/lib/lsan/lit_tests/AsanConfig/lit.site.cfg.in b/lib/lsan/lit_tests/AsanConfig/lit.site.cfg.in new file mode 100644 index 000000000000..9cf6572c54b7 --- /dev/null +++ b/lib/lsan/lit_tests/AsanConfig/lit.site.cfg.in @@ -0,0 +1,8 @@ +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/lib/lit.common.configured") + +# Tool-specific config options. +config.lsan_lit_src_root = "@LSAN_LIT_SOURCE_DIR@" + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@LSAN_LIT_SOURCE_DIR@/AsanConfig/lit.cfg") diff --git a/lib/lsan/lit_tests/CMakeLists.txt b/lib/lsan/lit_tests/CMakeLists.txt index e1be508202b8..526d71bf3418 100644 --- a/lib/lsan/lit_tests/CMakeLists.txt +++ b/lib/lsan/lit_tests/CMakeLists.txt @@ -1,9 +1,16 @@ set(LSAN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) set(LSAN_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/..) +set(LSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/LsanConfig/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/LsanConfig/lit.site.cfg + ) + configure_lit_site_cfg( - ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in - ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ${CMAKE_CURRENT_SOURCE_DIR}/AsanConfig/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/AsanConfig/lit.site.cfg ) configure_lit_site_cfg( @@ -11,18 +18,20 @@ configure_lit_site_cfg( ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg ) -if(COMPILER_RT_CAN_EXECUTE_TESTS) +if(COMPILER_RT_CAN_EXECUTE_TESTS AND NOT APPLE AND NOT ANDROID) set(LSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} ${LSAN_RUNTIME_LIBRARIES}) - set(LSAN_TEST_PARAMS - lsan_site_config=${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg) + foreach(arch ${LSAN_SUPPORTED_ARCH}) + list(APPEND LSAN_TEST_DEPS clang_rt.asan-${arch}) + endforeach() if(LLVM_INCLUDE_TESTS) - list(APPEND LSAN_TEST_DEPS LsanTests) + list(APPEND LSAN_TEST_DEPS LsanUnitTests) endif() add_lit_testsuite(check-lsan "Running the LeakSanitizer tests" - ${CMAKE_CURRENT_BINARY_DIR} - PARAMS ${LSAN_TEST_PARAMS} + ${CMAKE_CURRENT_BINARY_DIR}/LsanConfig + ${CMAKE_CURRENT_BINARY_DIR}/AsanConfig + ${CMAKE_CURRENT_BINARY_DIR}/Unit DEPENDS ${LSAN_TEST_DEPS}) set_target_properties(check-lsan PROPERTIES FOLDER "LSan tests") endif() diff --git a/lib/lsan/lit_tests/LsanConfig/lit.cfg b/lib/lsan/lit_tests/LsanConfig/lit.cfg new file mode 100644 index 000000000000..84faf9167a78 --- /dev/null +++ b/lib/lsan/lit_tests/LsanConfig/lit.cfg @@ -0,0 +1,30 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit_config.fatal( + "No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +lsan_lit_src_root = get_required_attr(config, "lsan_lit_src_root") +lsan_lit_cfg = os.path.join(lsan_lit_src_root, "lit.common.cfg") +if not os.path.exists(lsan_lit_cfg): + lit_config.fatal("Can't find common LSan lit config at: %r" % lsan_lit_cfg) +lit_config.load_config(config, lsan_lit_cfg) + +config.name = 'LeakSanitizer-Standalone' + +clang_lsan_cxxflags = config.clang_cxxflags + " -fsanitize=leak " + +config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + + clang_lsan_cxxflags + " ")) ) + +clang_lsan_cflags = config.clang_cflags + " -fsanitize=leak " + +config.substitutions.append( ("%clang_lsan ", (" " + config.clang + " " + + clang_lsan_cflags + " ")) ) diff --git a/lib/lsan/lit_tests/LsanConfig/lit.site.cfg.in b/lib/lsan/lit_tests/LsanConfig/lit.site.cfg.in new file mode 100644 index 000000000000..2a6d724c0436 --- /dev/null +++ b/lib/lsan/lit_tests/LsanConfig/lit.site.cfg.in @@ -0,0 +1,8 @@ +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/lib/lit.common.configured") + +# Tool-specific config options. +config.lsan_lit_src_root = "@LSAN_LIT_SOURCE_DIR@" + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@LSAN_LIT_SOURCE_DIR@/LsanConfig/lit.cfg") diff --git a/lib/lsan/lit_tests/TestCases/SharedLibs/huge_tls_lib_so.cc b/lib/lsan/lit_tests/TestCases/SharedLibs/huge_tls_lib_so.cc new file mode 100644 index 000000000000..475a66ec12a4 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/SharedLibs/huge_tls_lib_so.cc @@ -0,0 +1,12 @@ +// A loadable module with a large thread local section, which would require +// allocation of a new TLS storage chunk when loaded with dlopen(). We use it +// to test the reachability of such chunks in LSan tests. + +// This must be large enough that it doesn't fit into preallocated static TLS +// space (see STATIC_TLS_SURPLUS in glibc). +__thread void *huge_thread_local_array[(1 << 20) / sizeof(void *)]; // NOLINT + +extern "C" void **StoreToTLS(void *p) { + huge_thread_local_array[0] = p; + return &huge_thread_local_array[0]; +} diff --git a/lib/lsan/lit_tests/SharedLibs/lit.local.cfg b/lib/lsan/lit_tests/TestCases/SharedLibs/lit.local.cfg index b3677c17a0f2..b3677c17a0f2 100644 --- a/lib/lsan/lit_tests/SharedLibs/lit.local.cfg +++ b/lib/lsan/lit_tests/TestCases/SharedLibs/lit.local.cfg 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 000000000000..ab368245317c --- /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]] diff --git a/lib/lsan/lit_tests/TestCases/disabler.cc b/lib/lsan/lit_tests/TestCases/disabler.cc new file mode 100644 index 000000000000..db0cd8fabe4d --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/disabler.cc @@ -0,0 +1,23 @@ +// Test for ScopedDisabler. +// RUN: LSAN_BASE="report_objects=1:use_registers=0:use_stacks=0:use_globals=0:use_tls=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +int main() { + void **p; + { + __lsan::ScopedDisabler d; + p = new void *; + } + *reinterpret_cast<void **>(p) = malloc(666); + void *q = malloc(1337); + // Break optimization. + fprintf(stderr, "Test alloc: %p.\n", q); + return 0; +} +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/disabler_in_tsd_destructor.cc b/lib/lsan/lit_tests/TestCases/disabler_in_tsd_destructor.cc new file mode 100644 index 000000000000..94e4fc390b3b --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/disabler_in_tsd_destructor.cc @@ -0,0 +1,38 @@ +// Regression test. Disabler should not depend on TSD validity. +// RUN: LSAN_BASE="report_objects=1:use_registers=0:use_stacks=0:use_globals=0:use_tls=1" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE %t + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +pthread_key_t key; + +void key_destructor(void *arg) { + __lsan::ScopedDisabler d; + void *p = malloc(1337); + // Break optimization. + fprintf(stderr, "Test alloc: %p.\n", p); + pthread_setspecific(key, 0); +} + +void *thread_func(void *arg) { + 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; +} diff --git a/lib/lsan/lit_tests/TestCases/do_leak_check_override.cc b/lib/lsan/lit_tests/TestCases/do_leak_check_override.cc new file mode 100644 index 000000000000..be0ed0a6d48f --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/do_leak_check_override.cc @@ -0,0 +1,36 @@ +// Test for __lsan_do_leak_check(). We test it by making the leak check run +// before global destructors, which also tests compatibility with HeapChecker's +// "normal" mode (LSan runs in "strict" mode by default). +// RUN: LSAN_BASE="use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck --check-prefix=CHECK-strict %s +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t foo 2>&1 | FileCheck --check-prefix=CHECK-normal %s + +#include <stdio.h> +#include <stdlib.h> +#include <sanitizer/lsan_interface.h> + +struct LeakyGlobal { + LeakyGlobal() { + p = malloc(1337); + } + ~LeakyGlobal() { + p = 0; + } + void *p; +}; + +LeakyGlobal leaky_global; + +int main(int argc, char *argv[]) { + // Register leak check to run before global destructors. + if (argc > 1) + atexit(&__lsan_do_leak_check); + void *p = malloc(666); + printf("Test alloc: %p\n", p); + printf("Test alloc in leaky global: %p\n", leaky_global.p); + return 0; +} + +// CHECK-strict: SUMMARY: {{(Leak|Address)}}Sanitizer: 2003 byte(s) leaked in 2 allocation(s) +// CHECK-normal: SUMMARY: {{(Leak|Address)}}Sanitizer: 666 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/fork.cc b/lib/lsan/lit_tests/TestCases/fork.cc new file mode 100644 index 000000000000..69258d9a0c72 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/fork.cc @@ -0,0 +1,24 @@ +// Test that thread local data is handled correctly after forking without exec(). +// RUN: %clangxx_lsan %s -o %t +// RUN: %t 2>&1 + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> + +__thread void *thread_local_var; + +int main() { + int status = 0; + thread_local_var = malloc(1337); + pid_t pid = fork(); + assert(pid >= 0); + if (pid > 0) { + waitpid(pid, &status, 0); + assert(WIFEXITED(status)); + return WEXITSTATUS(status); + } + return 0; +} diff --git a/lib/lsan/lit_tests/TestCases/fork_threaded.cc b/lib/lsan/lit_tests/TestCases/fork_threaded.cc new file mode 100644 index 000000000000..24a586109e28 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/fork_threaded.cc @@ -0,0 +1,43 @@ +// Test that thread local data is handled correctly after forking without +// exec(). In this test leak checking is initiated from a non-main thread. +// RUN: %clangxx_lsan %s -o %t +// RUN: %t 2>&1 + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> + +__thread void *thread_local_var; + +void *exit_thread_func(void *arg) { + exit(0); +} + +void ExitFromThread() { + pthread_t tid; + int res; + res = pthread_create(&tid, 0, exit_thread_func, 0); + assert(res == 0); + pthread_join(tid, 0); +} + +int main() { + int status = 0; + thread_local_var = malloc(1337); + pid_t pid = fork(); + assert(pid >= 0); + if (pid > 0) { + waitpid(pid, &status, 0); + assert(WIFEXITED(status)); + return WEXITSTATUS(status); + } else { + // Spawn a thread and call exit() from there, to check that we track main + // thread's pid correctly even if leak checking is initiated from another + // thread. + ExitFromThread(); + } + return 0; +} diff --git a/lib/lsan/lit_tests/TestCases/high_allocator_contention.cc b/lib/lsan/lit_tests/TestCases/high_allocator_contention.cc new file mode 100644 index 000000000000..1cecb2a550a1 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/high_allocator_contention.cc @@ -0,0 +1,48 @@ +// A benchmark that executes malloc/free pairs in parallel. +// Usage: ./a.out number_of_threads total_number_of_allocations +// RUN: %clangxx_lsan %s -o %t +// RUN: %t 5 1000000 2>&1 +#include <assert.h> +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> + +int num_threads; +int total_num_alloc; +const int kMaxNumThreads = 5000; +pthread_t tid[kMaxNumThreads]; + +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +bool go = false; + +void *thread_fun(void *arg) { + pthread_mutex_lock(&mutex); + while (!go) pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + for (int i = 0; i < total_num_alloc / num_threads; i++) { + void *p = malloc(10); + __asm__ __volatile__("" : : "r"(p) : "memory"); + free((void *)p); + } + return 0; +} + +int main(int argc, char** argv) { + assert(argc == 3); + num_threads = atoi(argv[1]); + assert(num_threads > 0); + assert(num_threads <= kMaxNumThreads); + total_num_alloc = atoi(argv[2]); + assert(total_num_alloc > 0); + printf("%d threads, %d allocations in each\n", num_threads, + total_num_alloc / num_threads); + for (int i = 0; i < num_threads; i++) + pthread_create(&tid[i], 0, thread_fun, 0); + pthread_mutex_lock(&mutex); + go = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); + for (int i = 0; i < num_threads; i++) pthread_join(tid[i], 0); + return 0; +} diff --git a/lib/lsan/lit_tests/TestCases/ignore_object.cc b/lib/lsan/lit_tests/TestCases/ignore_object.cc new file mode 100644 index 000000000000..cbc743b75497 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/ignore_object.cc @@ -0,0 +1,30 @@ +// Test for __lsan_ignore_object(). +// RUN: LSAN_BASE="report_objects=1:use_registers=0:use_stacks=0:use_globals=0:use_tls=0:verbosity=3" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +int main() { + { + // The first malloc call can cause an allocation in libdl. Ignore it here so + // it doesn't show up in our output. + __lsan::ScopedDisabler d; + malloc(1); + } + // Explicitly ignored object. + void **p = new void *; + // Transitively ignored object. + *p = malloc(666); + // Non-ignored object. + volatile void *q = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + __lsan_ignore_object(p); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: ignoring heap object at [[ADDR]] +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc b/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc new file mode 100644 index 000000000000..2a6c72551772 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc @@ -0,0 +1,22 @@ +// Test for incorrect use of __lsan_ignore_object(). +// RUN: LSAN_BASE="verbosity=2" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +int main() { + void *p = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + __lsan_ignore_object(p); + __lsan_ignore_object(p); + free(p); + __lsan_ignore_object(p); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: heap object at [[ADDR]] is already being ignored +// CHECK: no heap object found at [[ADDR]] diff --git a/lib/lsan/lit_tests/TestCases/large_allocation_leak.cc b/lib/lsan/lit_tests/TestCases/large_allocation_leak.cc new file mode 100644 index 000000000000..57d056597ee2 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/large_allocation_leak.cc @@ -0,0 +1,18 @@ +// Test that LargeMmapAllocator's chunks aren't reachable via some internal data structure. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +int main() { + // maxsize in primary allocator is always less than this (1 << 25). + void *large_alloc = malloc(33554432); + fprintf(stderr, "Test alloc: %p.\n", large_alloc); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 33554432 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/leak_check_at_exit.cc b/lib/lsan/lit_tests/TestCases/leak_check_at_exit.cc new file mode 100644 index 000000000000..38c1063b6ebb --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/leak_check_at_exit.cc @@ -0,0 +1,19 @@ +// Test for the leak_check_at_exit flag. +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS="verbosity=1" %t foo 2>&1 | FileCheck %s --check-prefix=CHECK-do +// RUN: LSAN_OPTIONS="verbosity=1" %t 2>&1 | FileCheck %s --check-prefix=CHECK-do +// RUN: LSAN_OPTIONS="verbosity=1:leak_check_at_exit=0" ASAN_OPTIONS="$ASAN_OPTIONS:leak_check_at_exit=0" %t foo 2>&1 | FileCheck %s --check-prefix=CHECK-do +// RUN: LSAN_OPTIONS="verbosity=1:leak_check_at_exit=0" ASAN_OPTIONS="$ASAN_OPTIONS:leak_check_at_exit=0" %t 2>&1 | FileCheck %s --check-prefix=CHECK-dont + +#include <stdio.h> +#include <sanitizer/lsan_interface.h> + +int main(int argc, char *argv[]) { + printf("printf to break optimization\n"); + if (argc > 1) + __lsan_do_leak_check(); + return 0; +} + +// CHECK-do: SUMMARY: {{(Leak|Address)}}Sanitizer: +// CHECK-dont-NOT: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/link_turned_off.cc b/lib/lsan/lit_tests/TestCases/link_turned_off.cc new file mode 100644 index 000000000000..93628a1d15ee --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/link_turned_off.cc @@ -0,0 +1,24 @@ +// Test for disabling LSan at link-time. +// RUN: LSAN_BASE="use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t foo 2>&1 | FileCheck %s + +#include <sanitizer/lsan_interface.h> + +int argc_copy; + +extern "C" { +int __lsan_is_turned_off() { + return (argc_copy == 1); +} +} + +int main(int argc, char *argv[]) { + volatile int *x = new int; + *x = 42; + argc_copy = argc; + return 0; +} + +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 4 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/pointer_to_self.cc b/lib/lsan/lit_tests/TestCases/pointer_to_self.cc new file mode 100644 index 000000000000..0d2818d2fa1d --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/pointer_to_self.cc @@ -0,0 +1,18 @@ +// Regression test: pointers to self should not confuse LSan into thinking the +// object is indirectly leaked. Only external pointers count. +// RUN: LSAN_BASE="report_objects=1:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_stacks=0" not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +int main() { + void *p = malloc(1337); + *reinterpret_cast<void **>(p) = p; + fprintf(stderr, "Test alloc: %p.\n", p); +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/sanity_check_pure_c.c b/lib/lsan/lit_tests/TestCases/sanity_check_pure_c.c new file mode 100644 index 000000000000..085412b47d55 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/sanity_check_pure_c.c @@ -0,0 +1,10 @@ +// Check that we can build C code. +// RUN: %clang_lsan %s -o %t +#ifdef __cplusplus +#error "This test must be built in C mode" +#endif + +int main() { + // FIXME: ideally this should somehow check that we don't have libstdc++ + return 0; +} diff --git a/lib/lsan/lit_tests/TestCases/stale_stack_leak.cc b/lib/lsan/lit_tests/TestCases/stale_stack_leak.cc new file mode 100644 index 000000000000..fabfb4ff21a9 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/stale_stack_leak.cc @@ -0,0 +1,42 @@ +// Test that out-of-scope local variables are ignored by LSan. +// RUN: LSAN_BASE="report_objects=1:use_registers=0:use_stacks=1" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE":exitcode=0" %t 2>&1 | FileCheck --check-prefix=CHECK-sanity %s + +#include <stdio.h> +#include <stdlib.h> + +void **pp; + +// Put pointer far enough on the stack that LSan has space to run in without +// overwriting it. +// Hopefully the argument p will be passed on a register, saving us from false +// negatives. +__attribute__((noinline)) +void *PutPointerOnStaleStack(void *p) { + void *locals[2048]; + locals[0] = p; + pp = &locals[0]; + fprintf(stderr, "Test alloc: %p.\n", locals[0]); + return 0; +} + +int main() { + PutPointerOnStaleStack(malloc(1337)); + return 0; +} + +// This must run after LSan, to ensure LSan didn't overwrite the pointer before +// it had a chance to see it. If LSan is invoked with atexit(), this works. +// Otherwise, we need a different method. +__attribute__((destructor)) +void ConfirmPointerHasSurvived() { + fprintf(stderr, "Value after LSan: %p.\n", *pp); +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK-sanity: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: +// CHECK-sanity: Value after LSan: [[ADDR]]. diff --git a/lib/lsan/lit_tests/TestCases/suppressions_default.cc b/lib/lsan/lit_tests/TestCases/suppressions_default.cc new file mode 100644 index 000000000000..9a165f8770f9 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/suppressions_default.cc @@ -0,0 +1,29 @@ +// Test for ScopedDisabler. +// RUN: LSAN_BASE="use_registers=0:use_stacks=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +extern "C" +const char *__lsan_default_suppressions() { + return "leak:*LSanTestLeakingFunc*"; +} + +void LSanTestLeakingFunc() { + void *p = malloc(666); + fprintf(stderr, "Test alloc: %p.\n", p); +} + +int main() { + LSanTestLeakingFunc(); + void *q = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", q); + return 0; +} +// CHECK: Suppressions used: +// CHECK: 1 666 *LSanTestLeakingFunc* +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/suppressions_file.cc b/lib/lsan/lit_tests/TestCases/suppressions_file.cc new file mode 100644 index 000000000000..9a165f8770f9 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/suppressions_file.cc @@ -0,0 +1,29 @@ +// Test for ScopedDisabler. +// RUN: LSAN_BASE="use_registers=0:use_stacks=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE not %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +#include "sanitizer/lsan_interface.h" + +extern "C" +const char *__lsan_default_suppressions() { + return "leak:*LSanTestLeakingFunc*"; +} + +void LSanTestLeakingFunc() { + void *p = malloc(666); + fprintf(stderr, "Test alloc: %p.\n", p); +} + +int main() { + LSanTestLeakingFunc(); + void *q = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", q); + return 0; +} +// CHECK: Suppressions used: +// CHECK: 1 666 *LSanTestLeakingFunc* +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp b/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp new file mode 100644 index 000000000000..8d8e560cba4c --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp @@ -0,0 +1 @@ +leak:*LSanTestLeakingFunc* diff --git a/lib/lsan/lit_tests/TestCases/swapcontext.cc b/lib/lsan/lit_tests/TestCases/swapcontext.cc new file mode 100644 index 000000000000..a06685ca2f03 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/swapcontext.cc @@ -0,0 +1,42 @@ +// We can't unwind stack if we're running coroutines on heap-allocated +// memory. Make sure we don't report these leaks. + +// RUN: %clangxx_lsan %s -o %t +// RUN: %t 2>&1 +// RUN: not %t foo 2>&1 | FileCheck %s + +#include <stdio.h> +#include <ucontext.h> +#include <unistd.h> + +const int kStackSize = 1 << 20; + +void Child() { + int child_stack; + printf("Child: %p\n", &child_stack); + int *leaked = new int[666]; +} + +int main(int argc, char *argv[]) { + char stack_memory[kStackSize + 1]; + char *heap_memory = new char[kStackSize + 1]; + char *child_stack = (argc > 1) ? stack_memory : heap_memory; + + printf("Child stack: %p\n", child_stack); + ucontext_t orig_context; + ucontext_t child_context; + getcontext(&child_context); + child_context.uc_stack.ss_sp = child_stack; + child_context.uc_stack.ss_size = kStackSize / 2; + child_context.uc_link = &orig_context; + makecontext(&child_context, Child, 0); + if (swapcontext(&orig_context, &child_context) < 0) { + perror("swapcontext"); + return 1; + } + + delete[] heap_memory; + return 0; +} + +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 2664 byte(s) leaked in 1 allocation(s) diff --git a/lib/lsan/lit_tests/TestCases/use_after_return.cc b/lib/lsan/lit_tests/TestCases/use_after_return.cc new file mode 100644 index 000000000000..93b0ea6068ef --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_after_return.cc @@ -0,0 +1,23 @@ +// Test that fake stack (introduced by ASan's use-after-return mode) is included +// in the root set. +// RUN: LSAN_BASE="report_objects=1:use_registers=0" +// RUN: %clangxx_lsan %s -O2 -o %t +// RUN: ASAN_OPTIONS=$ASAN_OPTIONS:detect_stack_use_after_return=1 LSAN_OPTIONS=$LSAN_BASE:"use_stacks=0" not %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS=$ASAN_OPTIONS:detect_stack_use_after_return=1 LSAN_OPTIONS=$LSAN_BASE:"use_stacks=1" %t 2>&1 +// RUN: ASAN_OPTIONS=$ASAN_OPTIONS:detect_stack_use_after_return=1 LSAN_OPTIONS="" %t 2>&1 + +#include <stdio.h> +#include <stdlib.h> + +int main() { + void *stack_var = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", stack_var); + // Take pointer to variable, to ensure it's not optimized into a register. + fprintf(stderr, "Stack var at: %p.\n", &stack_var); + // Do not return from main to prevent the pointer from going out of scope. + exit(0); +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/use_globals_initialized.cc b/lib/lsan/lit_tests/TestCases/use_globals_initialized.cc index 53c22c8ac057..5a7c48bdf49a 100644 --- a/lib/lsan/lit_tests/use_globals_initialized.cc +++ b/lib/lsan/lit_tests/TestCases/use_globals_initialized.cc @@ -1,7 +1,7 @@ -// Test that initialized globals are included in the root set. -// RUN: LSAN_BASE="report_blocks=1:use_stacks=0:use_registers=0" +// Test that initialized globals are included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" // RUN: %clangxx_lsan %s -o %t -// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_globals=0" %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_globals=0" not %t 2>&1 | FileCheck %s // RUN: LSAN_OPTIONS=$LSAN_BASE:"use_globals=1" %t 2>&1 // RUN: LSAN_OPTIONS="" %t 2>&1 @@ -16,6 +16,6 @@ int main() { return 0; } // CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] // CHECK: LeakSanitizer: detected memory leaks -// CHECK: Directly leaked 1337 byte block at [[ADDR]] -// CHECK: SUMMARY: LeakSanitizer: +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_globals_uninitialized.cc b/lib/lsan/lit_tests/TestCases/use_globals_uninitialized.cc new file mode 100644 index 000000000000..e1d045e3f79f --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_globals_uninitialized.cc @@ -0,0 +1,21 @@ +// Test that uninitialized globals are included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_globals=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_globals=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <stdio.h> +#include <stdlib.h> + +void *bss_var; + +int main() { + bss_var = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", bss_var); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_registers.cc b/lib/lsan/lit_tests/TestCases/use_registers.cc new file mode 100644 index 000000000000..a7d8a69d7173 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_registers.cc @@ -0,0 +1,51 @@ +// Test that registers of running threads are included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0" +// RUN: %clangxx_lsan -pthread %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_registers=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_registers=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +extern "C" +void *registers_thread_func(void *arg) { + int *sync = reinterpret_cast<int *>(arg); + void *p = malloc(1337); + // To store the pointer, choose a register which is unlikely to be reused by + // a function call. +#if defined(__i386__) + asm ( "mov %0, %%esi" + : + : "r" (p) + ); +#elif defined(__x86_64__) + asm ( "mov %0, %%r15" + : + : "r" (p) + ); +#else +#error "Test is not supported on this architecture." +#endif + fprintf(stderr, "Test alloc: %p.\n", p); + fflush(stderr); + __sync_fetch_and_xor(sync, 1); + while (true) + pthread_yield(); +} + +int main() { + int sync = 0; + pthread_t thread_id; + int res = pthread_create(&thread_id, 0, registers_thread_func, &sync); + assert(res == 0); + while (!__sync_fetch_and_xor(&sync, 0)) + pthread_yield(); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_stacks.cc b/lib/lsan/lit_tests/TestCases/use_stacks.cc new file mode 100644 index 000000000000..4287a96b2285 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_stacks.cc @@ -0,0 +1,20 @@ +// Test that stack of main thread is included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_stacks=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_stacks=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <stdio.h> +#include <stdlib.h> + +int main() { + void *stack_var = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", stack_var); + // Do not return from main to prevent the pointer from going out of scope. + exit(0); +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_stacks_threaded.cc b/lib/lsan/lit_tests/TestCases/use_stacks_threaded.cc new file mode 100644 index 000000000000..c7dfaf8abad6 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_stacks_threaded.cc @@ -0,0 +1,36 @@ +// Test that stacks of non-main threads are included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_registers=0" +// RUN: %clangxx_lsan -pthread %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_stacks=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_stacks=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +extern "C" +void *stacks_thread_func(void *arg) { + int *sync = reinterpret_cast<int *>(arg); + void *p = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + fflush(stderr); + __sync_fetch_and_xor(sync, 1); + while (true) + pthread_yield(); +} + +int main() { + int sync = 0; + pthread_t thread_id; + int res = pthread_create(&thread_id, 0, stacks_thread_func, &sync); + assert(res == 0); + while (!__sync_fetch_and_xor(&sync, 0)) + pthread_yield(); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_tls_dynamic.cc b/lib/lsan/lit_tests/TestCases/use_tls_dynamic.cc new file mode 100644 index 000000000000..2570b63f0c5e --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_tls_dynamic.cc @@ -0,0 +1,33 @@ +// Test that dynamically allocated TLS space is included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx %p/SharedLibs/huge_tls_lib_so.cc -fPIC -shared -o %t-so.so +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <assert.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string> + +int main(int argc, char *argv[]) { + std::string path = std::string(argv[0]) + "-so.so"; + + void *handle = dlopen(path.c_str(), RTLD_LAZY); + assert(handle != 0); + typedef void **(* store_t)(void *p); + store_t StoreToTLS = (store_t)dlsym(handle, "StoreToTLS"); + assert(dlerror() == 0); + + void *p = malloc(1337); + void **p_in_tls = StoreToTLS(p); + assert(*p_in_tls == p); + fprintf(stderr, "Test alloc: %p.\n", p); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_dynamic.cc b/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_dynamic.cc new file mode 100644 index 000000000000..3dea41edddd4 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_dynamic.cc @@ -0,0 +1,37 @@ +// Test that dynamically allocated thread-specific storage is included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +// From glibc: this many keys are stored in the thread descriptor directly. +const unsigned PTHREAD_KEY_2NDLEVEL_SIZE = 32; + +int main() { + static const unsigned kDummyKeysCount = PTHREAD_KEY_2NDLEVEL_SIZE; + int res; + pthread_key_t dummy_keys[kDummyKeysCount]; + for (unsigned i = 0; i < kDummyKeysCount; i++) { + res = pthread_key_create(&dummy_keys[i], NULL); + assert(res == 0); + } + pthread_key_t key; + res = pthread_key_create(&key, NULL); + assert(key >= PTHREAD_KEY_2NDLEVEL_SIZE); + assert(res == 0); + void *p = malloc(1337); + res = pthread_setspecific(key, p); + assert(res == 0); + fprintf(stderr, "Test alloc: %p.\n", p); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_static.cc b/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_static.cc new file mode 100644 index 000000000000..b75f15153863 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_static.cc @@ -0,0 +1,31 @@ +// Test that statically allocated thread-specific storage is included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +// From glibc: this many keys are stored in the thread descriptor directly. +const unsigned PTHREAD_KEY_2NDLEVEL_SIZE = 32; + +int main() { + pthread_key_t key; + int res; + res = pthread_key_create(&key, NULL); + assert(res == 0); + assert(key < PTHREAD_KEY_2NDLEVEL_SIZE); + void *p = malloc(1337); + res = pthread_setspecific(key, p); + assert(res == 0); + fprintf(stderr, "Test alloc: %p.\n", p); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_tls_static.cc b/lib/lsan/lit_tests/TestCases/use_tls_static.cc new file mode 100644 index 000000000000..9ccb2b2b7fb1 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_tls_static.cc @@ -0,0 +1,21 @@ +// Test that statically allocated TLS space is included in the root set. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_tls=1" %t 2>&1 +// RUN: LSAN_OPTIONS="" %t 2>&1 + +#include <stdio.h> +#include <stdlib.h> + +__thread void *tls_var; + +int main() { + tls_var = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", tls_var); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/TestCases/use_unaligned.cc b/lib/lsan/lit_tests/TestCases/use_unaligned.cc new file mode 100644 index 000000000000..bc75f11b99f0 --- /dev/null +++ b/lib/lsan/lit_tests/TestCases/use_unaligned.cc @@ -0,0 +1,23 @@ +// Test that unaligned pointers are detected correctly. +// RUN: LSAN_BASE="report_objects=1:use_stacks=0:use_registers=0" +// RUN: %clangxx_lsan %s -o %t +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_unaligned=0" not %t 2>&1 | FileCheck %s +// RUN: LSAN_OPTIONS=$LSAN_BASE:"use_unaligned=1" %t 2>&1 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void *arr[2]; + +int main() { + void *p = malloc(1337); + fprintf(stderr, "Test alloc: %p.\n", p); + char *char_arr = (char *)arr; + memcpy(char_arr + 1, &p, sizeof(p)); + return 0; +} +// CHECK: Test alloc: [[ADDR:.*]]. +// CHECK: Directly leaked 1337 byte object at [[ADDR]] +// CHECK: LeakSanitizer: detected memory leaks +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: diff --git a/lib/lsan/lit_tests/Unit/lit.cfg b/lib/lsan/lit_tests/Unit/lit.cfg deleted file mode 100644 index bcd1de4477f1..000000000000 --- a/lib/lsan/lit_tests/Unit/lit.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# -*- Python -*- - -import os - -def get_required_attr(config, attr_name): - attr_value = getattr(config, attr_name, None) - if not attr_value: - lit.fatal("No attribute %r in test configuration! You may need to run " - "tests from your build directory or add this attribute " - "to lit.site.cfg " % attr_name) - return attr_value - -# Setup attributes common for all compiler-rt projects. -compiler_rt_src_root = get_required_attr(config, 'compiler_rt_src_root') -compiler_rt_lit_unit_cfg = os.path.join(compiler_rt_src_root, "lib", - "lit.common.unit.cfg") -lit.load_config(config, compiler_rt_lit_unit_cfg) - -# Setup config name. -config.name = 'LeakSanitizer-Unit' - -# Setup test source and exec root. For unit tests, we define -# it as build directory with LSan unit tests. -lsan_binary_dir = get_required_attr(config, "lsan_binary_dir") -config.test_exec_root = os.path.join(lsan_binary_dir, "tests") -config.test_source_root = config.test_exec_root diff --git a/lib/lsan/lit_tests/Unit/lit.site.cfg.in b/lib/lsan/lit_tests/Unit/lit.site.cfg.in index 90c88c952156..a3a4e9ad0b13 100644 --- a/lib/lsan/lit_tests/Unit/lit.site.cfg.in +++ b/lib/lsan/lit_tests/Unit/lit.site.cfg.in @@ -1,17 +1,12 @@ ## Autogenerated by LLVM/Clang configuration. # Do not edit! -config.target_triple = "@TARGET_TRIPLE@" -config.llvm_src_root = "@LLVM_SOURCE_DIR@" -config.compiler_rt_src_root = "@COMPILER_RT_SOURCE_DIR@" -config.llvm_build_mode = "@LLVM_BUILD_MODE@" -config.lsan_binary_dir = "@LSAN_BINARY_DIR@" +# Load common config for all compiler-rt unit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/lib/lit.common.unit.configured") -try: - config.llvm_build_mode = config.llvm_build_mode % lit.params -except KeyError,e: - key, = e.args - lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) - -# Let the main config do the real work. -lit.load_config(config, "@LSAN_SOURCE_DIR@/lit_tests/Unit/lit.cfg") +# Setup config name. +config.name = 'LeakSanitizer-Unit' +# Setup test source and exec root. For unit tests, we define +# it as build directory with LSan unit tests. +config.test_exec_root = "@LSAN_BINARY_DIR@/tests" +config.test_source_root = config.test_exec_root diff --git a/lib/lsan/lit_tests/lit.cfg b/lib/lsan/lit_tests/lit.cfg deleted file mode 100644 index 48e1453334d0..000000000000 --- a/lib/lsan/lit_tests/lit.cfg +++ /dev/null @@ -1,50 +0,0 @@ -# -*- Python -*- - -import os - -def get_required_attr(config, attr_name): - attr_value = getattr(config, attr_name, None) - if not attr_value: - lit.fatal("No attribute %r in test configuration! You may need to run " - "tests from your build directory or add this attribute " - "to lit.site.cfg " % attr_name) - return attr_value - -# Setup attributes common for all compiler-rt projects. -compiler_rt_src_root = get_required_attr(config, 'compiler_rt_src_root') -compiler_rt_lit_unit_cfg = os.path.join(compiler_rt_src_root, "lib", - "lit.common.unit.cfg") -lit.load_config(config, compiler_rt_lit_unit_cfg) - -# Setup config name. -config.name = 'LeakSanitizer' - -# Setup source root. -config.test_source_root = os.path.dirname(__file__) - -# Setup attributes common for all compiler-rt projects. -compiler_rt_lit_cfg = os.path.join(compiler_rt_src_root, "lib", - "lit.common.cfg") -if (not compiler_rt_lit_cfg) or (not os.path.exists(compiler_rt_lit_cfg)): - lit.fatal("Can't find common compiler-rt lit config at: %r" - % compiler_rt_lit_cfg) -lit.load_config(config, compiler_rt_lit_cfg) - -clang_cxxflags = ("-ccc-cxx " - + "-g " - + "-O0 " - + "-m64 ") - -clang_lsan_cxxflags = clang_cxxflags + "-fsanitize=leak " - -config.substitutions.append( ("%clangxx ", (" " + config.clang + " " + - clang_cxxflags + " ")) ) -config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + - clang_lsan_cxxflags + " ")) ) - -# Default test suffixes. -config.suffixes = ['.c', '.cc', '.cpp'] - -# LeakSanitizer tests are currently supported on x86-64 Linux only. -if config.host_os not in ['Linux'] or config.host_arch not in ['x86_64']: - config.unsupported = True diff --git a/lib/lsan/lit_tests/lit.common.cfg b/lib/lsan/lit_tests/lit.common.cfg new file mode 100644 index 000000000000..96dc1b1f55fc --- /dev/null +++ b/lib/lsan/lit_tests/lit.common.cfg @@ -0,0 +1,43 @@ +# -*- Python -*- + +# Common configuration for running leak detection tests under LSan/ASan. + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit_config.fatal( + "No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup source root. +lsan_lit_src_root = get_required_attr(config, 'lsan_lit_src_root') +config.test_source_root = os.path.join(lsan_lit_src_root, 'TestCases') + +clang_cxxflags = ("--driver-mode=g++ " + + "-g " + + "-O0 " + + "-m64 ") + +clang_cflags = ("-g " + + "-O0 " + + "-m64 ") + +config.clang_cxxflags = clang_cxxflags + +config.substitutions.append( ("%clangxx ", (" " + config.clang + " " + + clang_cxxflags + " ")) ) + +config.clang_cflags = clang_cflags + +config.substitutions.append( ("%clang ", (" " + config.clang + " " + + clang_cflags + " ")) ) + +# LeakSanitizer tests are currently supported on x86-64 Linux only. +if config.host_os not in ['Linux'] or config.host_arch not in ['x86_64']: + config.unsupported = True + +config.suffixes = ['.c', '.cc', '.cpp'] diff --git a/lib/lsan/lit_tests/lit.site.cfg.in b/lib/lsan/lit_tests/lit.site.cfg.in deleted file mode 100644 index 3de98a9811f8..000000000000 --- a/lib/lsan/lit_tests/lit.site.cfg.in +++ /dev/null @@ -1,20 +0,0 @@ -config.host_os = "@HOST_OS@" -config.host_arch = "@HOST_ARCH@" -config.llvm_build_mode = "@LLVM_BUILD_MODE@" -config.llvm_src_root = "@LLVM_SOURCE_DIR@" -config.compiler_rt_src_root = "@COMPILER_RT_SOURCE_DIR@" -config.llvm_obj_root = "@LLVM_BINARY_DIR@" -config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" -config.clang = "@LLVM_BINARY_DIR@/bin/clang" -config.compiler_rt_arch = "@COMPILER_RT_SUPPORTED_ARCH@" - -# LLVM tools dir can be passed in lit parameters, so try to -# apply substitution. -try: - config.llvm_tools_dir = config.llvm_tools_dir % lit.params -except KeyError,e: - key, = e.args - lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) - -# Let the main config do the real work. -lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/lsan/lsan.cc b/lib/lsan/lsan.cc index 9b83b411f843..058bbdba3907 100644 --- a/lib/lsan/lsan.cc +++ b/lib/lsan/lsan.cc @@ -20,25 +20,30 @@ #include "lsan_common.h" #include "lsan_thread.h" +bool lsan_inited; +bool lsan_init_is_running; + namespace __lsan { static void InitializeCommonFlags() { CommonFlags *cf = common_flags(); + SetCommonFlagDefaults(); cf->external_symbolizer_path = GetEnv("LSAN_SYMBOLIZER_PATH"); - cf->symbolize = (cf->external_symbolizer_path && - cf->external_symbolizer_path[0]); - cf->strip_path_prefix = ""; - cf->fast_unwind_on_malloc = true; cf->malloc_context_size = 30; + cf->detect_leaks = true; ParseCommonFlagsFromString(GetEnv("LSAN_OPTIONS")); } -void Init() { - static bool inited; - if (inited) +} // namespace __lsan + +using namespace __lsan; // NOLINT + +extern "C" void __lsan_init() { + CHECK(!lsan_init_is_running); + if (lsan_inited) return; - inited = true; + lsan_init_is_running = true; SanitizerToolName = "LeakSanitizer"; InitializeCommonFlags(); InitializeAllocator(); @@ -48,16 +53,19 @@ void Init() { u32 tid = ThreadCreate(0, 0, true); CHECK_EQ(tid, 0); ThreadStart(tid, GetTid()); + SetCurrentThread(tid); // Start symbolizer process if necessary. - const char* external_symbolizer = common_flags()->external_symbolizer_path; - if (common_flags()->symbolize && external_symbolizer && - external_symbolizer[0]) { - InitializeExternalSymbolizer(external_symbolizer); + if (common_flags()->symbolize) { + Symbolizer::Init(common_flags()->external_symbolizer_path); + } else { + Symbolizer::Disable(); } InitCommonLsan(); - Atexit(DoLeakCheck); + if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit) + Atexit(DoLeakCheck); + lsan_inited = true; + lsan_init_is_running = false; } -} // namespace __lsan diff --git a/lib/lsan/lsan.h b/lib/lsan/lsan.h index d89a6ab8f983..3e7f76b08193 100644 --- a/lib/lsan/lsan.h +++ b/lib/lsan/lsan.h @@ -17,7 +17,11 @@ namespace __lsan { -void Init(); void InitializeInterceptors(); } // namespace __lsan + +extern bool lsan_inited; +extern bool lsan_init_is_running; + +extern "C" void __lsan_init(); diff --git a/lib/lsan/lsan_allocator.cc b/lib/lsan/lsan_allocator.cc index 49b5a9fa4c5d..f7eee1314bf0 100644 --- a/lib/lsan/lsan_allocator.cc +++ b/lib/lsan/lsan_allocator.cc @@ -20,13 +20,13 @@ #include "sanitizer_common/sanitizer_stacktrace.h" #include "lsan_common.h" -namespace __lsan { +extern "C" void *memset(void *ptr, int value, uptr num); -static const uptr kMaxAllowedMallocSize = - FIRST_32_SECOND_64(3UL << 30, 8UL << 30); +namespace __lsan { +static const uptr kMaxAllowedMallocSize = 8UL << 30; static const uptr kAllocatorSpace = 0x600000000000ULL; -static const uptr kAllocatorSize = 0x10000000000ULL; // 1T. +static const uptr kAllocatorSize = 0x40000000000ULL; // 4T. struct ChunkMetadata { bool allocated : 8; // Must be first. @@ -36,7 +36,7 @@ struct ChunkMetadata { }; typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, - sizeof(ChunkMetadata), CompactSizeClassMap> PrimaryAllocator; + sizeof(ChunkMetadata), DefaultSizeClassMap> PrimaryAllocator; typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; typedef LargeMmapAllocator<> SecondaryAllocator; typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, @@ -54,23 +54,24 @@ void AllocatorThreadFinish() { } static ChunkMetadata *Metadata(void *p) { - return (ChunkMetadata *)allocator.GetMetaData(p); + return reinterpret_cast<ChunkMetadata *>(allocator.GetMetaData(p)); } static void RegisterAllocation(const StackTrace &stack, void *p, uptr size) { if (!p) return; ChunkMetadata *m = Metadata(p); CHECK(m); + m->tag = DisabledInThisThread() ? kIgnored : kDirectlyLeaked; m->stack_trace_id = StackDepotPut(stack.trace, stack.size); m->requested_size = size; - atomic_store((atomic_uint8_t*)m, 1, memory_order_relaxed); + atomic_store(reinterpret_cast<atomic_uint8_t *>(m), 1, memory_order_relaxed); } static void RegisterDeallocation(void *p) { if (!p) return; ChunkMetadata *m = Metadata(p); CHECK(m); - atomic_store((atomic_uint8_t*)m, 0, memory_order_relaxed); + atomic_store(reinterpret_cast<atomic_uint8_t *>(m), 0, memory_order_relaxed); } void *Allocate(const StackTrace &stack, uptr size, uptr alignment, @@ -78,11 +79,13 @@ void *Allocate(const StackTrace &stack, uptr size, uptr alignment, if (size == 0) size = 1; if (size > kMaxAllowedMallocSize) { - Report("WARNING: LeakSanitizer failed to allocate %p bytes\n", - (void*)size); - return 0; + Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", size); + return 0; } - void *p = allocator.Allocate(&cache, size, alignment, cleared); + void *p = allocator.Allocate(&cache, size, alignment, false); + // Do not rely on the allocator to clear the memory (it's slow). + if (cleared && allocator.FromPrimary(p)) + memset(p, 0, size); RegisterAllocation(stack, p, size); return p; } @@ -96,10 +99,9 @@ void *Reallocate(const StackTrace &stack, void *p, uptr new_size, uptr alignment) { RegisterDeallocation(p); if (new_size > kMaxAllowedMallocSize) { - Report("WARNING: LeakSanitizer failed to allocate %p bytes\n", - (void*)new_size); - allocator.Deallocate(&cache, p); - return 0; + Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", new_size); + allocator.Deallocate(&cache, p); + return 0; } p = allocator.Reallocate(&cache, p, new_size, alignment); RegisterAllocation(stack, p, new_size); @@ -132,26 +134,26 @@ void GetAllocatorGlobalRange(uptr *begin, uptr *end) { *end = *begin + sizeof(allocator); } -void *PointsIntoChunk(void* p) { - if (!allocator.PointerIsMine(p)) return 0; - void *chunk = allocator.GetBlockBegin(p); +uptr PointsIntoChunk(void* p) { + uptr addr = reinterpret_cast<uptr>(p); + uptr chunk = reinterpret_cast<uptr>(allocator.GetBlockBeginFastLocked(p)); if (!chunk) return 0; // LargeMmapAllocator considers pointers to the meta-region of a chunk to be // valid, but we don't want that. - if (p < chunk) return 0; - ChunkMetadata *m = Metadata(chunk); + if (addr < chunk) return 0; + ChunkMetadata *m = Metadata(reinterpret_cast<void *>(chunk)); CHECK(m); - if (m->allocated && (uptr)p < (uptr)chunk + m->requested_size) + if (m->allocated && addr < chunk + m->requested_size) return chunk; return 0; } -void *GetUserBegin(void *p) { - return p; +uptr GetUserBegin(uptr chunk) { + return chunk; } -LsanMetadata::LsanMetadata(void *chunk) { - metadata_ = Metadata(chunk); +LsanMetadata::LsanMetadata(uptr chunk) { + metadata_ = Metadata(reinterpret_cast<void *>(chunk)); CHECK(metadata_); } @@ -175,16 +177,22 @@ u32 LsanMetadata::stack_trace_id() const { return reinterpret_cast<ChunkMetadata *>(metadata_)->stack_trace_id; } -template<typename Callable> -void ForEachChunk(Callable const &callback) { - allocator.ForEachChunk(callback); +void ForEachChunk(ForEachChunkCallback callback, void *arg) { + allocator.ForEachChunk(callback, arg); } -template void ForEachChunk<ProcessPlatformSpecificAllocationsCb>( - ProcessPlatformSpecificAllocationsCb const &callback); -template void ForEachChunk<PrintLeakedCb>(PrintLeakedCb const &callback); -template void ForEachChunk<CollectLeaksCb>(CollectLeaksCb const &callback); -template void ForEachChunk<MarkIndirectlyLeakedCb>( - MarkIndirectlyLeakedCb const &callback); -template void ForEachChunk<ClearTagCb>(ClearTagCb const &callback); +IgnoreObjectResult IgnoreObjectLocked(const void *p) { + void *chunk = allocator.GetBlockBegin(p); + if (!chunk || p < chunk) return kIgnoreObjectInvalid; + ChunkMetadata *m = Metadata(chunk); + CHECK(m); + if (m->allocated && (uptr)p < (uptr)chunk + m->requested_size) { + if (m->tag == kIgnored) + return kIgnoreObjectAlreadyIgnored; + m->tag = kIgnored; + return kIgnoreObjectSuccess; + } else { + return kIgnoreObjectInvalid; + } +} } // namespace __lsan diff --git a/lib/lsan/lsan_common.cc b/lib/lsan/lsan_common.cc index e2971e999aa6..152588411e2f 100644 --- a/lib/lsan/lsan_common.cc +++ b/lib/lsan/lsan_common.cc @@ -16,27 +16,38 @@ #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_placement_new.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_stacktrace.h" #include "sanitizer_common/sanitizer_stoptheworld.h" +#include "sanitizer_common/sanitizer_suppressions.h" +#include "sanitizer_common/sanitizer_report_decorator.h" #if CAN_SANITIZE_LEAKS namespace __lsan { +// This mutex is used to prevent races between DoLeakCheck and IgnoreObject. +BlockingMutex global_mutex(LINKER_INITIALIZED); + +THREADLOCAL int disable_counter; +bool DisabledInThisThread() { return disable_counter > 0; } + Flags lsan_flags; static void InitializeFlags() { Flags *f = flags(); // Default values. - f->report_blocks = false; + f->report_objects = false; f->resolution = 0; f->max_leaks = 0; f->exitcode = 23; + f->suppressions=""; f->use_registers = true; f->use_globals = true; f->use_stacks = true; f->use_tls = true; f->use_unaligned = false; + f->verbosity = 0; f->log_pointers = false; f->log_threads = false; @@ -47,25 +58,60 @@ static void InitializeFlags() { ParseFlag(options, &f->use_stacks, "use_stacks"); ParseFlag(options, &f->use_tls, "use_tls"); ParseFlag(options, &f->use_unaligned, "use_unaligned"); - ParseFlag(options, &f->report_blocks, "report_blocks"); + ParseFlag(options, &f->report_objects, "report_objects"); ParseFlag(options, &f->resolution, "resolution"); CHECK_GE(&f->resolution, 0); ParseFlag(options, &f->max_leaks, "max_leaks"); CHECK_GE(&f->max_leaks, 0); + ParseFlag(options, &f->verbosity, "verbosity"); ParseFlag(options, &f->log_pointers, "log_pointers"); ParseFlag(options, &f->log_threads, "log_threads"); ParseFlag(options, &f->exitcode, "exitcode"); + ParseFlag(options, &f->suppressions, "suppressions"); + } +} + +SuppressionContext *suppression_ctx; + +void InitializeSuppressions() { + CHECK(!suppression_ctx); + ALIGNED(64) static char placeholder_[sizeof(SuppressionContext)]; + suppression_ctx = new(placeholder_) SuppressionContext; + char *suppressions_from_file; + uptr buffer_size; + if (ReadFileToBuffer(flags()->suppressions, &suppressions_from_file, + &buffer_size, 1 << 26 /* max_len */)) + suppression_ctx->Parse(suppressions_from_file); + if (flags()->suppressions[0] && !buffer_size) { + Printf("LeakSanitizer: failed to read suppressions file '%s'\n", + flags()->suppressions); + Die(); } + if (&__lsan_default_suppressions) + suppression_ctx->Parse(__lsan_default_suppressions()); } void InitCommonLsan() { InitializeFlags(); - InitializePlatformSpecificModules(); + if (common_flags()->detect_leaks) { + // Initialization which can fail or print warnings should only be done if + // LSan is actually enabled. + InitializeSuppressions(); + InitializePlatformSpecificModules(); + } } +class Decorator: private __sanitizer::AnsiColorDecorator { + public: + Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { } + const char *Error() { return Red(); } + const char *Leak() { return Blue(); } + const char *End() { return Default(); } +}; + static inline bool CanBeAHeapPointer(uptr p) { // Since our heap is located in mmap-ed memory, we can assume a sensible lower - // boundary on heap addresses. + // bound on heap addresses. const uptr kMinAddress = 4 * 4096; if (p < kMinAddress) return false; #ifdef __x86_64__ @@ -76,13 +122,14 @@ static inline bool CanBeAHeapPointer(uptr p) { #endif } -// Scan the memory range, looking for byte patterns that point into allocator -// chunks. Mark those chunks with tag and add them to the frontier. -// There are two usage modes for this function: finding non-leaked chunks -// (tag = kReachable) and finding indirectly leaked chunks -// (tag = kIndirectlyLeaked). In the second case, there's no flood fill, -// so frontier = 0. -void ScanRangeForPointers(uptr begin, uptr end, InternalVector<uptr> *frontier, +// Scans the memory range, looking for byte patterns that point into allocator +// chunks. Marks those chunks with |tag| and adds them to |frontier|. +// There are two usage modes for this function: finding reachable or ignored +// chunks (|tag| = kReachable or kIgnored) and finding indirectly leaked chunks +// (|tag| = kIndirectlyLeaked). In the second case, there's no flood fill, +// so |frontier| = 0. +void ScanRangeForPointers(uptr begin, uptr end, + Frontier *frontier, const char *region_type, ChunkTag tag) { const uptr alignment = flags()->pointer_alignment(); if (flags()->log_pointers) @@ -90,28 +137,34 @@ void ScanRangeForPointers(uptr begin, uptr end, InternalVector<uptr> *frontier, uptr pp = begin; if (pp % alignment) pp = pp + alignment - pp % alignment; - for (; pp + sizeof(uptr) <= end; pp += alignment) { - void *p = *reinterpret_cast<void**>(pp); + for (; pp + sizeof(void *) <= end; pp += alignment) { // NOLINT + void *p = *reinterpret_cast<void **>(pp); if (!CanBeAHeapPointer(reinterpret_cast<uptr>(p))) continue; - // FIXME: PointsIntoChunk is SLOW because GetBlockBegin() in - // LargeMmapAllocator involves a lock and a linear search. - void *chunk = PointsIntoChunk(p); + uptr chunk = PointsIntoChunk(p); if (!chunk) continue; + // Pointers to self don't count. This matters when tag == kIndirectlyLeaked. + if (chunk == begin) continue; LsanMetadata m(chunk); + // Reachable beats ignored beats leaked. if (m.tag() == kReachable) continue; + if (m.tag() == kIgnored && tag != kReachable) continue; m.set_tag(tag); if (flags()->log_pointers) - Report("%p: found %p pointing into chunk %p-%p of size %llu.\n", pp, p, - chunk, reinterpret_cast<uptr>(chunk) + m.requested_size(), - m.requested_size()); + Report("%p: found %p pointing into chunk %p-%p of size %zu.\n", pp, p, + chunk, chunk + m.requested_size(), m.requested_size()); if (frontier) - frontier->push_back(reinterpret_cast<uptr>(chunk)); + frontier->push_back(chunk); } } -// Scan thread data (stacks and TLS) for heap pointers. +void ForEachExtraStackRangeCb(uptr begin, uptr end, void* arg) { + Frontier *frontier = reinterpret_cast<Frontier *>(arg); + ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable); +} + +// Scans thread data (stacks and TLS) for heap pointers. static void ProcessThreads(SuspendedThreadsList const &suspended_threads, - InternalVector<uptr> *frontier) { + Frontier *frontier) { InternalScopedBuffer<uptr> registers(SuspendedThreadsList::RegisterCount()); uptr registers_begin = reinterpret_cast<uptr>(registers.data()); uptr registers_end = registers_begin + registers.size(); @@ -150,13 +203,14 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads, // signal handler on alternate stack). Again, consider the entire stack // range to be reachable. if (flags()->log_threads) - Report("WARNING: stack_pointer not in stack_range.\n"); + Report("WARNING: stack pointer not in stack range.\n"); } else { // Shrink the stack range to ignore out-of-scope values. stack_begin = sp; } ScanRangeForPointers(stack_begin, stack_end, frontier, "STACK", kReachable); + ForEachExtraStackRange(os_id, ForEachExtraStackRangeCb, frontier); } if (flags()->use_tls) { @@ -178,152 +232,210 @@ static void ProcessThreads(SuspendedThreadsList const &suspended_threads, } } -static void FloodFillReachable(InternalVector<uptr> *frontier) { +static void FloodFillTag(Frontier *frontier, ChunkTag tag) { while (frontier->size()) { uptr next_chunk = frontier->back(); frontier->pop_back(); - LsanMetadata m(reinterpret_cast<void *>(next_chunk)); + LsanMetadata m(next_chunk); ScanRangeForPointers(next_chunk, next_chunk + m.requested_size(), frontier, - "HEAP", kReachable); + "HEAP", tag); } } -// Mark leaked chunks which are reachable from other leaked chunks. -void MarkIndirectlyLeakedCb::operator()(void *p) const { - p = GetUserBegin(p); - LsanMetadata m(p); +// ForEachChunk callback. If the chunk is marked as leaked, marks all chunks +// which are reachable from it as indirectly leaked. +static void MarkIndirectlyLeakedCb(uptr chunk, void *arg) { + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); if (m.allocated() && m.tag() != kReachable) { - ScanRangeForPointers(reinterpret_cast<uptr>(p), - reinterpret_cast<uptr>(p) + m.requested_size(), + ScanRangeForPointers(chunk, chunk + m.requested_size(), /* frontier */ 0, "HEAP", kIndirectlyLeaked); } } -// Set the appropriate tag on each chunk. +// ForEachChunk callback. If chunk is marked as ignored, adds its address to +// frontier. +static void CollectIgnoredCb(uptr chunk, void *arg) { + CHECK(arg); + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); + if (m.allocated() && m.tag() == kIgnored) + reinterpret_cast<Frontier *>(arg)->push_back(chunk); +} + +// Sets the appropriate tag on each chunk. static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads) { // Holds the flood fill frontier. - InternalVector<uptr> frontier(GetPageSizeCached()); + Frontier frontier(GetPageSizeCached()); if (flags()->use_globals) ProcessGlobalRegions(&frontier); ProcessThreads(suspended_threads, &frontier); - FloodFillReachable(&frontier); + FloodFillTag(&frontier, kReachable); + // The check here is relatively expensive, so we do this in a separate flood + // fill. That way we can skip the check for chunks that are reachable + // otherwise. + if (flags()->log_pointers) + Report("Processing platform-specific allocations.\n"); ProcessPlatformSpecificAllocations(&frontier); - FloodFillReachable(&frontier); + FloodFillTag(&frontier, kReachable); - // Now all reachable chunks are marked. Iterate over leaked chunks and mark - // those that are reachable from other leaked chunks. if (flags()->log_pointers) - Report("Now scanning leaked blocks for pointers.\n"); - ForEachChunk(MarkIndirectlyLeakedCb()); -} + Report("Scanning ignored chunks.\n"); + CHECK_EQ(0, frontier.size()); + ForEachChunk(CollectIgnoredCb, &frontier); + FloodFillTag(&frontier, kIgnored); -void ClearTagCb::operator()(void *p) const { - p = GetUserBegin(p); - LsanMetadata m(p); - m.set_tag(kDirectlyLeaked); + // Iterate over leaked chunks and mark those that are reachable from other + // leaked chunks. + if (flags()->log_pointers) + Report("Scanning leaked chunks.\n"); + ForEachChunk(MarkIndirectlyLeakedCb, 0 /* arg */); } static void PrintStackTraceById(u32 stack_trace_id) { CHECK(stack_trace_id); uptr size = 0; const uptr *trace = StackDepotGet(stack_trace_id, &size); - StackTrace::PrintStack(trace, size, common_flags()->symbolize, - common_flags()->strip_path_prefix, 0); -} - -static void LockAndSuspendThreads(StopTheWorldCallback callback, void *arg) { - LockThreadRegistry(); - LockAllocator(); - StopTheWorld(callback, arg); - // Allocator must be unlocked by the callback. - UnlockThreadRegistry(); + StackTrace::PrintStack(trace, size); } -///// Normal leak checking. ///// - -void CollectLeaksCb::operator()(void *p) const { - p = GetUserBegin(p); - LsanMetadata m(p); +// ForEachChunk callback. Aggregates unreachable chunks into a LeakReport. +static void CollectLeaksCb(uptr chunk, void *arg) { + CHECK(arg); + LeakReport *leak_report = reinterpret_cast<LeakReport *>(arg); + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); if (!m.allocated()) return; - if (m.tag() != kReachable) { + if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked) { uptr resolution = flags()->resolution; if (resolution > 0) { uptr size = 0; const uptr *trace = StackDepotGet(m.stack_trace_id(), &size); size = Min(size, resolution); - leak_report_->Add(StackDepotPut(trace, size), m.requested_size(), - m.tag()); + leak_report->Add(StackDepotPut(trace, size), m.requested_size(), m.tag()); } else { - leak_report_->Add(m.stack_trace_id(), m.requested_size(), m.tag()); + leak_report->Add(m.stack_trace_id(), m.requested_size(), m.tag()); } } } -static void CollectLeaks(LeakReport *leak_report) { - ForEachChunk(CollectLeaksCb(leak_report)); -} - -void PrintLeakedCb::operator()(void *p) const { - p = GetUserBegin(p); - LsanMetadata m(p); +// ForEachChunkCallback. Prints addresses of unreachable chunks. +static void PrintLeakedCb(uptr chunk, void *arg) { + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); if (!m.allocated()) return; - if (m.tag() != kReachable) { - CHECK(m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked); - Printf("%s leaked %llu byte block at %p\n", + if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked) { + Printf("%s leaked %zu byte object at %p.\n", m.tag() == kDirectlyLeaked ? "Directly" : "Indirectly", - m.requested_size(), p); + m.requested_size(), chunk); } } +static void PrintMatchedSuppressions() { + InternalMmapVector<Suppression *> matched(1); + suppression_ctx->GetMatched(&matched); + if (!matched.size()) + return; + const char *line = "-----------------------------------------------------"; + Printf("%s\n", line); + Printf("Suppressions used:\n"); + Printf(" count bytes template\n"); + for (uptr i = 0; i < matched.size(); i++) + Printf("%7zu %10zu %s\n", static_cast<uptr>(matched[i]->hit_count), + matched[i]->weight, matched[i]->templ); + Printf("%s\n\n", line); +} + static void PrintLeaked() { - Printf("Reporting individual blocks:\n"); - Printf("============================\n"); - ForEachChunk(PrintLeakedCb()); Printf("\n"); + Printf("Reporting individual objects:\n"); + ForEachChunk(PrintLeakedCb, 0 /* arg */); } -enum LeakCheckResult { - kFatalError, - kLeaksFound, - kNoLeaks +struct DoLeakCheckParam { + bool success; + LeakReport leak_report; }; static void DoLeakCheckCallback(const SuspendedThreadsList &suspended_threads, void *arg) { - LeakCheckResult *result = reinterpret_cast<LeakCheckResult *>(arg); - CHECK_EQ(*result, kFatalError); - // Allocator must not be locked when we call GetRegionBegin(). - UnlockAllocator(); + DoLeakCheckParam *param = reinterpret_cast<DoLeakCheckParam *>(arg); + CHECK(param); + CHECK(!param->success); + CHECK(param->leak_report.IsEmpty()); ClassifyAllChunks(suspended_threads); - LeakReport leak_report; - CollectLeaks(&leak_report); - if (leak_report.IsEmpty()) { - *result = kNoLeaks; - return; - } - Printf("\n"); - Printf("=================================================================\n"); - Report("ERROR: LeakSanitizer: detected memory leaks\n"); - leak_report.PrintLargest(flags()->max_leaks); - if (flags()->report_blocks) + ForEachChunk(CollectLeaksCb, ¶m->leak_report); + if (!param->leak_report.IsEmpty() && flags()->report_objects) PrintLeaked(); - leak_report.PrintSummary(); - Printf("\n"); - ForEachChunk(ClearTagCb()); - *result = kLeaksFound; + param->success = true; } void DoLeakCheck() { - LeakCheckResult result = kFatalError; - LockAndSuspendThreads(DoLeakCheckCallback, &result); - if (result == kFatalError) { + EnsureMainThreadIDIsCorrect(); + BlockingMutexLock l(&global_mutex); + static bool already_done; + if (already_done) return; + already_done = true; + if (&__lsan_is_turned_off && __lsan_is_turned_off()) + return; + + DoLeakCheckParam param; + param.success = false; + LockThreadRegistry(); + LockAllocator(); + StopTheWorld(DoLeakCheckCallback, ¶m); + UnlockAllocator(); + UnlockThreadRegistry(); + + if (!param.success) { Report("LeakSanitizer has encountered a fatal error.\n"); Die(); - } else if (result == kLeaksFound) { - if (flags()->exitcode) - internal__exit(flags()->exitcode); } + uptr have_unsuppressed = param.leak_report.ApplySuppressions(); + if (have_unsuppressed) { + Decorator d; + Printf("\n" + "=================================================================" + "\n"); + Printf("%s", d.Error()); + Report("ERROR: LeakSanitizer: detected memory leaks\n"); + Printf("%s", d.End()); + param.leak_report.PrintLargest(flags()->max_leaks); + } + if (have_unsuppressed || (flags()->verbosity >= 1)) { + PrintMatchedSuppressions(); + param.leak_report.PrintSummary(); + } + if (have_unsuppressed && flags()->exitcode) + internal__exit(flags()->exitcode); +} + +static Suppression *GetSuppressionForAddr(uptr addr) { + static const uptr kMaxAddrFrames = 16; + InternalScopedBuffer<AddressInfo> addr_frames(kMaxAddrFrames); + for (uptr i = 0; i < kMaxAddrFrames; i++) new (&addr_frames[i]) AddressInfo(); + uptr addr_frames_num = Symbolizer::Get()->SymbolizeCode( + addr, addr_frames.data(), kMaxAddrFrames); + for (uptr i = 0; i < addr_frames_num; i++) { + Suppression* s; + if (suppression_ctx->Match(addr_frames[i].function, SuppressionLeak, &s) || + suppression_ctx->Match(addr_frames[i].file, SuppressionLeak, &s) || + suppression_ctx->Match(addr_frames[i].module, SuppressionLeak, &s)) + return s; + } + return 0; +} + +static Suppression *GetSuppressionForStack(u32 stack_trace_id) { + uptr size = 0; + const uptr *trace = StackDepotGet(stack_trace_id, &size); + for (uptr i = 0; i < size; i++) { + Suppression *s = + GetSuppressionForAddr(StackTrace::GetPreviousInstructionPc(trace[i])); + if (s) return s; + } + return 0; } ///// LeakReport implementation. ///// @@ -333,7 +445,7 @@ void DoLeakCheck() { // real-world applications. // FIXME: Get rid of this limit by changing the implementation of LeakReport to // use a hash table. -const uptr kMaxLeaksConsidered = 1000; +const uptr kMaxLeaksConsidered = 5000; void LeakReport::Add(u32 stack_trace_id, uptr leaked_size, ChunkTag tag) { CHECK(tag == kDirectlyLeaked || tag == kIndirectlyLeaked); @@ -347,35 +459,47 @@ void LeakReport::Add(u32 stack_trace_id, uptr leaked_size, ChunkTag tag) { } if (leaks_.size() == kMaxLeaksConsidered) return; Leak leak = { /* hit_count */ 1, leaked_size, stack_trace_id, - is_directly_leaked }; + is_directly_leaked, /* is_suppressed */ false }; leaks_.push_back(leak); } -static bool IsLarger(const Leak &leak1, const Leak &leak2) { - return leak1.total_size > leak2.total_size; +static bool LeakComparator(const Leak &leak1, const Leak &leak2) { + if (leak1.is_directly_leaked == leak2.is_directly_leaked) + return leak1.total_size > leak2.total_size; + else + return leak1.is_directly_leaked; } -void LeakReport::PrintLargest(uptr max_leaks) { +void LeakReport::PrintLargest(uptr num_leaks_to_print) { CHECK(leaks_.size() <= kMaxLeaksConsidered); Printf("\n"); if (leaks_.size() == kMaxLeaksConsidered) - Printf("Too many leaks! Only the first %llu leaks encountered will be " + Printf("Too many leaks! Only the first %zu leaks encountered will be " "reported.\n", kMaxLeaksConsidered); - if (max_leaks > 0 && max_leaks < leaks_.size()) - Printf("The %llu largest leak(s):\n", max_leaks); - InternalSort(&leaks_, leaks_.size(), IsLarger); - max_leaks = max_leaks > 0 ? Min(max_leaks, leaks_.size()) : leaks_.size(); - for (uptr i = 0; i < max_leaks; i++) { - Printf("%s leak of %llu byte(s) in %llu object(s) allocated from:\n", + + uptr unsuppressed_count = 0; + for (uptr i = 0; i < leaks_.size(); i++) + if (!leaks_[i].is_suppressed) unsuppressed_count++; + if (num_leaks_to_print > 0 && num_leaks_to_print < unsuppressed_count) + Printf("The %zu largest leak(s):\n", num_leaks_to_print); + InternalSort(&leaks_, leaks_.size(), LeakComparator); + uptr leaks_printed = 0; + Decorator d; + for (uptr i = 0; i < leaks_.size(); i++) { + if (leaks_[i].is_suppressed) continue; + Printf("%s", d.Leak()); + Printf("%s leak of %zu byte(s) in %zu object(s) allocated from:\n", leaks_[i].is_directly_leaked ? "Direct" : "Indirect", leaks_[i].total_size, leaks_[i].hit_count); + Printf("%s", d.End()); PrintStackTraceById(leaks_[i].stack_trace_id); - Printf("\n"); + leaks_printed++; + if (leaks_printed == num_leaks_to_print) break; } - if (max_leaks < leaks_.size()) { - uptr remaining = leaks_.size() - max_leaks; - Printf("Omitting %llu more leak(s).\n", remaining); + if (leaks_printed < unsuppressed_count) { + uptr remaining = unsuppressed_count - leaks_printed; + Printf("Omitting %zu more leak(s).\n", remaining); } } @@ -383,11 +507,86 @@ void LeakReport::PrintSummary() { CHECK(leaks_.size() <= kMaxLeaksConsidered); uptr bytes = 0, allocations = 0; for (uptr i = 0; i < leaks_.size(); i++) { + if (leaks_[i].is_suppressed) continue; bytes += leaks_[i].total_size; allocations += leaks_[i].hit_count; } - Printf("SUMMARY: LeakSanitizer: %llu byte(s) leaked in %llu allocation(s).\n", - bytes, allocations); + InternalScopedBuffer<char> summary(kMaxSummaryLength); + internal_snprintf(summary.data(), summary.size(), + "%zu byte(s) leaked in %zu allocation(s).", bytes, + allocations); + ReportErrorSummary(summary.data()); +} + +uptr LeakReport::ApplySuppressions() { + uptr unsuppressed_count = 0; + for (uptr i = 0; i < leaks_.size(); i++) { + Suppression *s = GetSuppressionForStack(leaks_[i].stack_trace_id); + if (s) { + s->weight += leaks_[i].total_size; + s->hit_count += leaks_[i].hit_count; + leaks_[i].is_suppressed = true; + } else { + unsuppressed_count++; + } + } + return unsuppressed_count; } } // namespace __lsan #endif // CAN_SANITIZE_LEAKS + +using namespace __lsan; // NOLINT + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_ignore_object(const void *p) { +#if CAN_SANITIZE_LEAKS + if (!common_flags()->detect_leaks) + return; + // Cannot use PointsIntoChunk or LsanMetadata here, since the allocator is not + // locked. + BlockingMutexLock l(&global_mutex); + IgnoreObjectResult res = IgnoreObjectLocked(p); + if (res == kIgnoreObjectInvalid && flags()->verbosity >= 2) + Report("__lsan_ignore_object(): no heap object found at %p", p); + if (res == kIgnoreObjectAlreadyIgnored && flags()->verbosity >= 2) + Report("__lsan_ignore_object(): " + "heap object at %p is already being ignored\n", p); + if (res == kIgnoreObjectSuccess && flags()->verbosity >= 3) + Report("__lsan_ignore_object(): ignoring heap object at %p\n", p); +#endif // CAN_SANITIZE_LEAKS +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_disable() { +#if CAN_SANITIZE_LEAKS + __lsan::disable_counter++; +#endif +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_enable() { +#if CAN_SANITIZE_LEAKS + if (!__lsan::disable_counter && common_flags()->detect_leaks) { + Report("Unmatched call to __lsan_enable().\n"); + Die(); + } + __lsan::disable_counter--; +#endif +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __lsan_do_leak_check() { +#if CAN_SANITIZE_LEAKS + if (common_flags()->detect_leaks) + __lsan::DoLeakCheck(); +#endif // CAN_SANITIZE_LEAKS +} + +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE +int __lsan_is_turned_off() { + return 0; +} +#endif +} // extern "C" diff --git a/lib/lsan/lsan_common.h b/lib/lsan/lsan_common.h index 8cb4b2753cd9..d490f8bafd9e 100644 --- a/lib/lsan/lsan_common.h +++ b/lib/lsan/lsan_common.h @@ -15,6 +15,7 @@ #ifndef LSAN_COMMON_H #define LSAN_COMMON_H +#include "sanitizer_common/sanitizer_allocator.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_internal_defs.h" #include "sanitizer_common/sanitizer_platform.h" @@ -32,7 +33,8 @@ namespace __lsan { enum ChunkTag { kDirectlyLeaked = 0, // default kIndirectlyLeaked = 1, - kReachable = 2 + kReachable = 2, + kIgnored = 3 }; struct Flags { @@ -40,15 +42,17 @@ struct Flags { return use_unaligned ? 1 : sizeof(uptr); } - // Print addresses of leaked blocks after main leak report. - bool report_blocks; - // Aggregate two blocks into one leak if this many stack frames match. If + // Print addresses of leaked objects after main leak report. + bool report_objects; + // Aggregate two objects into one leak if this many stack frames match. If // zero, the entire stack trace must match. int resolution; // The number of leaks reported. int max_leaks; // If nonzero kill the process with this exit code upon finding leaks. int exitcode; + // Suppressions file name. + const char* suppressions; // Flags controlling the root set of reachable memory. // Global variables (.data and .bss). @@ -63,6 +67,9 @@ struct Flags { // Consider unaligned pointers valid. bool use_unaligned; + // User-visible verbosity. + int verbosity; + // Debug logging. bool log_pointers; bool log_threads; @@ -71,17 +78,12 @@ struct Flags { extern Flags lsan_flags; inline Flags *flags() { return &lsan_flags; } -void InitCommonLsan(); -// Testing interface. Find leaked chunks and dump their addresses to vector. -void ReportLeaked(InternalVector<void *> *leaked, uptr sources); -// Normal leak check. Find leaks and print a report according to flags. -void DoLeakCheck(); - struct Leak { uptr hit_count; uptr total_size; u32 stack_trace_id; bool is_directly_leaked; + bool is_suppressed; }; // Aggregates leaks by stack trace prefix. @@ -92,66 +94,37 @@ class LeakReport { void PrintLargest(uptr max_leaks); void PrintSummary(); bool IsEmpty() { return leaks_.size() == 0; } + uptr ApplySuppressions(); private: - InternalVector<Leak> leaks_; + InternalMmapVector<Leak> leaks_; }; +typedef InternalMmapVector<uptr> Frontier; + // Platform-specific functions. void InitializePlatformSpecificModules(); -void ProcessGlobalRegions(InternalVector<uptr> *frontier); -void ProcessPlatformSpecificAllocations(InternalVector<uptr> *frontier); +void ProcessGlobalRegions(Frontier *frontier); +void ProcessPlatformSpecificAllocations(Frontier *frontier); -void ScanRangeForPointers(uptr begin, uptr end, InternalVector<uptr> *frontier, +void ScanRangeForPointers(uptr begin, uptr end, + Frontier *frontier, const char *region_type, ChunkTag tag); -// Callables for iterating over chunks. Those classes are used as template -// parameters in ForEachChunk, so we must expose them here to allow for explicit -// template instantiation. - -// Identifies unreachable chunks which must be treated as reachable. Marks them -// as reachable and adds them to the frontier. -class ProcessPlatformSpecificAllocationsCb { - public: - explicit ProcessPlatformSpecificAllocationsCb(InternalVector<uptr> *frontier) - : frontier_(frontier) {} - void operator()(void *p) const; - private: - InternalVector<uptr> *frontier_; -}; - -// Prints addresses of unreachable chunks. -class PrintLeakedCb { - public: - void operator()(void *p) const; -}; - -// Aggregates unreachable chunks into a LeakReport. -class CollectLeaksCb { - public: - explicit CollectLeaksCb(LeakReport *leak_report) - : leak_report_(leak_report) {} - void operator()(void *p) const; - private: - LeakReport *leak_report_; -}; - -// Resets each chunk's tag to default (kDirectlyLeaked). -class ClearTagCb { - public: - void operator()(void *p) const; +enum IgnoreObjectResult { + kIgnoreObjectSuccess, + kIgnoreObjectAlreadyIgnored, + kIgnoreObjectInvalid }; -// Scans each leaked chunk for pointers to other leaked chunks, and marks each -// of them as indirectly leaked. -class MarkIndirectlyLeakedCb { - public: - void operator()(void *p) const; -}; +// Functions called from the parent tool. +void InitCommonLsan(); +void DoLeakCheck(); +bool DisabledInThisThread(); // The following must be implemented in the parent tool. -template<typename Callable> void ForEachChunk(Callable const &callback); -// The address range occupied by the global allocator object. +void ForEachChunk(ForEachChunkCallback callback, void *arg); +// Returns the address range occupied by the global allocator object. void GetAllocatorGlobalRange(uptr *begin, uptr *end); // Wrappers for allocator's ForceLock()/ForceUnlock(). void LockAllocator(); @@ -162,16 +135,27 @@ void UnlockThreadRegistry(); bool GetThreadRangesLocked(uptr os_id, uptr *stack_begin, uptr *stack_end, uptr *tls_begin, uptr *tls_end, uptr *cache_begin, uptr *cache_end); -// If p points into a chunk that has been allocated to the user, return its -// user-visible address. Otherwise, return 0. -void *PointsIntoChunk(void *p); -// Return address of user-visible chunk contained in this allocator chunk. -void *GetUserBegin(void *p); +void ForEachExtraStackRange(uptr os_id, RangeIteratorCallback callback, + void *arg); +// If called from the main thread, updates the main thread's TID in the thread +// registry. We need this to handle processes that fork() without a subsequent +// exec(), which invalidates the recorded TID. To update it, we must call +// gettid() from the main thread. Our solution is to call this function before +// leak checking and also before every call to pthread_create() (to handle cases +// where leak checking is initiated from a non-main thread). +void EnsureMainThreadIDIsCorrect(); +// If p points into a chunk that has been allocated to the user, returns its +// user-visible address. Otherwise, returns 0. +uptr PointsIntoChunk(void *p); +// Returns address of user-visible chunk contained in this allocator chunk. +uptr GetUserBegin(uptr chunk); +// Helper for __lsan_ignore_object(). +IgnoreObjectResult IgnoreObjectLocked(const void *p); // Wrapper for chunk metadata operations. class LsanMetadata { public: - // Constructor accepts pointer to user-visible chunk. - explicit LsanMetadata(void *chunk); + // Constructor accepts address of user-visible chunk. + explicit LsanMetadata(uptr chunk); bool allocated() const; ChunkTag tag() const; void set_tag(ChunkTag value); @@ -183,4 +167,12 @@ class LsanMetadata { } // namespace __lsan +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE +int __lsan_is_turned_off(); + +SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE +const char *__lsan_default_suppressions(); +} // extern "C" + #endif // LSAN_COMMON_H diff --git a/lib/lsan/lsan_common_linux.cc b/lib/lsan/lsan_common_linux.cc index 10a434b5f851..ef8857fe8dbb 100644 --- a/lib/lsan/lsan_common_linux.cc +++ b/lib/lsan/lsan_common_linux.cc @@ -53,8 +53,7 @@ void InitializePlatformSpecificModules() { static int ProcessGlobalRegionsCallback(struct dl_phdr_info *info, size_t size, void *data) { - InternalVector<uptr> *frontier = - reinterpret_cast<InternalVector<uptr> *>(data); + Frontier *frontier = reinterpret_cast<Frontier *>(data); for (uptr j = 0; j < info->dlpi_phnum; j++) { const ElfW(Phdr) *phdr = &(info->dlpi_phdr[j]); // We're looking for .data and .bss sections, which reside in writeable, @@ -82,8 +81,8 @@ static int ProcessGlobalRegionsCallback(struct dl_phdr_info *info, size_t size, return 0; } -// Scan global variables for heap pointers. -void ProcessGlobalRegions(InternalVector<uptr> *frontier) { +// Scans global variables for heap pointers. +void ProcessGlobalRegions(Frontier *frontier) { // FIXME: dl_iterate_phdr acquires a linker lock, so we run a risk of // deadlocking by running this under StopTheWorld. However, the lock is // reentrant, so we should be able to fix this by acquiring the lock before @@ -91,32 +90,51 @@ void ProcessGlobalRegions(InternalVector<uptr> *frontier) { dl_iterate_phdr(ProcessGlobalRegionsCallback, frontier); } -static uptr GetCallerPC(u32 stack_id) { +static uptr GetCallerPC(u32 stack_id, StackDepotReverseMap *map) { CHECK(stack_id); uptr size = 0; - const uptr *trace = StackDepotGet(stack_id, &size); + const uptr *trace = map->Get(stack_id, &size); // The top frame is our malloc/calloc/etc. The next frame is the caller. - CHECK_GE(size, 2); - return trace[1]; + if (size >= 2) + return trace[1]; + return 0; } -void ProcessPlatformSpecificAllocationsCb::operator()(void *p) const { - p = GetUserBegin(p); - LsanMetadata m(p); +struct ProcessPlatformAllocParam { + Frontier *frontier; + StackDepotReverseMap *stack_depot_reverse_map; +}; + +// ForEachChunk callback. Identifies unreachable chunks which must be treated as +// reachable. Marks them as reachable and adds them to the frontier. +static void ProcessPlatformSpecificAllocationsCb(uptr chunk, void *arg) { + CHECK(arg); + ProcessPlatformAllocParam *param = + reinterpret_cast<ProcessPlatformAllocParam *>(arg); + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); if (m.allocated() && m.tag() != kReachable) { - if (linker->containsAddress(GetCallerPC(m.stack_trace_id()))) { + u32 stack_id = m.stack_trace_id(); + uptr caller_pc = 0; + if (stack_id > 0) + caller_pc = GetCallerPC(stack_id, param->stack_depot_reverse_map); + // If caller_pc is unknown, this chunk may be allocated in a coroutine. Mark + // it as reachable, as we can't properly report its allocation stack anyway. + if (caller_pc == 0 || linker->containsAddress(caller_pc)) { m.set_tag(kReachable); - frontier_->push_back(reinterpret_cast<uptr>(p)); + param->frontier->push_back(chunk); } } } -// Handle dynamically allocated TLS blocks by treating all chunks allocated from -// ld-linux.so as reachable. -void ProcessPlatformSpecificAllocations(InternalVector<uptr> *frontier) { +// Handles dynamically allocated TLS blocks by treating all chunks allocated +// from ld-linux.so as reachable. +void ProcessPlatformSpecificAllocations(Frontier *frontier) { if (!flags()->use_tls) return; if (!linker) return; - ForEachChunk(ProcessPlatformSpecificAllocationsCb(frontier)); + StackDepotReverseMap stack_depot_reverse_map; + ProcessPlatformAllocParam arg = {frontier, &stack_depot_reverse_map}; + ForEachChunk(ProcessPlatformSpecificAllocationsCb, &arg); } } // namespace __lsan diff --git a/lib/lsan/lsan_interceptors.cc b/lib/lsan/lsan_interceptors.cc index b2eb6e310229..400230b885ff 100644 --- a/lib/lsan/lsan_interceptors.cc +++ b/lib/lsan/lsan_interceptors.cc @@ -44,66 +44,85 @@ int pthread_setspecific(unsigned key, const void *v); stack_top = t->stack_end(); \ stack_bottom = t->stack_begin(); \ } \ - GetStackTrace(&stack, __sanitizer::common_flags()->malloc_context_size, \ - StackTrace::GetCurrentPc(), \ - GET_CURRENT_FRAME(), stack_top, stack_bottom, fast); \ + stack.Unwind(__sanitizer::common_flags()->malloc_context_size, \ + StackTrace::GetCurrentPc(), \ + GET_CURRENT_FRAME(), stack_top, stack_bottom, fast); \ } +#define ENSURE_LSAN_INITED do { \ + CHECK(!lsan_init_is_running); \ + if (!lsan_inited) \ + __lsan_init(); \ +} while (0) + ///// Malloc/free interceptors. ///// +const bool kAlwaysClearMemory = true; + namespace std { struct nothrow_t; } INTERCEPTOR(void*, malloc, uptr size) { - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; - return Allocate(stack, size, 1, false); + return Allocate(stack, size, 1, kAlwaysClearMemory); } INTERCEPTOR(void, free, void *p) { - Init(); + ENSURE_LSAN_INITED; Deallocate(p); } INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) { + if (lsan_init_is_running) { + // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym. + const uptr kCallocPoolSize = 1024; + static uptr calloc_memory_for_dlsym[kCallocPoolSize]; + static uptr allocated; + uptr size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; + void *mem = (void*)&calloc_memory_for_dlsym[allocated]; + allocated += size_in_words; + CHECK(allocated < kCallocPoolSize); + return mem; + } if (CallocShouldReturnNullDueToOverflow(size, nmemb)) return 0; - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; size *= nmemb; return Allocate(stack, size, 1, true); } INTERCEPTOR(void*, realloc, void *q, uptr size) { - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; return Reallocate(stack, q, size, 1); } INTERCEPTOR(void*, memalign, uptr alignment, uptr size) { - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; - return Allocate(stack, size, alignment, false); + return Allocate(stack, size, alignment, kAlwaysClearMemory); } INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; - *memptr = Allocate(stack, size, alignment, false); + *memptr = Allocate(stack, size, alignment, kAlwaysClearMemory); // FIXME: Return ENOMEM if user requested more than max alloc size. return 0; } INTERCEPTOR(void*, valloc, uptr size) { - Init(); + ENSURE_LSAN_INITED; GET_STACK_TRACE; if (size == 0) size = GetPageSizeCached(); - return Allocate(stack, size, GetPageSizeCached(), false); + return Allocate(stack, size, GetPageSizeCached(), kAlwaysClearMemory); } INTERCEPTOR(uptr, malloc_usable_size, void *ptr) { - Init(); + ENSURE_LSAN_INITED; return GetMallocUsableSize(ptr); } @@ -121,28 +140,52 @@ INTERCEPTOR(int, mallopt, int cmd, int value) { return -1; } -void *operator new(uptr size) ALIAS("malloc") SANITIZER_INTERFACE_ATTRIBUTE; -void *operator new[](uptr size) ALIAS("malloc") SANITIZER_INTERFACE_ATTRIBUTE; -void *operator new(uptr size, std::nothrow_t const&) ALIAS("malloc") - SANITIZER_INTERFACE_ATTRIBUTE; -void *operator new[](uptr size, std::nothrow_t const&) ALIAS("malloc") - SANITIZER_INTERFACE_ATTRIBUTE; -void operator delete(void *ptr) ALIAS("free") SANITIZER_INTERFACE_ATTRIBUTE; -void operator delete[](void *ptr) ALIAS("free") SANITIZER_INTERFACE_ATTRIBUTE; -void operator delete(void *ptr, std::nothrow_t const&) ALIAS("free") - SANITIZER_INTERFACE_ATTRIBUTE; -void operator delete[](void *ptr, std::nothrow_t const&) ALIAS("free") - SANITIZER_INTERFACE_ATTRIBUTE; +INTERCEPTOR(void*, pvalloc, uptr size) { + ENSURE_LSAN_INITED; + GET_STACK_TRACE; + uptr PageSize = GetPageSizeCached(); + size = RoundUpTo(size, PageSize); + if (size == 0) { + // pvalloc(0) should allocate one page. + size = PageSize; + } + return Allocate(stack, size, GetPageSizeCached(), kAlwaysClearMemory); +} + +INTERCEPTOR(void, cfree, void *p) ALIAS("free"); + +#define OPERATOR_NEW_BODY \ + ENSURE_LSAN_INITED; \ + GET_STACK_TRACE; \ + return Allocate(stack, size, 1, kAlwaysClearMemory); + +INTERCEPTOR_ATTRIBUTE +void *operator new(uptr size) { OPERATOR_NEW_BODY; } +INTERCEPTOR_ATTRIBUTE +void *operator new[](uptr size) { OPERATOR_NEW_BODY; } +INTERCEPTOR_ATTRIBUTE +void *operator new(uptr size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } +INTERCEPTOR_ATTRIBUTE +void *operator new[](uptr size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } + +#define OPERATOR_DELETE_BODY \ + ENSURE_LSAN_INITED; \ + Deallocate(ptr); + +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr) { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr) { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, std::nothrow_t const &) { + OPERATOR_DELETE_BODY; +} -extern "C" { -void cfree(void *p) ALIAS("free") SANITIZER_INTERFACE_ATTRIBUTE; -void *pvalloc(uptr size) ALIAS("valloc") - SANITIZER_INTERFACE_ATTRIBUTE; // We need this to intercept the __libc_memalign calls that are used to // allocate dynamic TLS space in ld-linux.so. -void *__libc_memalign(uptr alignment, uptr size) - ALIAS("memalign") SANITIZER_INTERFACE_ATTRIBUTE; -} +INTERCEPTOR(void *, __libc_memalign, uptr align, uptr s) ALIAS("memalign"); ///// Thread initialization and finalization. ///// @@ -166,9 +209,6 @@ struct ThreadParam { atomic_uintptr_t tid; }; -// PTHREAD_DESTRUCTOR_ITERATIONS from glibc. -const uptr kPthreadDestructorIterations = 4; - extern "C" void *__lsan_thread_start_func(void *arg) { ThreadParam *p = (ThreadParam*)arg; void* (*callback)(void *arg) = p->callback; @@ -191,13 +231,14 @@ extern "C" void *__lsan_thread_start_func(void *arg) { INTERCEPTOR(int, pthread_create, void *th, void *attr, void *(*callback)(void *), void *param) { - Init(); + ENSURE_LSAN_INITED; + EnsureMainThreadIDIsCorrect(); __sanitizer_pthread_attr_t myattr; if (attr == 0) { pthread_attr_init(&myattr); attr = &myattr; } - AdjustStackSizeLinux(attr, 0); + AdjustStackSizeLinux(attr); int detached = 0; pthread_attr_getdetachstate(attr, &detached); ThreadParam p; @@ -218,7 +259,7 @@ INTERCEPTOR(int, pthread_create, void *th, void *attr, } INTERCEPTOR(int, pthread_join, void *th, void **ret) { - Init(); + ENSURE_LSAN_INITED; int tid = ThreadTid((uptr)th); int res = REAL(pthread_join)(th, ret); if (res == 0) @@ -231,12 +272,14 @@ namespace __lsan { void InitializeInterceptors() { INTERCEPT_FUNCTION(malloc); INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(cfree); INTERCEPT_FUNCTION(calloc); INTERCEPT_FUNCTION(realloc); INTERCEPT_FUNCTION(memalign); INTERCEPT_FUNCTION(posix_memalign); - INTERCEPT_FUNCTION(memalign); + INTERCEPT_FUNCTION(__libc_memalign); INTERCEPT_FUNCTION(valloc); + INTERCEPT_FUNCTION(pvalloc); INTERCEPT_FUNCTION(malloc_usable_size); INTERCEPT_FUNCTION(mallinfo); INTERCEPT_FUNCTION(mallopt); diff --git a/lib/lsan/lsan_preinit.cc b/lib/lsan/lsan_preinit.cc new file mode 100644 index 000000000000..e6639516dc81 --- /dev/null +++ b/lib/lsan/lsan_preinit.cc @@ -0,0 +1,26 @@ +//===-- lsan_preinit.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. +// +// Call __lsan_init at the very early stage of process startup. +//===----------------------------------------------------------------------===// + +#include "lsan.h" + +#ifndef LSAN_USE_PREINIT_ARRAY +#define LSAN_USE_PREINIT_ARRAY 1 +#endif + +#if LSAN_USE_PREINIT_ARRAY && !defined(PIC) + // We force __lsan_init to be called before anyone else by placing it into + // .preinit_array section. + __attribute__((section(".preinit_array"), used)) + void (*__local_lsan_preinit)(void) = __lsan_init; +#endif diff --git a/lib/lsan/lsan_thread.cc b/lib/lsan/lsan_thread.cc index 3e28dee1325a..0f8efc093b56 100644 --- a/lib/lsan/lsan_thread.cc +++ b/lib/lsan/lsan_thread.cc @@ -123,6 +123,11 @@ void ThreadJoin(u32 tid) { thread_registry->JoinThread(tid, /* arg */0); } +void EnsureMainThreadIDIsCorrect() { + if (GetCurrentThread() == 0) + CurrentThreadContext()->os_id = GetTid(); +} + ///// Interface to the common LSan module. ///// bool GetThreadRangesLocked(uptr os_id, uptr *stack_begin, uptr *stack_end, @@ -140,6 +145,10 @@ bool GetThreadRangesLocked(uptr os_id, uptr *stack_begin, uptr *stack_end, return true; } +void ForEachExtraStackRange(uptr os_id, RangeIteratorCallback callback, + void *arg) { +} + void LockThreadRegistry() { thread_registry->Lock(); } diff --git a/lib/lsan/lsan_thread.h b/lib/lsan/lsan_thread.h index b62f04b8eb74..4641b32ed6e6 100644 --- a/lib/lsan/lsan_thread.h +++ b/lib/lsan/lsan_thread.h @@ -47,7 +47,7 @@ u32 ThreadTid(uptr uid); u32 GetCurrentThread(); void SetCurrentThread(u32 tid); ThreadContext *CurrentThreadContext(); - +void EnsureMainThreadIDIsCorrect(); } // namespace __lsan #endif // LSAN_THREAD_H diff --git a/lib/lsan/tests/CMakeLists.txt b/lib/lsan/tests/CMakeLists.txt index 3d97ae96f650..2221e0650237 100644 --- a/lib/lsan/tests/CMakeLists.txt +++ b/lib/lsan/tests/CMakeLists.txt @@ -9,13 +9,17 @@ set(LSAN_TESTS_SRC lsan_dummy_unittest.cc) set(LSAN_TESTS_CFLAGS - ${LSAN_CFLAGS} + ${SANITIZER_COMMON_CFLAGS} ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} - -I${COMPILER_RT_SOURCE_DIR}/lib) + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${LSAN_SRC_DIR}) -add_custom_target(LsanTests) -set_target_properties(LsanTests PROPERTIES - FOLDER "LSan unittests") +set(LSAN_TEST_LINK_FLAGS_COMMON + -lstdc++ -ldl -lpthread -lm) + +add_custom_target(LsanUnitTests) +set_target_properties(LsanUnitTests PROPERTIES + FOLDER "LSan unit tests") # Compile source for the given architecture, using compiler # options in ${ARGN}, and add it to the object list. @@ -25,24 +29,27 @@ macro(lsan_compile obj_list source arch) get_target_flags_for_arch(${arch} TARGET_CFLAGS) clang_compile(${output_obj} ${source} CFLAGS ${ARGN} ${TARGET_CFLAGS} - DEPS gtest) + DEPS gtest ${LSAN_RUNTIME_LIBRARIES}) list(APPEND ${obj_list} ${output_obj}) endmacro() -function(add_lsan_test testname arch) - set(testname_arch ${testname}-${arch}-Test) - get_target_flags_for_arch(${arch} TARGET_LINKFLAGS) - add_unittest(LsanTests ${testname_arch} ${ARGN}) - target_link_libraries(${testname_arch} "clang_rt.lsan-${arch}") - set_target_compile_flags(${testname_arch} ${LSAN_TESTS_CFLAGS}) - set_target_link_flags(${testname_arch} ${TARGET_LINKFLAGS}) +function(add_lsan_test test_suite test_name arch) + get_target_flags_for_arch(${arch} TARGET_LINK_FLAGS) + add_compiler_rt_test(${test_suite} ${test_name} + OBJECTS ${ARGN} + DEPS ${LSAN_RUNTIME_LIBRARIES} ${ARGN} + LINK_FLAGS ${LSAN_TEST_LINK_FLAGS_COMMON} + ${TARGET_LINK_FLAGS}) endfunction() macro(add_lsan_tests_for_arch arch) set(LSAN_TESTS_OBJ) - lsan_compile(LSAN_TESTS_OBJ ${LSAN_TESTS_SRC} ${arch} ${LSAN_TESTS_CFLAGS} - -I${LSAN_SRC_DIR}) - add_lsan_test(Lsan ${arch} ${LSAN_TESTS_OBJ}) + set(LSAN_TEST_SOURCES ${LSAN_TESTS_SRC} + ${COMPILER_RT_GTEST_SOURCE}) + foreach(source ${LSAN_TEST_SOURCES}) + lsan_compile(LSAN_TESTS_OBJ ${source} ${arch} ${LSAN_TESTS_CFLAGS}) + endforeach() + add_lsan_test(LsanUnitTests Lsan-${arch}-Test ${arch} ${LSAN_TESTS_OBJ}) endmacro() # Build tests for 64-bit Linux only. diff --git a/lib/lsan/tests/lsan_dummy_unittest.cc b/lib/lsan/tests/lsan_dummy_unittest.cc index e69de29bb2d1..5468400775a0 100644 --- a/lib/lsan/tests/lsan_dummy_unittest.cc +++ b/lib/lsan/tests/lsan_dummy_unittest.cc @@ -0,0 +1,22 @@ +//===-- lsan_dummy_unittest.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 runtime. +// +//===----------------------------------------------------------------------===// +#include "gtest/gtest.h" + +TEST(LeakSanitizer, EmptyTest) { + // Empty test to suppress LIT warnings about lack of tests. +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/lsan/tests/lsan_testlib.cc b/lib/lsan/tests/lsan_testlib.cc index 363cc14f1941..8db6cf1e2f8c 100644 --- a/lib/lsan/tests/lsan_testlib.cc +++ b/lib/lsan/tests/lsan_testlib.cc @@ -14,12 +14,12 @@ /* Usage: clang++ ../sanitizer_common/sanitizer_*.cc ../interception/interception_*.cc \ lsan*.cc tests/lsan_testlib.cc -I. -I.. -g -ldl -lpthread -fPIC -shared -O2 \ - -o lsan.so + -DLSAN_USE_PREINIT_ARRAY=0 -o lsan.so LD_PRELOAD=./lsan.so /your/app */ #include "lsan.h" __attribute__((constructor)) void constructor() { - __lsan::Init(); + __lsan_init(); } |