Index: lib/lsan/lit_tests/AsanConfig/lit.cfg =================================================================== --- lib/lsan/lit_tests/AsanConfig/lit.cfg +++ lib/lsan/lit_tests/AsanConfig/lit.cfg @@ -24,3 +24,4 @@ clang_lsan_cxxflags + " ")) ) config.environment['ASAN_OPTIONS'] = 'detect_leaks=1' +config.environment['ASAN_SYMBOLIZER_PATH'] = config.llvm_symbolizer_path Index: lib/lsan/lit_tests/LsanConfig/lit.cfg =================================================================== --- lib/lsan/lit_tests/LsanConfig/lit.cfg +++ lib/lsan/lit_tests/LsanConfig/lit.cfg @@ -22,3 +22,5 @@ config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + clang_lsan_cxxflags + " ")) ) + +config.environment['LSAN_SYMBOLIZER_PATH'] = config.llvm_symbolizer_path Index: lib/lsan/lit_tests/TestCases/suppressions_default.cc =================================================================== --- /dev/null +++ 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 %t 2>&1 | FileCheck %s + +#include +#include + +#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: LeakSanitizer: 1337 byte(s) leaked in 1 allocation(s) Index: lib/lsan/lit_tests/TestCases/suppressions_file.cc =================================================================== --- /dev/null +++ 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 %t 2>&1 | FileCheck %s + +#include +#include + +#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: LeakSanitizer: 1337 byte(s) leaked in 1 allocation(s) Index: lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp =================================================================== --- /dev/null +++ lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp @@ -0,0 +1 @@ +leak:*LSanTestLeakingFunc* Index: lib/lsan/lit_tests/lit.common.cfg =================================================================== --- lib/lsan/lit_tests/lit.common.cfg +++ lib/lsan/lit_tests/lit.common.cfg @@ -12,6 +12,10 @@ "to lit.site.cfg " % attr_name) return attr_value +# Setup path to external LLVM symbolizer to run LeakSanitizer output tests. +llvm_tools_dir = get_required_attr(config, 'llvm_tools_dir') +config.llvm_symbolizer_path = os.path.join(llvm_tools_dir, "llvm-symbolizer") + # 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') Index: lib/lsan/lsan_common.h =================================================================== --- lib/lsan/lsan_common.h +++ lib/lsan/lsan_common.h @@ -51,6 +51,8 @@ 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). @@ -81,6 +83,7 @@ uptr total_size; u32 stack_trace_id; bool is_directly_leaked; + bool is_suppressed; }; // Aggregates leaks by stack trace prefix. @@ -91,6 +94,7 @@ void PrintLargest(uptr max_leaks); void PrintSummary(); bool IsEmpty() { return leaks_.size() == 0; } + uptr ApplySuppressions(); private: InternalMmapVector leaks_; }; @@ -157,6 +161,8 @@ extern "C" { int __lsan_is_turned_off() SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; +const char *__lsan_default_suppressions() SANITIZER_WEAK_ATTRIBUTE + SANITIZER_INTERFACE_ATTRIBUTE; } // extern "C" #endif // LSAN_COMMON_H Index: lib/lsan/lsan_common.cc =================================================================== --- lib/lsan/lsan_common.cc +++ lib/lsan/lsan_common.cc @@ -16,9 +16,11 @@ #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" #if CAN_SANITIZE_LEAKS namespace __lsan { @@ -38,6 +40,7 @@ f->resolution = 0; f->max_leaks = 0; f->exitcode = 23; + f->suppressions=""; f->use_registers = true; f->use_globals = true; f->use_stacks = true; @@ -63,17 +66,39 @@ 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(); + InitializeSuppressions(); InitializePlatformSpecificModules(); } 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__ @@ -158,7 +183,7 @@ // 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; @@ -285,6 +310,21 @@ } } +static void PrintMatchedSuppressions() { + InternalMmapVector 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(matched[i]->hit_count), + matched[i]->weight, matched[i]->templ); + Printf("%s\n\n", line); +} + static void PrintLeaked() { Printf("\n"); Printf("Reporting individual objects:\n"); @@ -330,16 +370,46 @@ Die(); } if (!param.leak_report.IsEmpty()) { + uptr unsuppressed_count = param.leak_report.ApplySuppressions(); + if (!unsuppressed_count) return; Printf("\n=================================================================" "\n"); Report("ERROR: LeakSanitizer: detected memory leaks\n"); param.leak_report.PrintLargest(flags()->max_leaks); + PrintMatchedSuppressions(); param.leak_report.PrintSummary(); if (flags()->exitcode) internal__exit(flags()->exitcode); } } +static Suppression *GetSuppressionForAddr(uptr addr) { + static const uptr kMaxAddrFrames = 16; + InternalScopedBuffer addr_frames(kMaxAddrFrames); + for (uptr i = 0; i < kMaxAddrFrames; i++) new (&addr_frames[i]) AddressInfo(); + uptr addr_frames_num = __sanitizer::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. ///// // A hard limit on the number of distinct leaks, to avoid quadratic complexity @@ -361,7 +431,7 @@ } 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); } @@ -369,26 +439,33 @@ return leak1.total_size > leak2.total_size; } -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 %zu leaks encountered will be " "reported.\n", kMaxLeaksConsidered); - if (max_leaks > 0 && max_leaks < leaks_.size()) - Printf("The %zu largest leak(s):\n", max_leaks); + + 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(), IsLarger); - max_leaks = max_leaks > 0 ? Min(max_leaks, leaks_.size()) : leaks_.size(); - for (uptr i = 0; i < max_leaks; i++) { + uptr leaks_printed = 0; + for (uptr i = 0; i < leaks_.size(); i++) { + if (leaks_[i].is_suppressed) continue; 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); PrintStackTraceById(leaks_[i].stack_trace_id); Printf("\n"); + leaks_printed = 0; + if (leaks_printed == num_leaks_to_print) break; } - if (max_leaks < leaks_.size()) { - uptr remaining = leaks_.size() - max_leaks; + if (leaks_printed < unsuppressed_count) { + uptr remaining = unsuppressed_count - leaks_printed; Printf("Omitting %zu more leak(s).\n", remaining); } } @@ -397,6 +474,7 @@ 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; } @@ -404,6 +482,21 @@ "SUMMARY: LeakSanitizer: %zu byte(s) leaked in %zu allocation(s).\n\n", bytes, allocations); } + +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 Index: lib/sanitizer_common/sanitizer_suppressions.h =================================================================== --- lib/sanitizer_common/sanitizer_suppressions.h +++ lib/sanitizer_common/sanitizer_suppressions.h @@ -24,6 +24,7 @@ SuppressionMutex, SuppressionThread, SuppressionSignal, + SuppressionLeak, SuppressionTypeCount }; @@ -31,6 +32,7 @@ SuppressionType type; char *templ; unsigned hit_count; + uptr weight; }; class SuppressionContext { Index: lib/sanitizer_common/sanitizer_suppressions.cc =================================================================== --- lib/sanitizer_common/sanitizer_suppressions.cc +++ lib/sanitizer_common/sanitizer_suppressions.cc @@ -20,7 +20,7 @@ namespace __sanitizer { static const char *const kTypeStrings[SuppressionTypeCount] = { - "none", "race", "mutex", "thread", "signal" + "none", "race", "mutex", "thread", "signal", "leak" }; bool TemplateMatch(char *templ, const char *str) { @@ -95,7 +95,7 @@ } } if (type == SuppressionTypeCount) { - Printf("%s: failed to parse suppressions file\n", SanitizerToolName); + Printf("%s: failed to parse suppressions\n", SanitizerToolName); Die(); } Suppression s; @@ -104,6 +104,7 @@ internal_memcpy(s.templ, line, end2 - line); s.templ[end2 - line] = 0; s.hit_count = 0; + s.weight = 0; suppressions_.push_back(s); } if (end[0] == 0) Index: lib/sanitizer_common/tests/sanitizer_suppressions_test.cc =================================================================== --- lib/sanitizer_common/tests/sanitizer_suppressions_test.cc +++ lib/sanitizer_common/tests/sanitizer_suppressions_test.cc @@ -45,8 +45,9 @@ CHECK(!internal_strcmp(SuppressionTypeString(SuppressionMutex), "mutex")); CHECK(!internal_strcmp(SuppressionTypeString(SuppressionThread), "thread")); CHECK(!internal_strcmp(SuppressionTypeString(SuppressionSignal), "signal")); + CHECK(!internal_strcmp(SuppressionTypeString(SuppressionLeak), "leak")); // Ensure this test is up-to-date when suppression types are added. - CHECK_EQ(SuppressionTypeCount, 5); + CHECK_EQ(SuppressionTypeCount, 6); } class SuppressionContextTest : public ::testing::Test {