[lsan] Add suppression support.

llvm-svn: 185152
This commit is contained in:
Sergey Matveev 2013-06-28 14:38:31 +00:00
parent 7323383bd7
commit 2b19ee3da8
11 changed files with 182 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1 @@
leak:*LSanTestLeakingFunc*

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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 {