summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Matveev <earthdok@google.com>2013-06-28 14:38:31 +0000
committerSergey Matveev <earthdok@google.com>2013-06-28 14:38:31 +0000
commitb33cfeb6004d3a93e6d35749c14db0190c6c2b4c (patch)
treeda6a9f9272964d2e7bc53a12b66f6a2bc715fabb
parentd50d29ecfafbdbdb033f7d94b7ff88c1fce40452 (diff)
downloadcompiler-rt-b33cfeb6004d3a93e6d35749c14db0190c6c2b4c.tar.gz
compiler-rt-b33cfeb6004d3a93e6d35749c14db0190c6c2b4c.tar.bz2
compiler-rt-b33cfeb6004d3a93e6d35749c14db0190c6c2b4c.tar.xz
[lsan] Add suppression support.
git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@185152 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--lib/lsan/lit_tests/AsanConfig/lit.cfg1
-rw-r--r--lib/lsan/lit_tests/LsanConfig/lit.cfg2
-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/lit.common.cfg4
-rw-r--r--lib/lsan/lsan_common.cc113
-rw-r--r--lib/lsan/lsan_common.h6
-rw-r--r--lib/sanitizer_common/sanitizer_suppressions.cc5
-rw-r--r--lib/sanitizer_common/sanitizer_suppressions.h2
-rw-r--r--lib/sanitizer_common/tests/sanitizer_suppressions_test.cc3
11 files changed, 182 insertions, 13 deletions
diff --git a/lib/lsan/lit_tests/AsanConfig/lit.cfg b/lib/lsan/lit_tests/AsanConfig/lit.cfg
index 9bf3e9cc..eb242442 100644
--- a/lib/lsan/lit_tests/AsanConfig/lit.cfg
+++ b/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/lib/lsan/lit_tests/LsanConfig/lit.cfg b/lib/lsan/lit_tests/LsanConfig/lit.cfg
index 2273ca26..bc1d5481 100644
--- a/lib/lsan/lit_tests/LsanConfig/lit.cfg
+++ b/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/lib/lsan/lit_tests/TestCases/suppressions_default.cc b/lib/lsan/lit_tests/TestCases/suppressions_default.cc
new file mode 100644
index 00000000..92bf5a24
--- /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 %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: LeakSanitizer: 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 00000000..92bf5a24
--- /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 %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: LeakSanitizer: 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 00000000..8d8e560c
--- /dev/null
+++ b/lib/lsan/lit_tests/TestCases/suppressions_file.cc.supp
@@ -0,0 +1 @@
+leak:*LSanTestLeakingFunc*
diff --git a/lib/lsan/lit_tests/lit.common.cfg b/lib/lsan/lit_tests/lit.common.cfg
index dfae8db4..cf5ccd50 100644
--- a/lib/lsan/lit_tests/lit.common.cfg
+++ b/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/lib/lsan/lsan_common.cc b/lib/lsan/lsan_common.cc
index 1f0bf970..ab3ea77e 100644
--- a/lib/lsan/lsan_common.cc
+++ b/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<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("\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<AddressInfo> 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/lib/lsan/lsan_common.h b/lib/lsan/lsan_common.h
index 0ff596ce..74e6a817 100644
--- a/lib/lsan/lsan_common.h
+++ b/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<Leak> 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/lib/sanitizer_common/sanitizer_suppressions.cc b/lib/sanitizer_common/sanitizer_suppressions.cc
index 24713528..f88020fa 100644
--- a/lib/sanitizer_common/sanitizer_suppressions.cc
+++ b/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/lib/sanitizer_common/sanitizer_suppressions.h b/lib/sanitizer_common/sanitizer_suppressions.h
index d78da0c4..e0b562e5 100644
--- a/lib/sanitizer_common/sanitizer_suppressions.h
+++ b/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/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc b/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc
index f6ee5ce8..75061ac4 100644
--- a/lib/sanitizer_common/tests/sanitizer_suppressions_test.cc
+++ b/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 {