aboutsummaryrefslogtreecommitdiff
path: root/lib/lsan
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lsan')
-rw-r--r--lib/lsan/CMakeLists.txt21
-rw-r--r--lib/lsan/Makefile.mk13
-rw-r--r--lib/lsan/lit_tests/AsanConfig/lit.cfg32
-rw-r--r--lib/lsan/lit_tests/AsanConfig/lit.site.cfg.in8
-rw-r--r--lib/lsan/lit_tests/CMakeLists.txt25
-rw-r--r--lib/lsan/lit_tests/LsanConfig/lit.cfg30
-rw-r--r--lib/lsan/lit_tests/LsanConfig/lit.site.cfg.in8
-rw-r--r--lib/lsan/lit_tests/TestCases/SharedLibs/huge_tls_lib_so.cc12
-rw-r--r--lib/lsan/lit_tests/TestCases/SharedLibs/lit.local.cfg (renamed from lib/lsan/lit_tests/SharedLibs/lit.local.cfg)0
-rw-r--r--lib/lsan/lit_tests/TestCases/cleanup_in_tsd_destructor.cc45
-rw-r--r--lib/lsan/lit_tests/TestCases/disabler.cc23
-rw-r--r--lib/lsan/lit_tests/TestCases/disabler_in_tsd_destructor.cc38
-rw-r--r--lib/lsan/lit_tests/TestCases/do_leak_check_override.cc36
-rw-r--r--lib/lsan/lit_tests/TestCases/fork.cc24
-rw-r--r--lib/lsan/lit_tests/TestCases/fork_threaded.cc43
-rw-r--r--lib/lsan/lit_tests/TestCases/high_allocator_contention.cc48
-rw-r--r--lib/lsan/lit_tests/TestCases/ignore_object.cc30
-rw-r--r--lib/lsan/lit_tests/TestCases/ignore_object_errors.cc22
-rw-r--r--lib/lsan/lit_tests/TestCases/large_allocation_leak.cc18
-rw-r--r--lib/lsan/lit_tests/TestCases/leak_check_at_exit.cc19
-rw-r--r--lib/lsan/lit_tests/TestCases/link_turned_off.cc24
-rw-r--r--lib/lsan/lit_tests/TestCases/pointer_to_self.cc18
-rw-r--r--lib/lsan/lit_tests/TestCases/sanity_check_pure_c.c10
-rw-r--r--lib/lsan/lit_tests/TestCases/stale_stack_leak.cc42
-rw-r--r--lib/lsan/lit_tests/TestCases/suppressions_default.cc29
-rw-r--r--lib/lsan/lit_tests/TestCases/suppressions_file.cc29
-rw-r--r--lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp1
-rw-r--r--lib/lsan/lit_tests/TestCases/swapcontext.cc42
-rw-r--r--lib/lsan/lit_tests/TestCases/use_after_return.cc23
-rw-r--r--lib/lsan/lit_tests/TestCases/use_globals_initialized.cc (renamed from lib/lsan/lit_tests/use_globals_initialized.cc)10
-rw-r--r--lib/lsan/lit_tests/TestCases/use_globals_uninitialized.cc21
-rw-r--r--lib/lsan/lit_tests/TestCases/use_registers.cc51
-rw-r--r--lib/lsan/lit_tests/TestCases/use_stacks.cc20
-rw-r--r--lib/lsan/lit_tests/TestCases/use_stacks_threaded.cc36
-rw-r--r--lib/lsan/lit_tests/TestCases/use_tls_dynamic.cc33
-rw-r--r--lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_dynamic.cc37
-rw-r--r--lib/lsan/lit_tests/TestCases/use_tls_pthread_specific_static.cc31
-rw-r--r--lib/lsan/lit_tests/TestCases/use_tls_static.cc21
-rw-r--r--lib/lsan/lit_tests/TestCases/use_unaligned.cc23
-rw-r--r--lib/lsan/lit_tests/Unit/lit.cfg26
-rw-r--r--lib/lsan/lit_tests/Unit/lit.site.cfg.in21
-rw-r--r--lib/lsan/lit_tests/lit.cfg50
-rw-r--r--lib/lsan/lit_tests/lit.common.cfg43
-rw-r--r--lib/lsan/lit_tests/lit.site.cfg.in20
-rw-r--r--lib/lsan/lsan.cc36
-rw-r--r--lib/lsan/lsan.h6
-rw-r--r--lib/lsan/lsan_allocator.cc80
-rw-r--r--lib/lsan/lsan_common.cc449
-rw-r--r--lib/lsan/lsan_common.h120
-rw-r--r--lib/lsan/lsan_common_linux.cc52
-rw-r--r--lib/lsan/lsan_interceptors.cc125
-rw-r--r--lib/lsan/lsan_preinit.cc26
-rw-r--r--lib/lsan/lsan_thread.cc9
-rw-r--r--lib/lsan/lsan_thread.h2
-rw-r--r--lib/lsan/tests/CMakeLists.txt39
-rw-r--r--lib/lsan/tests/lsan_dummy_unittest.cc22
-rw-r--r--lib/lsan/tests/lsan_testlib.cc4
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, &param->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, &param);
+ 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();
}