diff --git a/compiler-rt/lib/lsan/lsan_common.h b/compiler-rt/lib/lsan/lsan_common.h --- a/compiler-rt/lib/lsan/lsan_common.h +++ b/compiler-rt/lib/lsan/lsan_common.h @@ -105,6 +105,8 @@ void PrintSummary(); void ApplySuppressions(); uptr UnsuppressedLeakCount(); + uptr IndirectUnsuppressedLeakCount(); + void GetSuppressions(InternalMmapVector *suppressed); private: void PrintReportForLeak(uptr index); @@ -132,9 +134,12 @@ // This is used when the OS has a unified callback API for suspending // threads and enumerating roots. struct CheckForLeaksParam { - Frontier frontier; + CheckForLeaksParam(const InternalMmapVector &suppressed_stacks) + : suppressed_stacks(suppressed_stacks) {} + const InternalMmapVector &suppressed_stacks; LeakReport leak_report; bool success = false; + Frontier frontier; }; InternalMmapVector const *GetRootRegions(); diff --git a/compiler-rt/lib/lsan/lsan_common.cpp b/compiler-rt/lib/lsan/lsan_common.cpp --- a/compiler-rt/lib/lsan/lsan_common.cpp +++ b/compiler-rt/lib/lsan/lsan_common.cpp @@ -390,6 +390,24 @@ } } +static void IgnoredSuppressedCb(uptr chunk, void *arg) { + CHECK(arg); + chunk = GetUserBegin(chunk); + LsanMetadata m(chunk); + if (!m.allocated() || m.tag() == kIgnored) + return; + + const InternalMmapVector &suppressed = + *static_cast *>(arg); + uptr idx = InternalLowerBound(suppressed, m.stack_trace_id()); + if (idx >= suppressed.size() || m.stack_trace_id() != suppressed[idx]) + return; + + LOG_POINTERS("Suppressed: chunk %p-%p of size %zu.\n", chunk, + chunk + m.requested_size(), m.requested_size()); + m.set_tag(kIgnored); +} + // ForEachChunk callback. If chunk is marked as ignored, adds its address to // frontier. static void CollectIgnoredCb(uptr chunk, void *arg) { @@ -472,7 +490,11 @@ // Sets the appropriate tag on each chunk. static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads, + const InternalMmapVector &suppressed_stacks, Frontier *frontier) { + if (!suppressed_stacks.empty()) + ForEachChunk(IgnoredSuppressedCb, + const_cast *>(&suppressed_stacks)); ForEachChunk(CollectIgnoredCb, frontier); ProcessGlobalRegions(frontier); ProcessThreads(suspended_threads, frontier); @@ -586,7 +608,8 @@ CHECK(param); CHECK(!param->success); ReportUnsuspendedThreads(suspended_threads); - ClassifyAllChunks(suspended_threads, ¶m->frontier); + ClassifyAllChunks(suspended_threads, param->suppressed_stacks, + ¶m->frontier); ForEachChunk(CollectLeaksCb, ¶m->leak_report); // Clean up for subsequent leak checks. This assumes we did not overwrite any // kIgnored tags. @@ -594,43 +617,69 @@ param->success = true; } -static bool CheckForLeaks() { - if (&__lsan_is_turned_off && __lsan_is_turned_off()) - return false; - EnsureMainThreadIDIsCorrect(); - CheckForLeaksParam param; - LockStuffAndStopTheWorld(CheckForLeaksCallback, ¶m); - - if (!param.success) { - Report("LeakSanitizer has encountered a fatal error.\n"); - Report( - "HINT: For debugging, try setting environment variable " - "LSAN_OPTIONS=verbosity=1:log_threads=1\n"); - Report( - "HINT: LeakSanitizer does not work under ptrace (strace, gdb, etc)\n"); - Die(); - } - param.leak_report.ApplySuppressions(); - uptr unsuppressed_count = param.leak_report.UnsuppressedLeakCount(); - if (unsuppressed_count > 0) { +static bool PrintResults(LeakReport &report) { + uptr unsuppressed_count = report.UnsuppressedLeakCount(); + if (unsuppressed_count) { Decorator d; - Printf("\n" - "=================================================================" - "\n"); + Printf( + "\n" + "=================================================================" + "\n"); Printf("%s", d.Error()); Report("ERROR: LeakSanitizer: detected memory leaks\n"); Printf("%s", d.Default()); - param.leak_report.ReportTopLeaks(flags()->max_leaks); + report.ReportTopLeaks(flags()->max_leaks); } if (common_flags()->print_suppressions) PrintMatchedSuppressions(); if (unsuppressed_count > 0) { - param.leak_report.PrintSummary(); + report.PrintSummary(); return true; } return false; } +static bool CheckForLeaks() { + if (&__lsan_is_turned_off && __lsan_is_turned_off()) + return false; + // Inside LockStuffAndStopTheWorld we can't run symbolizer, so we can't match + // suppressions. However if a stack id was previously suppressed, it should be + // suppressed in future checks as well. + static InternalMmapVector suppressed_stacks; + for (int i = 0;; ++i) { + EnsureMainThreadIDIsCorrect(); + CheckForLeaksParam param(suppressed_stacks); + LockStuffAndStopTheWorld(CheckForLeaksCallback, ¶m); + if (!param.success) { + Report("LeakSanitizer has encountered a fatal error.\n"); + Report( + "HINT: For debugging, try setting environment variable " + "LSAN_OPTIONS=verbosity=1:log_threads=1\n"); + Report( + "HINT: LeakSanitizer does not work under ptrace (strace, gdb, " + "etc)\n"); + Die(); + } + param.leak_report.ApplySuppressions(); + + // No leaks to report or too many iterations, so we done here. + if (i >= 8 || !param.leak_report.UnsuppressedLeakCount()) + return PrintResults(param.leak_report); + + uptr size = suppressed_stacks.size(); + param.leak_report.GetSuppressions(&suppressed_stacks); + SortAndDedup(suppressed_stacks); + // No new suppressed stacks, so rerun will not help here. + if (size == param.suppressed_stacks.size()) + return PrintResults(param.leak_report); + + // We found a new previously unseen suppressed call stack. Rerun to make + // sure it does not hold indirect leaks. + VReport(1, "Rerun with %zu suppressed stacks.", + param.suppressed_stacks.size()); + } +} + static bool has_reported_leaks = false; bool HasReportedLeaks() { return has_reported_leaks; } @@ -801,6 +850,12 @@ } } +void LeakReport::GetSuppressions(InternalMmapVector *suppressed_stacks) { + for (uptr i = 0; i < leaks_.size(); i++) + if (leaks_[i].is_suppressed) + suppressed_stacks->push_back(leaks_[i].stack_trace_id); +} + uptr LeakReport::UnsuppressedLeakCount() { uptr result = 0; for (uptr i = 0; i < leaks_.size(); i++) @@ -808,6 +863,14 @@ return result; } +uptr LeakReport::IndirectUnsuppressedLeakCount() { + uptr result = 0; + for (uptr i = 0; i < leaks_.size(); i++) + if (!leaks_[i].is_suppressed && !leaks_[i].is_directly_leaked) + result++; + return result; +} + } // namespace __lsan #else // CAN_SANITIZE_LEAKS namespace __lsan { diff --git a/compiler-rt/test/lsan/TestCases/suppressions_file.cpp b/compiler-rt/test/lsan/TestCases/suppressions_file.cpp --- a/compiler-rt/test/lsan/TestCases/suppressions_file.cpp +++ b/compiler-rt/test/lsan/TestCases/suppressions_file.cpp @@ -17,15 +17,14 @@ #include #include -void* LSanTestLeakingFunc() { +void *LSanTestLeakingFunc() { void *p = malloc(666); fprintf(stderr, "Test alloc: %p.\n", p); return p; } void LSanTestUnsuppressedLeakingFunc() { - void** p = (void**)LSanTestLeakingFunc(); - // FIXME: This must be suppressed as well. + void **p = (void **)LSanTestLeakingFunc(); *p = malloc(777); fprintf(stderr, "Test alloc: %p.\n", *p); } @@ -38,6 +37,6 @@ } // CHECK: Suppressions used: // CHECK: 1 666 *LSanTestLeakingFunc* -// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 2114 byte(s) leaked in 2 allocation(s) +// CHECK: SUMMARY: {{(Leak|Address)}}Sanitizer: 1337 byte(s) leaked in 1 allocation(s) // NOSUPP: SUMMARY: {{(Leak|Address)}}Sanitizer: 2780 byte(s) leaked in 3 allocation(s).