forked from OSchip/llvm-project
parent
7323383bd7
commit
2b19ee3da8
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
leak:*LSanTestLeakingFunc*
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue