From 2b19ee3da8164f62a49e2c593986a59169fcb3e8 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Fri, 28 Jun 2013 14:38:31 +0000 Subject: [PATCH] [lsan] Add suppression support. llvm-svn: 185152 --- compiler-rt/lib/lsan/lit_tests/AsanConfig/lit.cfg | 1 + compiler-rt/lib/lsan/lit_tests/LsanConfig/lit.cfg | 2 + .../lit_tests/TestCases/suppressions_default.cc | 29 ++++++ .../lsan/lit_tests/TestCases/suppressions_file.cc | 29 ++++++ .../lit_tests/TestCases/suppressions_file.cc.supp | 1 + compiler-rt/lib/lsan/lit_tests/lit.common.cfg | 4 + compiler-rt/lib/lsan/lsan_common.cc | 113 +++++++++++++++++++-- compiler-rt/lib/lsan/lsan_common.h | 6 ++ .../lib/sanitizer_common/sanitizer_suppressions.cc | 5 +- .../lib/sanitizer_common/sanitizer_suppressions.h | 2 + .../tests/sanitizer_suppressions_test.cc | 3 +- 11 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_default.cc create mode 100644 compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc create mode 100644 compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp diff --git a/compiler-rt/lib/lsan/lit_tests/AsanConfig/lit.cfg b/compiler-rt/lib/lsan/lit_tests/AsanConfig/lit.cfg index 9bf3e9c..eb24244 100644 --- a/compiler-rt/lib/lsan/lit_tests/AsanConfig/lit.cfg +++ b/compiler-rt/lib/lsan/lit_tests/AsanConfig/lit.cfg @@ -24,3 +24,4 @@ config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + clang_lsan_cxxflags + " ")) ) config.environment['ASAN_OPTIONS'] = 'detect_leaks=1' +config.environment['ASAN_SYMBOLIZER_PATH'] = config.llvm_symbolizer_path diff --git a/compiler-rt/lib/lsan/lit_tests/LsanConfig/lit.cfg b/compiler-rt/lib/lsan/lit_tests/LsanConfig/lit.cfg index 2273ca2..bc1d548 100644 --- a/compiler-rt/lib/lsan/lit_tests/LsanConfig/lit.cfg +++ b/compiler-rt/lib/lsan/lit_tests/LsanConfig/lit.cfg @@ -22,3 +22,5 @@ clang_lsan_cxxflags = config.clang_cxxflags + " -fsanitize=leak " config.substitutions.append( ("%clangxx_lsan ", (" " + config.clang + " " + clang_lsan_cxxflags + " ")) ) + +config.environment['LSAN_SYMBOLIZER_PATH'] = config.llvm_symbolizer_path diff --git a/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_default.cc b/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_default.cc new file mode 100644 index 0000000..92bf5a2 --- /dev/null +++ b/compiler-rt/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) diff --git a/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc b/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc new file mode 100644 index 0000000..92bf5a2 --- /dev/null +++ b/compiler-rt/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) diff --git a/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp b/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp new file mode 100644 index 0000000..8d8e560 --- /dev/null +++ b/compiler-rt/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp @@ -0,0 +1 @@ +leak:*LSanTestLeakingFunc* diff --git a/compiler-rt/lib/lsan/lit_tests/lit.common.cfg b/compiler-rt/lib/lsan/lit_tests/lit.common.cfg index dfae8db..cf5ccd5 100644 --- a/compiler-rt/lib/lsan/lit_tests/lit.common.cfg +++ b/compiler-rt/lib/lsan/lit_tests/lit.common.cfg @@ -12,6 +12,10 @@ def get_required_attr(config, attr_name): "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') diff --git a/compiler-rt/lib/lsan/lsan_common.cc b/compiler-rt/lib/lsan/lsan_common.cc index 1f0bf97..ab3ea77 100644 --- a/compiler-rt/lib/lsan/lsan_common.cc +++ b/compiler-rt/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 @@ static void InitializeFlags() { 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 @@ static void InitializeFlags() { 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 @@ 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; @@ -285,6 +310,21 @@ static void PrintLeakedCb(uptr chunk, void *arg) { } } +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 @@ void DoLeakCheck() { 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 @@ 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); } @@ -369,26 +439,33 @@ static bool IsLarger(const Leak &leak1, const Leak &leak2) { 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 @@ 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; } @@ -404,6 +482,21 @@ void LeakReport::PrintSummary() { "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 diff --git a/compiler-rt/lib/lsan/lsan_common.h b/compiler-rt/lib/lsan/lsan_common.h index 0ff596c..74e6a81 100644 --- a/compiler-rt/lib/lsan/lsan_common.h +++ b/compiler-rt/lib/lsan/lsan_common.h @@ -51,6 +51,8 @@ struct Flags { 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 @@ struct Leak { uptr total_size; u32 stack_trace_id; bool is_directly_leaked; + bool is_suppressed; }; // Aggregates leaks by stack trace prefix. @@ -91,6 +94,7 @@ class LeakReport { void PrintLargest(uptr max_leaks); void PrintSummary(); bool IsEmpty() { return leaks_.size() == 0; } + uptr ApplySuppressions(); private: InternalMmapVector leaks_; }; @@ -157,6 +161,8 @@ class LsanMetadata { 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 diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.cc b/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.cc index 2471352..f88020f 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.cc +++ b/compiler-rt/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 @@ void SuppressionContext::Parse(const char *str) { } } 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 @@ void SuppressionContext::Parse(const char *str) { 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) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.h b/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.h index d78da0c..e0b562e 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_suppressions.h @@ -24,6 +24,7 @@ enum SuppressionType { SuppressionMutex, SuppressionThread, SuppressionSignal, + SuppressionLeak, SuppressionTypeCount }; @@ -31,6 +32,7 @@ struct Suppression { SuppressionType type; char *templ; unsigned hit_count; + uptr weight; }; class SuppressionContext { diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc b/compiler-rt/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc index f6ee5ce..75061ac 100644 --- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc @@ -45,8 +45,9 @@ TEST(Suppressions, TypeStrings) { 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 { -- 2.7.4