168d75effSDimitry Andric //=-- lsan_common.cpp -----------------------------------------------------===// 268d75effSDimitry Andric // 368d75effSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 468d75effSDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 568d75effSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 668d75effSDimitry Andric // 768d75effSDimitry Andric //===----------------------------------------------------------------------===// 868d75effSDimitry Andric // 968d75effSDimitry Andric // This file is a part of LeakSanitizer. 1068d75effSDimitry Andric // Implementation of common leak checking functionality. 1168d75effSDimitry Andric // 1268d75effSDimitry Andric //===----------------------------------------------------------------------===// 1368d75effSDimitry Andric 1468d75effSDimitry Andric #include "lsan_common.h" 1568d75effSDimitry Andric 1668d75effSDimitry Andric #include "sanitizer_common/sanitizer_common.h" 1768d75effSDimitry Andric #include "sanitizer_common/sanitizer_flag_parser.h" 1868d75effSDimitry Andric #include "sanitizer_common/sanitizer_flags.h" 1968d75effSDimitry Andric #include "sanitizer_common/sanitizer_placement_new.h" 2068d75effSDimitry Andric #include "sanitizer_common/sanitizer_procmaps.h" 2168d75effSDimitry Andric #include "sanitizer_common/sanitizer_report_decorator.h" 2268d75effSDimitry Andric #include "sanitizer_common/sanitizer_stackdepot.h" 2368d75effSDimitry Andric #include "sanitizer_common/sanitizer_stacktrace.h" 2468d75effSDimitry Andric #include "sanitizer_common/sanitizer_suppressions.h" 2568d75effSDimitry Andric #include "sanitizer_common/sanitizer_thread_registry.h" 2668d75effSDimitry Andric #include "sanitizer_common/sanitizer_tls_get_addr.h" 2768d75effSDimitry Andric 2868d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 2968d75effSDimitry Andric namespace __lsan { 3068d75effSDimitry Andric 3168d75effSDimitry Andric // This mutex is used to prevent races between DoLeakCheck and IgnoreObject, and 3268d75effSDimitry Andric // also to protect the global list of root regions. 3368d75effSDimitry Andric BlockingMutex global_mutex(LINKER_INITIALIZED); 3468d75effSDimitry Andric 3568d75effSDimitry Andric Flags lsan_flags; 3668d75effSDimitry Andric 375ffd83dbSDimitry Andric 3868d75effSDimitry Andric void DisableCounterUnderflow() { 3968d75effSDimitry Andric if (common_flags()->detect_leaks) { 4068d75effSDimitry Andric Report("Unmatched call to __lsan_enable().\n"); 4168d75effSDimitry Andric Die(); 4268d75effSDimitry Andric } 4368d75effSDimitry Andric } 4468d75effSDimitry Andric 4568d75effSDimitry Andric void Flags::SetDefaults() { 4668d75effSDimitry Andric #define LSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; 4768d75effSDimitry Andric #include "lsan_flags.inc" 4868d75effSDimitry Andric #undef LSAN_FLAG 4968d75effSDimitry Andric } 5068d75effSDimitry Andric 5168d75effSDimitry Andric void RegisterLsanFlags(FlagParser *parser, Flags *f) { 5268d75effSDimitry Andric #define LSAN_FLAG(Type, Name, DefaultValue, Description) \ 5368d75effSDimitry Andric RegisterFlag(parser, #Name, Description, &f->Name); 5468d75effSDimitry Andric #include "lsan_flags.inc" 5568d75effSDimitry Andric #undef LSAN_FLAG 5668d75effSDimitry Andric } 5768d75effSDimitry Andric 5868d75effSDimitry Andric #define LOG_POINTERS(...) \ 5968d75effSDimitry Andric do { \ 6068d75effSDimitry Andric if (flags()->log_pointers) Report(__VA_ARGS__); \ 6168d75effSDimitry Andric } while (0) 6268d75effSDimitry Andric 6368d75effSDimitry Andric #define LOG_THREADS(...) \ 6468d75effSDimitry Andric do { \ 6568d75effSDimitry Andric if (flags()->log_threads) Report(__VA_ARGS__); \ 6668d75effSDimitry Andric } while (0) 6768d75effSDimitry Andric 68e8d8bef9SDimitry Andric class LeakSuppressionContext { 69e8d8bef9SDimitry Andric bool parsed = false; 70e8d8bef9SDimitry Andric SuppressionContext context; 71e8d8bef9SDimitry Andric bool suppressed_stacks_sorted = true; 72e8d8bef9SDimitry Andric InternalMmapVector<u32> suppressed_stacks; 73e8d8bef9SDimitry Andric 74e8d8bef9SDimitry Andric Suppression *GetSuppressionForAddr(uptr addr); 75e8d8bef9SDimitry Andric void LazyInit(); 76e8d8bef9SDimitry Andric 77e8d8bef9SDimitry Andric public: 78e8d8bef9SDimitry Andric LeakSuppressionContext(const char *supprression_types[], 79e8d8bef9SDimitry Andric int suppression_types_num) 80e8d8bef9SDimitry Andric : context(supprression_types, suppression_types_num) {} 81e8d8bef9SDimitry Andric 82e8d8bef9SDimitry Andric Suppression *GetSuppressionForStack(u32 stack_trace_id); 83e8d8bef9SDimitry Andric 84e8d8bef9SDimitry Andric const InternalMmapVector<u32> &GetSortedSuppressedStacks() { 85e8d8bef9SDimitry Andric if (!suppressed_stacks_sorted) { 86e8d8bef9SDimitry Andric suppressed_stacks_sorted = true; 87e8d8bef9SDimitry Andric SortAndDedup(suppressed_stacks); 88e8d8bef9SDimitry Andric } 89e8d8bef9SDimitry Andric return suppressed_stacks; 90e8d8bef9SDimitry Andric } 91e8d8bef9SDimitry Andric void PrintMatchedSuppressions(); 92e8d8bef9SDimitry Andric }; 93e8d8bef9SDimitry Andric 94e8d8bef9SDimitry Andric ALIGNED(64) static char suppression_placeholder[sizeof(LeakSuppressionContext)]; 95e8d8bef9SDimitry Andric static LeakSuppressionContext *suppression_ctx = nullptr; 9668d75effSDimitry Andric static const char kSuppressionLeak[] = "leak"; 9768d75effSDimitry Andric static const char *kSuppressionTypes[] = { kSuppressionLeak }; 9868d75effSDimitry Andric static const char kStdSuppressions[] = 9968d75effSDimitry Andric #if SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT 10068d75effSDimitry Andric // For more details refer to the SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT 10168d75effSDimitry Andric // definition. 10268d75effSDimitry Andric "leak:*pthread_exit*\n" 10368d75effSDimitry Andric #endif // SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT 10468d75effSDimitry Andric #if SANITIZER_MAC 10568d75effSDimitry Andric // For Darwin and os_log/os_trace: https://reviews.llvm.org/D35173 10668d75effSDimitry Andric "leak:*_os_trace*\n" 10768d75effSDimitry Andric #endif 10868d75effSDimitry Andric // TLS leak in some glibc versions, described in 10968d75effSDimitry Andric // https://sourceware.org/bugzilla/show_bug.cgi?id=12650. 11068d75effSDimitry Andric "leak:*tls_get_addr*\n"; 11168d75effSDimitry Andric 11268d75effSDimitry Andric void InitializeSuppressions() { 11368d75effSDimitry Andric CHECK_EQ(nullptr, suppression_ctx); 11468d75effSDimitry Andric suppression_ctx = new (suppression_placeholder) 115e8d8bef9SDimitry Andric LeakSuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes)); 11668d75effSDimitry Andric } 11768d75effSDimitry Andric 118e8d8bef9SDimitry Andric void LeakSuppressionContext::LazyInit() { 119e8d8bef9SDimitry Andric if (!parsed) { 120e8d8bef9SDimitry Andric parsed = true; 121e8d8bef9SDimitry Andric context.ParseFromFile(flags()->suppressions); 122e8d8bef9SDimitry Andric if (&__lsan_default_suppressions) 123e8d8bef9SDimitry Andric context.Parse(__lsan_default_suppressions()); 124e8d8bef9SDimitry Andric context.Parse(kStdSuppressions); 125e8d8bef9SDimitry Andric } 126e8d8bef9SDimitry Andric } 127e8d8bef9SDimitry Andric 128e8d8bef9SDimitry Andric static LeakSuppressionContext *GetSuppressionContext() { 12968d75effSDimitry Andric CHECK(suppression_ctx); 13068d75effSDimitry Andric return suppression_ctx; 13168d75effSDimitry Andric } 13268d75effSDimitry Andric 13368d75effSDimitry Andric static InternalMmapVector<RootRegion> *root_regions; 13468d75effSDimitry Andric 13568d75effSDimitry Andric InternalMmapVector<RootRegion> const *GetRootRegions() { return root_regions; } 13668d75effSDimitry Andric 13768d75effSDimitry Andric void InitializeRootRegions() { 13868d75effSDimitry Andric CHECK(!root_regions); 13968d75effSDimitry Andric ALIGNED(64) static char placeholder[sizeof(InternalMmapVector<RootRegion>)]; 14068d75effSDimitry Andric root_regions = new (placeholder) InternalMmapVector<RootRegion>(); 14168d75effSDimitry Andric } 14268d75effSDimitry Andric 14368d75effSDimitry Andric void InitCommonLsan() { 14468d75effSDimitry Andric InitializeRootRegions(); 14568d75effSDimitry Andric if (common_flags()->detect_leaks) { 14668d75effSDimitry Andric // Initialization which can fail or print warnings should only be done if 14768d75effSDimitry Andric // LSan is actually enabled. 14868d75effSDimitry Andric InitializeSuppressions(); 14968d75effSDimitry Andric InitializePlatformSpecificModules(); 15068d75effSDimitry Andric } 15168d75effSDimitry Andric } 15268d75effSDimitry Andric 15368d75effSDimitry Andric class Decorator: public __sanitizer::SanitizerCommonDecorator { 15468d75effSDimitry Andric public: 15568d75effSDimitry Andric Decorator() : SanitizerCommonDecorator() { } 15668d75effSDimitry Andric const char *Error() { return Red(); } 15768d75effSDimitry Andric const char *Leak() { return Blue(); } 15868d75effSDimitry Andric }; 15968d75effSDimitry Andric 16068d75effSDimitry Andric static inline bool CanBeAHeapPointer(uptr p) { 16168d75effSDimitry Andric // Since our heap is located in mmap-ed memory, we can assume a sensible lower 16268d75effSDimitry Andric // bound on heap addresses. 16368d75effSDimitry Andric const uptr kMinAddress = 4 * 4096; 16468d75effSDimitry Andric if (p < kMinAddress) return false; 16568d75effSDimitry Andric #if defined(__x86_64__) 16668d75effSDimitry Andric // Accept only canonical form user-space addresses. 16768d75effSDimitry Andric return ((p >> 47) == 0); 16868d75effSDimitry Andric #elif defined(__mips64) 16968d75effSDimitry Andric return ((p >> 40) == 0); 17068d75effSDimitry Andric #elif defined(__aarch64__) 17168d75effSDimitry Andric unsigned runtimeVMA = 17268d75effSDimitry Andric (MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1); 17368d75effSDimitry Andric return ((p >> runtimeVMA) == 0); 17468d75effSDimitry Andric #else 17568d75effSDimitry Andric return true; 17668d75effSDimitry Andric #endif 17768d75effSDimitry Andric } 17868d75effSDimitry Andric 17968d75effSDimitry Andric // Scans the memory range, looking for byte patterns that point into allocator 18068d75effSDimitry Andric // chunks. Marks those chunks with |tag| and adds them to |frontier|. 18168d75effSDimitry Andric // There are two usage modes for this function: finding reachable chunks 18268d75effSDimitry Andric // (|tag| = kReachable) and finding indirectly leaked chunks 18368d75effSDimitry Andric // (|tag| = kIndirectlyLeaked). In the second case, there's no flood fill, 18468d75effSDimitry Andric // so |frontier| = 0. 18568d75effSDimitry Andric void ScanRangeForPointers(uptr begin, uptr end, 18668d75effSDimitry Andric Frontier *frontier, 18768d75effSDimitry Andric const char *region_type, ChunkTag tag) { 18868d75effSDimitry Andric CHECK(tag == kReachable || tag == kIndirectlyLeaked); 18968d75effSDimitry Andric const uptr alignment = flags()->pointer_alignment(); 19068d75effSDimitry Andric LOG_POINTERS("Scanning %s range %p-%p.\n", region_type, begin, end); 19168d75effSDimitry Andric uptr pp = begin; 19268d75effSDimitry Andric if (pp % alignment) 19368d75effSDimitry Andric pp = pp + alignment - pp % alignment; 19468d75effSDimitry Andric for (; pp + sizeof(void *) <= end; pp += alignment) { 19568d75effSDimitry Andric void *p = *reinterpret_cast<void **>(pp); 19668d75effSDimitry Andric if (!CanBeAHeapPointer(reinterpret_cast<uptr>(p))) continue; 19768d75effSDimitry Andric uptr chunk = PointsIntoChunk(p); 19868d75effSDimitry Andric if (!chunk) continue; 19968d75effSDimitry Andric // Pointers to self don't count. This matters when tag == kIndirectlyLeaked. 20068d75effSDimitry Andric if (chunk == begin) continue; 20168d75effSDimitry Andric LsanMetadata m(chunk); 20268d75effSDimitry Andric if (m.tag() == kReachable || m.tag() == kIgnored) continue; 20368d75effSDimitry Andric 20468d75effSDimitry Andric // Do this check relatively late so we can log only the interesting cases. 20568d75effSDimitry Andric if (!flags()->use_poisoned && WordIsPoisoned(pp)) { 20668d75effSDimitry Andric LOG_POINTERS( 20768d75effSDimitry Andric "%p is poisoned: ignoring %p pointing into chunk %p-%p of size " 20868d75effSDimitry Andric "%zu.\n", 20968d75effSDimitry Andric pp, p, chunk, chunk + m.requested_size(), m.requested_size()); 21068d75effSDimitry Andric continue; 21168d75effSDimitry Andric } 21268d75effSDimitry Andric 21368d75effSDimitry Andric m.set_tag(tag); 21468d75effSDimitry Andric LOG_POINTERS("%p: found %p pointing into chunk %p-%p of size %zu.\n", pp, p, 21568d75effSDimitry Andric chunk, chunk + m.requested_size(), m.requested_size()); 21668d75effSDimitry Andric if (frontier) 21768d75effSDimitry Andric frontier->push_back(chunk); 21868d75effSDimitry Andric } 21968d75effSDimitry Andric } 22068d75effSDimitry Andric 22168d75effSDimitry Andric // Scans a global range for pointers 22268d75effSDimitry Andric void ScanGlobalRange(uptr begin, uptr end, Frontier *frontier) { 22368d75effSDimitry Andric uptr allocator_begin = 0, allocator_end = 0; 22468d75effSDimitry Andric GetAllocatorGlobalRange(&allocator_begin, &allocator_end); 22568d75effSDimitry Andric if (begin <= allocator_begin && allocator_begin < end) { 22668d75effSDimitry Andric CHECK_LE(allocator_begin, allocator_end); 22768d75effSDimitry Andric CHECK_LE(allocator_end, end); 22868d75effSDimitry Andric if (begin < allocator_begin) 22968d75effSDimitry Andric ScanRangeForPointers(begin, allocator_begin, frontier, "GLOBAL", 23068d75effSDimitry Andric kReachable); 23168d75effSDimitry Andric if (allocator_end < end) 23268d75effSDimitry Andric ScanRangeForPointers(allocator_end, end, frontier, "GLOBAL", kReachable); 23368d75effSDimitry Andric } else { 23468d75effSDimitry Andric ScanRangeForPointers(begin, end, frontier, "GLOBAL", kReachable); 23568d75effSDimitry Andric } 23668d75effSDimitry Andric } 23768d75effSDimitry Andric 23868d75effSDimitry Andric void ForEachExtraStackRangeCb(uptr begin, uptr end, void* arg) { 23968d75effSDimitry Andric Frontier *frontier = reinterpret_cast<Frontier *>(arg); 24068d75effSDimitry Andric ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable); 24168d75effSDimitry Andric } 24268d75effSDimitry Andric 2435ffd83dbSDimitry Andric #if SANITIZER_FUCHSIA 2445ffd83dbSDimitry Andric 2455ffd83dbSDimitry Andric // Fuchsia handles all threads together with its own callback. 2465ffd83dbSDimitry Andric static void ProcessThreads(SuspendedThreadsList const &, Frontier *) {} 2475ffd83dbSDimitry Andric 2485ffd83dbSDimitry Andric #else 2495ffd83dbSDimitry Andric 250e8d8bef9SDimitry Andric #if SANITIZER_ANDROID 251e8d8bef9SDimitry Andric // FIXME: Move this out into *libcdep.cpp 252e8d8bef9SDimitry Andric extern "C" SANITIZER_WEAK_ATTRIBUTE void __libc_iterate_dynamic_tls( 253e8d8bef9SDimitry Andric pid_t, void (*cb)(void *, void *, uptr, void *), void *); 254e8d8bef9SDimitry Andric #endif 255e8d8bef9SDimitry Andric 256e8d8bef9SDimitry Andric static void ProcessThreadRegistry(Frontier *frontier) { 257e8d8bef9SDimitry Andric InternalMmapVector<uptr> ptrs; 258e8d8bef9SDimitry Andric GetThreadRegistryLocked()->RunCallbackForEachThreadLocked( 259e8d8bef9SDimitry Andric GetAdditionalThreadContextPtrs, &ptrs); 260e8d8bef9SDimitry Andric 261e8d8bef9SDimitry Andric for (uptr i = 0; i < ptrs.size(); ++i) { 262e8d8bef9SDimitry Andric void *ptr = reinterpret_cast<void *>(ptrs[i]); 263e8d8bef9SDimitry Andric uptr chunk = PointsIntoChunk(ptr); 264e8d8bef9SDimitry Andric if (!chunk) 265e8d8bef9SDimitry Andric continue; 266e8d8bef9SDimitry Andric LsanMetadata m(chunk); 267e8d8bef9SDimitry Andric if (!m.allocated()) 268e8d8bef9SDimitry Andric continue; 269e8d8bef9SDimitry Andric 270e8d8bef9SDimitry Andric // Mark as reachable and add to frontier. 271e8d8bef9SDimitry Andric LOG_POINTERS("Treating pointer %p from ThreadContext as reachable\n", ptr); 272e8d8bef9SDimitry Andric m.set_tag(kReachable); 273e8d8bef9SDimitry Andric frontier->push_back(chunk); 274e8d8bef9SDimitry Andric } 275e8d8bef9SDimitry Andric } 276e8d8bef9SDimitry Andric 27768d75effSDimitry Andric // Scans thread data (stacks and TLS) for heap pointers. 27868d75effSDimitry Andric static void ProcessThreads(SuspendedThreadsList const &suspended_threads, 27968d75effSDimitry Andric Frontier *frontier) { 280e8d8bef9SDimitry Andric InternalMmapVector<uptr> registers; 28168d75effSDimitry Andric for (uptr i = 0; i < suspended_threads.ThreadCount(); i++) { 28268d75effSDimitry Andric tid_t os_id = static_cast<tid_t>(suspended_threads.GetThreadID(i)); 28368d75effSDimitry Andric LOG_THREADS("Processing thread %d.\n", os_id); 28468d75effSDimitry Andric uptr stack_begin, stack_end, tls_begin, tls_end, cache_begin, cache_end; 28568d75effSDimitry Andric DTLS *dtls; 28668d75effSDimitry Andric bool thread_found = GetThreadRangesLocked(os_id, &stack_begin, &stack_end, 28768d75effSDimitry Andric &tls_begin, &tls_end, 28868d75effSDimitry Andric &cache_begin, &cache_end, &dtls); 28968d75effSDimitry Andric if (!thread_found) { 29068d75effSDimitry Andric // If a thread can't be found in the thread registry, it's probably in the 29168d75effSDimitry Andric // process of destruction. Log this event and move on. 29268d75effSDimitry Andric LOG_THREADS("Thread %d not found in registry.\n", os_id); 29368d75effSDimitry Andric continue; 29468d75effSDimitry Andric } 29568d75effSDimitry Andric uptr sp; 29668d75effSDimitry Andric PtraceRegistersStatus have_registers = 297e8d8bef9SDimitry Andric suspended_threads.GetRegistersAndSP(i, ®isters, &sp); 29868d75effSDimitry Andric if (have_registers != REGISTERS_AVAILABLE) { 29968d75effSDimitry Andric Report("Unable to get registers from thread %d.\n", os_id); 30068d75effSDimitry Andric // If unable to get SP, consider the entire stack to be reachable unless 30168d75effSDimitry Andric // GetRegistersAndSP failed with ESRCH. 30268d75effSDimitry Andric if (have_registers == REGISTERS_UNAVAILABLE_FATAL) continue; 30368d75effSDimitry Andric sp = stack_begin; 30468d75effSDimitry Andric } 30568d75effSDimitry Andric 306e8d8bef9SDimitry Andric if (flags()->use_registers && have_registers) { 307e8d8bef9SDimitry Andric uptr registers_begin = reinterpret_cast<uptr>(registers.data()); 308e8d8bef9SDimitry Andric uptr registers_end = 309e8d8bef9SDimitry Andric reinterpret_cast<uptr>(registers.data() + registers.size()); 31068d75effSDimitry Andric ScanRangeForPointers(registers_begin, registers_end, frontier, 31168d75effSDimitry Andric "REGISTERS", kReachable); 312e8d8bef9SDimitry Andric } 31368d75effSDimitry Andric 31468d75effSDimitry Andric if (flags()->use_stacks) { 31568d75effSDimitry Andric LOG_THREADS("Stack at %p-%p (SP = %p).\n", stack_begin, stack_end, sp); 31668d75effSDimitry Andric if (sp < stack_begin || sp >= stack_end) { 31768d75effSDimitry Andric // SP is outside the recorded stack range (e.g. the thread is running a 31868d75effSDimitry Andric // signal handler on alternate stack, or swapcontext was used). 31968d75effSDimitry Andric // Again, consider the entire stack range to be reachable. 32068d75effSDimitry Andric LOG_THREADS("WARNING: stack pointer not in stack range.\n"); 32168d75effSDimitry Andric uptr page_size = GetPageSizeCached(); 32268d75effSDimitry Andric int skipped = 0; 32368d75effSDimitry Andric while (stack_begin < stack_end && 32468d75effSDimitry Andric !IsAccessibleMemoryRange(stack_begin, 1)) { 32568d75effSDimitry Andric skipped++; 32668d75effSDimitry Andric stack_begin += page_size; 32768d75effSDimitry Andric } 32868d75effSDimitry Andric LOG_THREADS("Skipped %d guard page(s) to obtain stack %p-%p.\n", 32968d75effSDimitry Andric skipped, stack_begin, stack_end); 33068d75effSDimitry Andric } else { 33168d75effSDimitry Andric // Shrink the stack range to ignore out-of-scope values. 33268d75effSDimitry Andric stack_begin = sp; 33368d75effSDimitry Andric } 33468d75effSDimitry Andric ScanRangeForPointers(stack_begin, stack_end, frontier, "STACK", 33568d75effSDimitry Andric kReachable); 33668d75effSDimitry Andric ForEachExtraStackRange(os_id, ForEachExtraStackRangeCb, frontier); 33768d75effSDimitry Andric } 33868d75effSDimitry Andric 33968d75effSDimitry Andric if (flags()->use_tls) { 34068d75effSDimitry Andric if (tls_begin) { 34168d75effSDimitry Andric LOG_THREADS("TLS at %p-%p.\n", tls_begin, tls_end); 34268d75effSDimitry Andric // If the tls and cache ranges don't overlap, scan full tls range, 34368d75effSDimitry Andric // otherwise, only scan the non-overlapping portions 34468d75effSDimitry Andric if (cache_begin == cache_end || tls_end < cache_begin || 34568d75effSDimitry Andric tls_begin > cache_end) { 34668d75effSDimitry Andric ScanRangeForPointers(tls_begin, tls_end, frontier, "TLS", kReachable); 34768d75effSDimitry Andric } else { 34868d75effSDimitry Andric if (tls_begin < cache_begin) 34968d75effSDimitry Andric ScanRangeForPointers(tls_begin, cache_begin, frontier, "TLS", 35068d75effSDimitry Andric kReachable); 35168d75effSDimitry Andric if (tls_end > cache_end) 35268d75effSDimitry Andric ScanRangeForPointers(cache_end, tls_end, frontier, "TLS", 35368d75effSDimitry Andric kReachable); 35468d75effSDimitry Andric } 35568d75effSDimitry Andric } 356e8d8bef9SDimitry Andric #if SANITIZER_ANDROID 357e8d8bef9SDimitry Andric auto *cb = +[](void *dtls_begin, void *dtls_end, uptr /*dso_idd*/, 358e8d8bef9SDimitry Andric void *arg) -> void { 359e8d8bef9SDimitry Andric ScanRangeForPointers(reinterpret_cast<uptr>(dtls_begin), 360e8d8bef9SDimitry Andric reinterpret_cast<uptr>(dtls_end), 361e8d8bef9SDimitry Andric reinterpret_cast<Frontier *>(arg), "DTLS", 362e8d8bef9SDimitry Andric kReachable); 363e8d8bef9SDimitry Andric }; 364e8d8bef9SDimitry Andric 365e8d8bef9SDimitry Andric // FIXME: There might be a race-condition here (and in Bionic) if the 366e8d8bef9SDimitry Andric // thread is suspended in the middle of updating its DTLS. IOWs, we 367e8d8bef9SDimitry Andric // could scan already freed memory. (probably fine for now) 368e8d8bef9SDimitry Andric __libc_iterate_dynamic_tls(os_id, cb, frontier); 369e8d8bef9SDimitry Andric #else 37068d75effSDimitry Andric if (dtls && !DTLSInDestruction(dtls)) { 371e8d8bef9SDimitry Andric ForEachDVT(dtls, [&](const DTLS::DTV &dtv, int id) { 372e8d8bef9SDimitry Andric uptr dtls_beg = dtv.beg; 373e8d8bef9SDimitry Andric uptr dtls_end = dtls_beg + dtv.size; 37468d75effSDimitry Andric if (dtls_beg < dtls_end) { 375e8d8bef9SDimitry Andric LOG_THREADS("DTLS %zu at %p-%p.\n", id, dtls_beg, dtls_end); 37668d75effSDimitry Andric ScanRangeForPointers(dtls_beg, dtls_end, frontier, "DTLS", 37768d75effSDimitry Andric kReachable); 37868d75effSDimitry Andric } 379e8d8bef9SDimitry Andric }); 38068d75effSDimitry Andric } else { 38168d75effSDimitry Andric // We are handling a thread with DTLS under destruction. Log about 38268d75effSDimitry Andric // this and continue. 38368d75effSDimitry Andric LOG_THREADS("Thread %d has DTLS under destruction.\n", os_id); 38468d75effSDimitry Andric } 385e8d8bef9SDimitry Andric #endif 38668d75effSDimitry Andric } 38768d75effSDimitry Andric } 388e8d8bef9SDimitry Andric 389e8d8bef9SDimitry Andric // Add pointers reachable from ThreadContexts 390e8d8bef9SDimitry Andric ProcessThreadRegistry(frontier); 39168d75effSDimitry Andric } 39268d75effSDimitry Andric 3935ffd83dbSDimitry Andric #endif // SANITIZER_FUCHSIA 3945ffd83dbSDimitry Andric 39568d75effSDimitry Andric void ScanRootRegion(Frontier *frontier, const RootRegion &root_region, 39668d75effSDimitry Andric uptr region_begin, uptr region_end, bool is_readable) { 39768d75effSDimitry Andric uptr intersection_begin = Max(root_region.begin, region_begin); 39868d75effSDimitry Andric uptr intersection_end = Min(region_end, root_region.begin + root_region.size); 39968d75effSDimitry Andric if (intersection_begin >= intersection_end) return; 40068d75effSDimitry Andric LOG_POINTERS("Root region %p-%p intersects with mapped region %p-%p (%s)\n", 40168d75effSDimitry Andric root_region.begin, root_region.begin + root_region.size, 40268d75effSDimitry Andric region_begin, region_end, 40368d75effSDimitry Andric is_readable ? "readable" : "unreadable"); 40468d75effSDimitry Andric if (is_readable) 40568d75effSDimitry Andric ScanRangeForPointers(intersection_begin, intersection_end, frontier, "ROOT", 40668d75effSDimitry Andric kReachable); 40768d75effSDimitry Andric } 40868d75effSDimitry Andric 40968d75effSDimitry Andric static void ProcessRootRegion(Frontier *frontier, 41068d75effSDimitry Andric const RootRegion &root_region) { 41168d75effSDimitry Andric MemoryMappingLayout proc_maps(/*cache_enabled*/ true); 41268d75effSDimitry Andric MemoryMappedSegment segment; 41368d75effSDimitry Andric while (proc_maps.Next(&segment)) { 41468d75effSDimitry Andric ScanRootRegion(frontier, root_region, segment.start, segment.end, 41568d75effSDimitry Andric segment.IsReadable()); 41668d75effSDimitry Andric } 41768d75effSDimitry Andric } 41868d75effSDimitry Andric 41968d75effSDimitry Andric // Scans root regions for heap pointers. 42068d75effSDimitry Andric static void ProcessRootRegions(Frontier *frontier) { 42168d75effSDimitry Andric if (!flags()->use_root_regions) return; 42268d75effSDimitry Andric CHECK(root_regions); 42368d75effSDimitry Andric for (uptr i = 0; i < root_regions->size(); i++) { 42468d75effSDimitry Andric ProcessRootRegion(frontier, (*root_regions)[i]); 42568d75effSDimitry Andric } 42668d75effSDimitry Andric } 42768d75effSDimitry Andric 42868d75effSDimitry Andric static void FloodFillTag(Frontier *frontier, ChunkTag tag) { 42968d75effSDimitry Andric while (frontier->size()) { 43068d75effSDimitry Andric uptr next_chunk = frontier->back(); 43168d75effSDimitry Andric frontier->pop_back(); 43268d75effSDimitry Andric LsanMetadata m(next_chunk); 43368d75effSDimitry Andric ScanRangeForPointers(next_chunk, next_chunk + m.requested_size(), frontier, 43468d75effSDimitry Andric "HEAP", tag); 43568d75effSDimitry Andric } 43668d75effSDimitry Andric } 43768d75effSDimitry Andric 43868d75effSDimitry Andric // ForEachChunk callback. If the chunk is marked as leaked, marks all chunks 43968d75effSDimitry Andric // which are reachable from it as indirectly leaked. 44068d75effSDimitry Andric static void MarkIndirectlyLeakedCb(uptr chunk, void *arg) { 44168d75effSDimitry Andric chunk = GetUserBegin(chunk); 44268d75effSDimitry Andric LsanMetadata m(chunk); 44368d75effSDimitry Andric if (m.allocated() && m.tag() != kReachable) { 44468d75effSDimitry Andric ScanRangeForPointers(chunk, chunk + m.requested_size(), 44568d75effSDimitry Andric /* frontier */ nullptr, "HEAP", kIndirectlyLeaked); 44668d75effSDimitry Andric } 44768d75effSDimitry Andric } 44868d75effSDimitry Andric 449e8d8bef9SDimitry Andric static void IgnoredSuppressedCb(uptr chunk, void *arg) { 450e8d8bef9SDimitry Andric CHECK(arg); 451e8d8bef9SDimitry Andric chunk = GetUserBegin(chunk); 452e8d8bef9SDimitry Andric LsanMetadata m(chunk); 453e8d8bef9SDimitry Andric if (!m.allocated() || m.tag() == kIgnored) 454e8d8bef9SDimitry Andric return; 455e8d8bef9SDimitry Andric 456e8d8bef9SDimitry Andric const InternalMmapVector<u32> &suppressed = 457e8d8bef9SDimitry Andric *static_cast<const InternalMmapVector<u32> *>(arg); 458e8d8bef9SDimitry Andric uptr idx = InternalLowerBound(suppressed, m.stack_trace_id()); 459e8d8bef9SDimitry Andric if (idx >= suppressed.size() || m.stack_trace_id() != suppressed[idx]) 460e8d8bef9SDimitry Andric return; 461e8d8bef9SDimitry Andric 462e8d8bef9SDimitry Andric LOG_POINTERS("Suppressed: chunk %p-%p of size %zu.\n", chunk, 463e8d8bef9SDimitry Andric chunk + m.requested_size(), m.requested_size()); 464e8d8bef9SDimitry Andric m.set_tag(kIgnored); 465e8d8bef9SDimitry Andric } 466e8d8bef9SDimitry Andric 46768d75effSDimitry Andric // ForEachChunk callback. If chunk is marked as ignored, adds its address to 46868d75effSDimitry Andric // frontier. 46968d75effSDimitry Andric static void CollectIgnoredCb(uptr chunk, void *arg) { 47068d75effSDimitry Andric CHECK(arg); 47168d75effSDimitry Andric chunk = GetUserBegin(chunk); 47268d75effSDimitry Andric LsanMetadata m(chunk); 47368d75effSDimitry Andric if (m.allocated() && m.tag() == kIgnored) { 47468d75effSDimitry Andric LOG_POINTERS("Ignored: chunk %p-%p of size %zu.\n", 47568d75effSDimitry Andric chunk, chunk + m.requested_size(), m.requested_size()); 47668d75effSDimitry Andric reinterpret_cast<Frontier *>(arg)->push_back(chunk); 47768d75effSDimitry Andric } 47868d75effSDimitry Andric } 47968d75effSDimitry Andric 48068d75effSDimitry Andric static uptr GetCallerPC(u32 stack_id, StackDepotReverseMap *map) { 48168d75effSDimitry Andric CHECK(stack_id); 48268d75effSDimitry Andric StackTrace stack = map->Get(stack_id); 48368d75effSDimitry Andric // The top frame is our malloc/calloc/etc. The next frame is the caller. 48468d75effSDimitry Andric if (stack.size >= 2) 48568d75effSDimitry Andric return stack.trace[1]; 48668d75effSDimitry Andric return 0; 48768d75effSDimitry Andric } 48868d75effSDimitry Andric 48968d75effSDimitry Andric struct InvalidPCParam { 49068d75effSDimitry Andric Frontier *frontier; 49168d75effSDimitry Andric StackDepotReverseMap *stack_depot_reverse_map; 49268d75effSDimitry Andric bool skip_linker_allocations; 49368d75effSDimitry Andric }; 49468d75effSDimitry Andric 49568d75effSDimitry Andric // ForEachChunk callback. If the caller pc is invalid or is within the linker, 49668d75effSDimitry Andric // mark as reachable. Called by ProcessPlatformSpecificAllocations. 49768d75effSDimitry Andric static void MarkInvalidPCCb(uptr chunk, void *arg) { 49868d75effSDimitry Andric CHECK(arg); 49968d75effSDimitry Andric InvalidPCParam *param = reinterpret_cast<InvalidPCParam *>(arg); 50068d75effSDimitry Andric chunk = GetUserBegin(chunk); 50168d75effSDimitry Andric LsanMetadata m(chunk); 50268d75effSDimitry Andric if (m.allocated() && m.tag() != kReachable && m.tag() != kIgnored) { 50368d75effSDimitry Andric u32 stack_id = m.stack_trace_id(); 50468d75effSDimitry Andric uptr caller_pc = 0; 50568d75effSDimitry Andric if (stack_id > 0) 50668d75effSDimitry Andric caller_pc = GetCallerPC(stack_id, param->stack_depot_reverse_map); 50768d75effSDimitry Andric // If caller_pc is unknown, this chunk may be allocated in a coroutine. Mark 50868d75effSDimitry Andric // it as reachable, as we can't properly report its allocation stack anyway. 50968d75effSDimitry Andric if (caller_pc == 0 || (param->skip_linker_allocations && 51068d75effSDimitry Andric GetLinker()->containsAddress(caller_pc))) { 51168d75effSDimitry Andric m.set_tag(kReachable); 51268d75effSDimitry Andric param->frontier->push_back(chunk); 51368d75effSDimitry Andric } 51468d75effSDimitry Andric } 51568d75effSDimitry Andric } 51668d75effSDimitry Andric 51768d75effSDimitry Andric // On Linux, treats all chunks allocated from ld-linux.so as reachable, which 51868d75effSDimitry Andric // covers dynamically allocated TLS blocks, internal dynamic loader's loaded 51968d75effSDimitry Andric // modules accounting etc. 52068d75effSDimitry Andric // Dynamic TLS blocks contain the TLS variables of dynamically loaded modules. 52168d75effSDimitry Andric // They are allocated with a __libc_memalign() call in allocate_and_init() 52268d75effSDimitry Andric // (elf/dl-tls.c). Glibc won't tell us the address ranges occupied by those 52368d75effSDimitry Andric // blocks, but we can make sure they come from our own allocator by intercepting 52468d75effSDimitry Andric // __libc_memalign(). On top of that, there is no easy way to reach them. Their 52568d75effSDimitry Andric // addresses are stored in a dynamically allocated array (the DTV) which is 52668d75effSDimitry Andric // referenced from the static TLS. Unfortunately, we can't just rely on the DTV 52768d75effSDimitry Andric // being reachable from the static TLS, and the dynamic TLS being reachable from 52868d75effSDimitry Andric // the DTV. This is because the initial DTV is allocated before our interception 52968d75effSDimitry Andric // mechanism kicks in, and thus we don't recognize it as allocated memory. We 53068d75effSDimitry Andric // can't special-case it either, since we don't know its size. 53168d75effSDimitry Andric // Our solution is to include in the root set all allocations made from 53268d75effSDimitry Andric // ld-linux.so (which is where allocate_and_init() is implemented). This is 53368d75effSDimitry Andric // guaranteed to include all dynamic TLS blocks (and possibly other allocations 53468d75effSDimitry Andric // which we don't care about). 53568d75effSDimitry Andric // On all other platforms, this simply checks to ensure that the caller pc is 53668d75effSDimitry Andric // valid before reporting chunks as leaked. 53768d75effSDimitry Andric void ProcessPC(Frontier *frontier) { 53868d75effSDimitry Andric StackDepotReverseMap stack_depot_reverse_map; 53968d75effSDimitry Andric InvalidPCParam arg; 54068d75effSDimitry Andric arg.frontier = frontier; 54168d75effSDimitry Andric arg.stack_depot_reverse_map = &stack_depot_reverse_map; 54268d75effSDimitry Andric arg.skip_linker_allocations = 54368d75effSDimitry Andric flags()->use_tls && flags()->use_ld_allocations && GetLinker() != nullptr; 54468d75effSDimitry Andric ForEachChunk(MarkInvalidPCCb, &arg); 54568d75effSDimitry Andric } 54668d75effSDimitry Andric 54768d75effSDimitry Andric // Sets the appropriate tag on each chunk. 5485ffd83dbSDimitry Andric static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads, 5495ffd83dbSDimitry Andric Frontier *frontier) { 550e8d8bef9SDimitry Andric const InternalMmapVector<u32> &suppressed_stacks = 551e8d8bef9SDimitry Andric GetSuppressionContext()->GetSortedSuppressedStacks(); 552e8d8bef9SDimitry Andric if (!suppressed_stacks.empty()) { 553e8d8bef9SDimitry Andric ForEachChunk(IgnoredSuppressedCb, 554e8d8bef9SDimitry Andric const_cast<InternalMmapVector<u32> *>(&suppressed_stacks)); 555e8d8bef9SDimitry Andric } 5565ffd83dbSDimitry Andric ForEachChunk(CollectIgnoredCb, frontier); 5575ffd83dbSDimitry Andric ProcessGlobalRegions(frontier); 5585ffd83dbSDimitry Andric ProcessThreads(suspended_threads, frontier); 5595ffd83dbSDimitry Andric ProcessRootRegions(frontier); 5605ffd83dbSDimitry Andric FloodFillTag(frontier, kReachable); 56168d75effSDimitry Andric 5625ffd83dbSDimitry Andric CHECK_EQ(0, frontier->size()); 5635ffd83dbSDimitry Andric ProcessPC(frontier); 56468d75effSDimitry Andric 56568d75effSDimitry Andric // The check here is relatively expensive, so we do this in a separate flood 56668d75effSDimitry Andric // fill. That way we can skip the check for chunks that are reachable 56768d75effSDimitry Andric // otherwise. 56868d75effSDimitry Andric LOG_POINTERS("Processing platform-specific allocations.\n"); 5695ffd83dbSDimitry Andric ProcessPlatformSpecificAllocations(frontier); 5705ffd83dbSDimitry Andric FloodFillTag(frontier, kReachable); 57168d75effSDimitry Andric 57268d75effSDimitry Andric // Iterate over leaked chunks and mark those that are reachable from other 57368d75effSDimitry Andric // leaked chunks. 57468d75effSDimitry Andric LOG_POINTERS("Scanning leaked chunks.\n"); 57568d75effSDimitry Andric ForEachChunk(MarkIndirectlyLeakedCb, nullptr); 57668d75effSDimitry Andric } 57768d75effSDimitry Andric 57868d75effSDimitry Andric // ForEachChunk callback. Resets the tags to pre-leak-check state. 57968d75effSDimitry Andric static void ResetTagsCb(uptr chunk, void *arg) { 58068d75effSDimitry Andric (void)arg; 58168d75effSDimitry Andric chunk = GetUserBegin(chunk); 58268d75effSDimitry Andric LsanMetadata m(chunk); 58368d75effSDimitry Andric if (m.allocated() && m.tag() != kIgnored) 58468d75effSDimitry Andric m.set_tag(kDirectlyLeaked); 58568d75effSDimitry Andric } 58668d75effSDimitry Andric 58768d75effSDimitry Andric static void PrintStackTraceById(u32 stack_trace_id) { 58868d75effSDimitry Andric CHECK(stack_trace_id); 58968d75effSDimitry Andric StackDepotGet(stack_trace_id).Print(); 59068d75effSDimitry Andric } 59168d75effSDimitry Andric 59268d75effSDimitry Andric // ForEachChunk callback. Aggregates information about unreachable chunks into 59368d75effSDimitry Andric // a LeakReport. 59468d75effSDimitry Andric static void CollectLeaksCb(uptr chunk, void *arg) { 59568d75effSDimitry Andric CHECK(arg); 59668d75effSDimitry Andric LeakReport *leak_report = reinterpret_cast<LeakReport *>(arg); 59768d75effSDimitry Andric chunk = GetUserBegin(chunk); 59868d75effSDimitry Andric LsanMetadata m(chunk); 59968d75effSDimitry Andric if (!m.allocated()) return; 60068d75effSDimitry Andric if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked) { 60168d75effSDimitry Andric u32 resolution = flags()->resolution; 60268d75effSDimitry Andric u32 stack_trace_id = 0; 60368d75effSDimitry Andric if (resolution > 0) { 60468d75effSDimitry Andric StackTrace stack = StackDepotGet(m.stack_trace_id()); 60568d75effSDimitry Andric stack.size = Min(stack.size, resolution); 60668d75effSDimitry Andric stack_trace_id = StackDepotPut(stack); 60768d75effSDimitry Andric } else { 60868d75effSDimitry Andric stack_trace_id = m.stack_trace_id(); 60968d75effSDimitry Andric } 61068d75effSDimitry Andric leak_report->AddLeakedChunk(chunk, stack_trace_id, m.requested_size(), 61168d75effSDimitry Andric m.tag()); 61268d75effSDimitry Andric } 61368d75effSDimitry Andric } 61468d75effSDimitry Andric 615e8d8bef9SDimitry Andric void LeakSuppressionContext::PrintMatchedSuppressions() { 61668d75effSDimitry Andric InternalMmapVector<Suppression *> matched; 617e8d8bef9SDimitry Andric context.GetMatched(&matched); 61868d75effSDimitry Andric if (!matched.size()) 61968d75effSDimitry Andric return; 62068d75effSDimitry Andric const char *line = "-----------------------------------------------------"; 62168d75effSDimitry Andric Printf("%s\n", line); 62268d75effSDimitry Andric Printf("Suppressions used:\n"); 62368d75effSDimitry Andric Printf(" count bytes template\n"); 624e8d8bef9SDimitry Andric for (uptr i = 0; i < matched.size(); i++) { 625e8d8bef9SDimitry Andric Printf("%7zu %10zu %s\n", 626e8d8bef9SDimitry Andric static_cast<uptr>(atomic_load_relaxed(&matched[i]->hit_count)), 627e8d8bef9SDimitry Andric matched[i]->weight, matched[i]->templ); 628e8d8bef9SDimitry Andric } 62968d75effSDimitry Andric Printf("%s\n\n", line); 63068d75effSDimitry Andric } 63168d75effSDimitry Andric 63268d75effSDimitry Andric static void ReportIfNotSuspended(ThreadContextBase *tctx, void *arg) { 63368d75effSDimitry Andric const InternalMmapVector<tid_t> &suspended_threads = 63468d75effSDimitry Andric *(const InternalMmapVector<tid_t> *)arg; 63568d75effSDimitry Andric if (tctx->status == ThreadStatusRunning) { 636e8d8bef9SDimitry Andric uptr i = InternalLowerBound(suspended_threads, tctx->os_id); 63768d75effSDimitry Andric if (i >= suspended_threads.size() || suspended_threads[i] != tctx->os_id) 63868d75effSDimitry Andric Report("Running thread %d was not suspended. False leaks are possible.\n", 63968d75effSDimitry Andric tctx->os_id); 64068d75effSDimitry Andric } 64168d75effSDimitry Andric } 64268d75effSDimitry Andric 6435ffd83dbSDimitry Andric #if SANITIZER_FUCHSIA 6445ffd83dbSDimitry Andric 6455ffd83dbSDimitry Andric // Fuchsia provides a libc interface that guarantees all threads are 6465ffd83dbSDimitry Andric // covered, and SuspendedThreadList is never really used. 6475ffd83dbSDimitry Andric static void ReportUnsuspendedThreads(const SuspendedThreadsList &) {} 6485ffd83dbSDimitry Andric 6495ffd83dbSDimitry Andric #else // !SANITIZER_FUCHSIA 6505ffd83dbSDimitry Andric 65168d75effSDimitry Andric static void ReportUnsuspendedThreads( 65268d75effSDimitry Andric const SuspendedThreadsList &suspended_threads) { 65368d75effSDimitry Andric InternalMmapVector<tid_t> threads(suspended_threads.ThreadCount()); 65468d75effSDimitry Andric for (uptr i = 0; i < suspended_threads.ThreadCount(); ++i) 65568d75effSDimitry Andric threads[i] = suspended_threads.GetThreadID(i); 65668d75effSDimitry Andric 65768d75effSDimitry Andric Sort(threads.data(), threads.size()); 65868d75effSDimitry Andric 65968d75effSDimitry Andric GetThreadRegistryLocked()->RunCallbackForEachThreadLocked( 66068d75effSDimitry Andric &ReportIfNotSuspended, &threads); 66168d75effSDimitry Andric } 66268d75effSDimitry Andric 6635ffd83dbSDimitry Andric #endif // !SANITIZER_FUCHSIA 6645ffd83dbSDimitry Andric 66568d75effSDimitry Andric static void CheckForLeaksCallback(const SuspendedThreadsList &suspended_threads, 66668d75effSDimitry Andric void *arg) { 66768d75effSDimitry Andric CheckForLeaksParam *param = reinterpret_cast<CheckForLeaksParam *>(arg); 66868d75effSDimitry Andric CHECK(param); 66968d75effSDimitry Andric CHECK(!param->success); 67068d75effSDimitry Andric ReportUnsuspendedThreads(suspended_threads); 6715ffd83dbSDimitry Andric ClassifyAllChunks(suspended_threads, ¶m->frontier); 67268d75effSDimitry Andric ForEachChunk(CollectLeaksCb, ¶m->leak_report); 67368d75effSDimitry Andric // Clean up for subsequent leak checks. This assumes we did not overwrite any 67468d75effSDimitry Andric // kIgnored tags. 67568d75effSDimitry Andric ForEachChunk(ResetTagsCb, nullptr); 67668d75effSDimitry Andric param->success = true; 67768d75effSDimitry Andric } 67868d75effSDimitry Andric 679e8d8bef9SDimitry Andric static bool PrintResults(LeakReport &report) { 680e8d8bef9SDimitry Andric uptr unsuppressed_count = report.UnsuppressedLeakCount(); 681e8d8bef9SDimitry Andric if (unsuppressed_count) { 682e8d8bef9SDimitry Andric Decorator d; 683e8d8bef9SDimitry Andric Printf( 684e8d8bef9SDimitry Andric "\n" 685e8d8bef9SDimitry Andric "=================================================================" 686e8d8bef9SDimitry Andric "\n"); 687e8d8bef9SDimitry Andric Printf("%s", d.Error()); 688e8d8bef9SDimitry Andric Report("ERROR: LeakSanitizer: detected memory leaks\n"); 689e8d8bef9SDimitry Andric Printf("%s", d.Default()); 690e8d8bef9SDimitry Andric report.ReportTopLeaks(flags()->max_leaks); 691e8d8bef9SDimitry Andric } 692e8d8bef9SDimitry Andric if (common_flags()->print_suppressions) 693e8d8bef9SDimitry Andric GetSuppressionContext()->PrintMatchedSuppressions(); 694e8d8bef9SDimitry Andric if (unsuppressed_count > 0) { 695e8d8bef9SDimitry Andric report.PrintSummary(); 696e8d8bef9SDimitry Andric return true; 697e8d8bef9SDimitry Andric } 698e8d8bef9SDimitry Andric return false; 699e8d8bef9SDimitry Andric } 700e8d8bef9SDimitry Andric 70168d75effSDimitry Andric static bool CheckForLeaks() { 70268d75effSDimitry Andric if (&__lsan_is_turned_off && __lsan_is_turned_off()) 70368d75effSDimitry Andric return false; 704e8d8bef9SDimitry Andric // Inside LockStuffAndStopTheWorld we can't run symbolizer, so we can't match 705e8d8bef9SDimitry Andric // suppressions. However if a stack id was previously suppressed, it should be 706e8d8bef9SDimitry Andric // suppressed in future checks as well. 707e8d8bef9SDimitry Andric for (int i = 0;; ++i) { 70868d75effSDimitry Andric EnsureMainThreadIDIsCorrect(); 70968d75effSDimitry Andric CheckForLeaksParam param; 71068d75effSDimitry Andric LockStuffAndStopTheWorld(CheckForLeaksCallback, ¶m); 71168d75effSDimitry Andric if (!param.success) { 71268d75effSDimitry Andric Report("LeakSanitizer has encountered a fatal error.\n"); 71368d75effSDimitry Andric Report( 71468d75effSDimitry Andric "HINT: For debugging, try setting environment variable " 71568d75effSDimitry Andric "LSAN_OPTIONS=verbosity=1:log_threads=1\n"); 71668d75effSDimitry Andric Report( 717e8d8bef9SDimitry Andric "HINT: LeakSanitizer does not work under ptrace (strace, gdb, " 718e8d8bef9SDimitry Andric "etc)\n"); 71968d75effSDimitry Andric Die(); 72068d75effSDimitry Andric } 721e8d8bef9SDimitry Andric // No new suppressions stacks, so rerun will not help and we can report. 722e8d8bef9SDimitry Andric if (!param.leak_report.ApplySuppressions()) 723e8d8bef9SDimitry Andric return PrintResults(param.leak_report); 724e8d8bef9SDimitry Andric 725e8d8bef9SDimitry Andric // No indirect leaks to report, so we are done here. 726e8d8bef9SDimitry Andric if (!param.leak_report.IndirectUnsuppressedLeakCount()) 727e8d8bef9SDimitry Andric return PrintResults(param.leak_report); 728e8d8bef9SDimitry Andric 729e8d8bef9SDimitry Andric if (i >= 8) { 730e8d8bef9SDimitry Andric Report("WARNING: LeakSanitizer gave up on indirect leaks suppression.\n"); 731e8d8bef9SDimitry Andric return PrintResults(param.leak_report); 73268d75effSDimitry Andric } 733e8d8bef9SDimitry Andric 734e8d8bef9SDimitry Andric // We found a new previously unseen suppressed call stack. Rerun to make 735e8d8bef9SDimitry Andric // sure it does not hold indirect leaks. 736e8d8bef9SDimitry Andric VReport(1, "Rerun with %zu suppressed stacks.", 737e8d8bef9SDimitry Andric GetSuppressionContext()->GetSortedSuppressedStacks().size()); 73868d75effSDimitry Andric } 73968d75effSDimitry Andric } 74068d75effSDimitry Andric 74168d75effSDimitry Andric static bool has_reported_leaks = false; 74268d75effSDimitry Andric bool HasReportedLeaks() { return has_reported_leaks; } 74368d75effSDimitry Andric 74468d75effSDimitry Andric void DoLeakCheck() { 74568d75effSDimitry Andric BlockingMutexLock l(&global_mutex); 74668d75effSDimitry Andric static bool already_done; 74768d75effSDimitry Andric if (already_done) return; 74868d75effSDimitry Andric already_done = true; 74968d75effSDimitry Andric has_reported_leaks = CheckForLeaks(); 75068d75effSDimitry Andric if (has_reported_leaks) HandleLeaks(); 75168d75effSDimitry Andric } 75268d75effSDimitry Andric 75368d75effSDimitry Andric static int DoRecoverableLeakCheck() { 75468d75effSDimitry Andric BlockingMutexLock l(&global_mutex); 75568d75effSDimitry Andric bool have_leaks = CheckForLeaks(); 75668d75effSDimitry Andric return have_leaks ? 1 : 0; 75768d75effSDimitry Andric } 75868d75effSDimitry Andric 75968d75effSDimitry Andric void DoRecoverableLeakCheckVoid() { DoRecoverableLeakCheck(); } 76068d75effSDimitry Andric 761e8d8bef9SDimitry Andric Suppression *LeakSuppressionContext::GetSuppressionForAddr(uptr addr) { 76268d75effSDimitry Andric Suppression *s = nullptr; 76368d75effSDimitry Andric 76468d75effSDimitry Andric // Suppress by module name. 76568d75effSDimitry Andric if (const char *module_name = 76668d75effSDimitry Andric Symbolizer::GetOrInit()->GetModuleNameForPc(addr)) 767e8d8bef9SDimitry Andric if (context.Match(module_name, kSuppressionLeak, &s)) 76868d75effSDimitry Andric return s; 76968d75effSDimitry Andric 77068d75effSDimitry Andric // Suppress by file or function name. 77168d75effSDimitry Andric SymbolizedStack *frames = Symbolizer::GetOrInit()->SymbolizePC(addr); 77268d75effSDimitry Andric for (SymbolizedStack *cur = frames; cur; cur = cur->next) { 773e8d8bef9SDimitry Andric if (context.Match(cur->info.function, kSuppressionLeak, &s) || 774e8d8bef9SDimitry Andric context.Match(cur->info.file, kSuppressionLeak, &s)) { 77568d75effSDimitry Andric break; 77668d75effSDimitry Andric } 77768d75effSDimitry Andric } 77868d75effSDimitry Andric frames->ClearAll(); 77968d75effSDimitry Andric return s; 78068d75effSDimitry Andric } 78168d75effSDimitry Andric 782e8d8bef9SDimitry Andric Suppression *LeakSuppressionContext::GetSuppressionForStack( 783e8d8bef9SDimitry Andric u32 stack_trace_id) { 784e8d8bef9SDimitry Andric LazyInit(); 78568d75effSDimitry Andric StackTrace stack = StackDepotGet(stack_trace_id); 78668d75effSDimitry Andric for (uptr i = 0; i < stack.size; i++) { 78768d75effSDimitry Andric Suppression *s = GetSuppressionForAddr( 78868d75effSDimitry Andric StackTrace::GetPreviousInstructionPc(stack.trace[i])); 789e8d8bef9SDimitry Andric if (s) { 790e8d8bef9SDimitry Andric suppressed_stacks_sorted = false; 791e8d8bef9SDimitry Andric suppressed_stacks.push_back(stack_trace_id); 792e8d8bef9SDimitry Andric return s; 793e8d8bef9SDimitry Andric } 79468d75effSDimitry Andric } 79568d75effSDimitry Andric return nullptr; 79668d75effSDimitry Andric } 79768d75effSDimitry Andric 79868d75effSDimitry Andric ///// LeakReport implementation. ///// 79968d75effSDimitry Andric 80068d75effSDimitry Andric // A hard limit on the number of distinct leaks, to avoid quadratic complexity 80168d75effSDimitry Andric // in LeakReport::AddLeakedChunk(). We don't expect to ever see this many leaks 80268d75effSDimitry Andric // in real-world applications. 80368d75effSDimitry Andric // FIXME: Get rid of this limit by changing the implementation of LeakReport to 80468d75effSDimitry Andric // use a hash table. 80568d75effSDimitry Andric const uptr kMaxLeaksConsidered = 5000; 80668d75effSDimitry Andric 80768d75effSDimitry Andric void LeakReport::AddLeakedChunk(uptr chunk, u32 stack_trace_id, 80868d75effSDimitry Andric uptr leaked_size, ChunkTag tag) { 80968d75effSDimitry Andric CHECK(tag == kDirectlyLeaked || tag == kIndirectlyLeaked); 81068d75effSDimitry Andric bool is_directly_leaked = (tag == kDirectlyLeaked); 81168d75effSDimitry Andric uptr i; 81268d75effSDimitry Andric for (i = 0; i < leaks_.size(); i++) { 81368d75effSDimitry Andric if (leaks_[i].stack_trace_id == stack_trace_id && 81468d75effSDimitry Andric leaks_[i].is_directly_leaked == is_directly_leaked) { 81568d75effSDimitry Andric leaks_[i].hit_count++; 81668d75effSDimitry Andric leaks_[i].total_size += leaked_size; 81768d75effSDimitry Andric break; 81868d75effSDimitry Andric } 81968d75effSDimitry Andric } 82068d75effSDimitry Andric if (i == leaks_.size()) { 82168d75effSDimitry Andric if (leaks_.size() == kMaxLeaksConsidered) return; 82268d75effSDimitry Andric Leak leak = { next_id_++, /* hit_count */ 1, leaked_size, stack_trace_id, 82368d75effSDimitry Andric is_directly_leaked, /* is_suppressed */ false }; 82468d75effSDimitry Andric leaks_.push_back(leak); 82568d75effSDimitry Andric } 82668d75effSDimitry Andric if (flags()->report_objects) { 82768d75effSDimitry Andric LeakedObject obj = {leaks_[i].id, chunk, leaked_size}; 82868d75effSDimitry Andric leaked_objects_.push_back(obj); 82968d75effSDimitry Andric } 83068d75effSDimitry Andric } 83168d75effSDimitry Andric 83268d75effSDimitry Andric static bool LeakComparator(const Leak &leak1, const Leak &leak2) { 83368d75effSDimitry Andric if (leak1.is_directly_leaked == leak2.is_directly_leaked) 83468d75effSDimitry Andric return leak1.total_size > leak2.total_size; 83568d75effSDimitry Andric else 83668d75effSDimitry Andric return leak1.is_directly_leaked; 83768d75effSDimitry Andric } 83868d75effSDimitry Andric 83968d75effSDimitry Andric void LeakReport::ReportTopLeaks(uptr num_leaks_to_report) { 84068d75effSDimitry Andric CHECK(leaks_.size() <= kMaxLeaksConsidered); 84168d75effSDimitry Andric Printf("\n"); 84268d75effSDimitry Andric if (leaks_.size() == kMaxLeaksConsidered) 84368d75effSDimitry Andric Printf("Too many leaks! Only the first %zu leaks encountered will be " 84468d75effSDimitry Andric "reported.\n", 84568d75effSDimitry Andric kMaxLeaksConsidered); 84668d75effSDimitry Andric 84768d75effSDimitry Andric uptr unsuppressed_count = UnsuppressedLeakCount(); 84868d75effSDimitry Andric if (num_leaks_to_report > 0 && num_leaks_to_report < unsuppressed_count) 84968d75effSDimitry Andric Printf("The %zu top leak(s):\n", num_leaks_to_report); 85068d75effSDimitry Andric Sort(leaks_.data(), leaks_.size(), &LeakComparator); 85168d75effSDimitry Andric uptr leaks_reported = 0; 85268d75effSDimitry Andric for (uptr i = 0; i < leaks_.size(); i++) { 85368d75effSDimitry Andric if (leaks_[i].is_suppressed) continue; 85468d75effSDimitry Andric PrintReportForLeak(i); 85568d75effSDimitry Andric leaks_reported++; 85668d75effSDimitry Andric if (leaks_reported == num_leaks_to_report) break; 85768d75effSDimitry Andric } 85868d75effSDimitry Andric if (leaks_reported < unsuppressed_count) { 85968d75effSDimitry Andric uptr remaining = unsuppressed_count - leaks_reported; 86068d75effSDimitry Andric Printf("Omitting %zu more leak(s).\n", remaining); 86168d75effSDimitry Andric } 86268d75effSDimitry Andric } 86368d75effSDimitry Andric 86468d75effSDimitry Andric void LeakReport::PrintReportForLeak(uptr index) { 86568d75effSDimitry Andric Decorator d; 86668d75effSDimitry Andric Printf("%s", d.Leak()); 86768d75effSDimitry Andric Printf("%s leak of %zu byte(s) in %zu object(s) allocated from:\n", 86868d75effSDimitry Andric leaks_[index].is_directly_leaked ? "Direct" : "Indirect", 86968d75effSDimitry Andric leaks_[index].total_size, leaks_[index].hit_count); 87068d75effSDimitry Andric Printf("%s", d.Default()); 87168d75effSDimitry Andric 87268d75effSDimitry Andric PrintStackTraceById(leaks_[index].stack_trace_id); 87368d75effSDimitry Andric 87468d75effSDimitry Andric if (flags()->report_objects) { 87568d75effSDimitry Andric Printf("Objects leaked above:\n"); 87668d75effSDimitry Andric PrintLeakedObjectsForLeak(index); 87768d75effSDimitry Andric Printf("\n"); 87868d75effSDimitry Andric } 87968d75effSDimitry Andric } 88068d75effSDimitry Andric 88168d75effSDimitry Andric void LeakReport::PrintLeakedObjectsForLeak(uptr index) { 88268d75effSDimitry Andric u32 leak_id = leaks_[index].id; 88368d75effSDimitry Andric for (uptr j = 0; j < leaked_objects_.size(); j++) { 88468d75effSDimitry Andric if (leaked_objects_[j].leak_id == leak_id) 88568d75effSDimitry Andric Printf("%p (%zu bytes)\n", leaked_objects_[j].addr, 88668d75effSDimitry Andric leaked_objects_[j].size); 88768d75effSDimitry Andric } 88868d75effSDimitry Andric } 88968d75effSDimitry Andric 89068d75effSDimitry Andric void LeakReport::PrintSummary() { 89168d75effSDimitry Andric CHECK(leaks_.size() <= kMaxLeaksConsidered); 89268d75effSDimitry Andric uptr bytes = 0, allocations = 0; 89368d75effSDimitry Andric for (uptr i = 0; i < leaks_.size(); i++) { 89468d75effSDimitry Andric if (leaks_[i].is_suppressed) continue; 89568d75effSDimitry Andric bytes += leaks_[i].total_size; 89668d75effSDimitry Andric allocations += leaks_[i].hit_count; 89768d75effSDimitry Andric } 898*fe6060f1SDimitry Andric InternalScopedString summary; 89968d75effSDimitry Andric summary.append("%zu byte(s) leaked in %zu allocation(s).", bytes, 90068d75effSDimitry Andric allocations); 90168d75effSDimitry Andric ReportErrorSummary(summary.data()); 90268d75effSDimitry Andric } 90368d75effSDimitry Andric 904e8d8bef9SDimitry Andric uptr LeakReport::ApplySuppressions() { 905e8d8bef9SDimitry Andric LeakSuppressionContext *suppressions = GetSuppressionContext(); 906e8d8bef9SDimitry Andric uptr new_suppressions = false; 90768d75effSDimitry Andric for (uptr i = 0; i < leaks_.size(); i++) { 908e8d8bef9SDimitry Andric Suppression *s = 909e8d8bef9SDimitry Andric suppressions->GetSuppressionForStack(leaks_[i].stack_trace_id); 91068d75effSDimitry Andric if (s) { 91168d75effSDimitry Andric s->weight += leaks_[i].total_size; 91268d75effSDimitry Andric atomic_store_relaxed(&s->hit_count, atomic_load_relaxed(&s->hit_count) + 91368d75effSDimitry Andric leaks_[i].hit_count); 91468d75effSDimitry Andric leaks_[i].is_suppressed = true; 915e8d8bef9SDimitry Andric ++new_suppressions; 91668d75effSDimitry Andric } 91768d75effSDimitry Andric } 918e8d8bef9SDimitry Andric return new_suppressions; 91968d75effSDimitry Andric } 92068d75effSDimitry Andric 92168d75effSDimitry Andric uptr LeakReport::UnsuppressedLeakCount() { 92268d75effSDimitry Andric uptr result = 0; 92368d75effSDimitry Andric for (uptr i = 0; i < leaks_.size(); i++) 92468d75effSDimitry Andric if (!leaks_[i].is_suppressed) result++; 92568d75effSDimitry Andric return result; 92668d75effSDimitry Andric } 92768d75effSDimitry Andric 928e8d8bef9SDimitry Andric uptr LeakReport::IndirectUnsuppressedLeakCount() { 929e8d8bef9SDimitry Andric uptr result = 0; 930e8d8bef9SDimitry Andric for (uptr i = 0; i < leaks_.size(); i++) 931e8d8bef9SDimitry Andric if (!leaks_[i].is_suppressed && !leaks_[i].is_directly_leaked) 932e8d8bef9SDimitry Andric result++; 933e8d8bef9SDimitry Andric return result; 934e8d8bef9SDimitry Andric } 935e8d8bef9SDimitry Andric 93668d75effSDimitry Andric } // namespace __lsan 93768d75effSDimitry Andric #else // CAN_SANITIZE_LEAKS 93868d75effSDimitry Andric namespace __lsan { 93968d75effSDimitry Andric void InitCommonLsan() { } 94068d75effSDimitry Andric void DoLeakCheck() { } 94168d75effSDimitry Andric void DoRecoverableLeakCheckVoid() { } 94268d75effSDimitry Andric void DisableInThisThread() { } 94368d75effSDimitry Andric void EnableInThisThread() { } 94468d75effSDimitry Andric } 94568d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 94668d75effSDimitry Andric 94768d75effSDimitry Andric using namespace __lsan; 94868d75effSDimitry Andric 94968d75effSDimitry Andric extern "C" { 95068d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 95168d75effSDimitry Andric void __lsan_ignore_object(const void *p) { 95268d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 95368d75effSDimitry Andric if (!common_flags()->detect_leaks) 95468d75effSDimitry Andric return; 95568d75effSDimitry Andric // Cannot use PointsIntoChunk or LsanMetadata here, since the allocator is not 95668d75effSDimitry Andric // locked. 95768d75effSDimitry Andric BlockingMutexLock l(&global_mutex); 95868d75effSDimitry Andric IgnoreObjectResult res = IgnoreObjectLocked(p); 95968d75effSDimitry Andric if (res == kIgnoreObjectInvalid) 96068d75effSDimitry Andric VReport(1, "__lsan_ignore_object(): no heap object found at %p", p); 96168d75effSDimitry Andric if (res == kIgnoreObjectAlreadyIgnored) 96268d75effSDimitry Andric VReport(1, "__lsan_ignore_object(): " 96368d75effSDimitry Andric "heap object at %p is already being ignored\n", p); 96468d75effSDimitry Andric if (res == kIgnoreObjectSuccess) 96568d75effSDimitry Andric VReport(1, "__lsan_ignore_object(): ignoring heap object at %p\n", p); 96668d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 96768d75effSDimitry Andric } 96868d75effSDimitry Andric 96968d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 97068d75effSDimitry Andric void __lsan_register_root_region(const void *begin, uptr size) { 97168d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 97268d75effSDimitry Andric BlockingMutexLock l(&global_mutex); 97368d75effSDimitry Andric CHECK(root_regions); 97468d75effSDimitry Andric RootRegion region = {reinterpret_cast<uptr>(begin), size}; 97568d75effSDimitry Andric root_regions->push_back(region); 97668d75effSDimitry Andric VReport(1, "Registered root region at %p of size %llu\n", begin, size); 97768d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 97868d75effSDimitry Andric } 97968d75effSDimitry Andric 98068d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 98168d75effSDimitry Andric void __lsan_unregister_root_region(const void *begin, uptr size) { 98268d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 98368d75effSDimitry Andric BlockingMutexLock l(&global_mutex); 98468d75effSDimitry Andric CHECK(root_regions); 98568d75effSDimitry Andric bool removed = false; 98668d75effSDimitry Andric for (uptr i = 0; i < root_regions->size(); i++) { 98768d75effSDimitry Andric RootRegion region = (*root_regions)[i]; 98868d75effSDimitry Andric if (region.begin == reinterpret_cast<uptr>(begin) && region.size == size) { 98968d75effSDimitry Andric removed = true; 99068d75effSDimitry Andric uptr last_index = root_regions->size() - 1; 99168d75effSDimitry Andric (*root_regions)[i] = (*root_regions)[last_index]; 99268d75effSDimitry Andric root_regions->pop_back(); 99368d75effSDimitry Andric VReport(1, "Unregistered root region at %p of size %llu\n", begin, size); 99468d75effSDimitry Andric break; 99568d75effSDimitry Andric } 99668d75effSDimitry Andric } 99768d75effSDimitry Andric if (!removed) { 99868d75effSDimitry Andric Report( 99968d75effSDimitry Andric "__lsan_unregister_root_region(): region at %p of size %llu has not " 100068d75effSDimitry Andric "been registered.\n", 100168d75effSDimitry Andric begin, size); 100268d75effSDimitry Andric Die(); 100368d75effSDimitry Andric } 100468d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 100568d75effSDimitry Andric } 100668d75effSDimitry Andric 100768d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 100868d75effSDimitry Andric void __lsan_disable() { 100968d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 101068d75effSDimitry Andric __lsan::DisableInThisThread(); 101168d75effSDimitry Andric #endif 101268d75effSDimitry Andric } 101368d75effSDimitry Andric 101468d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 101568d75effSDimitry Andric void __lsan_enable() { 101668d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 101768d75effSDimitry Andric __lsan::EnableInThisThread(); 101868d75effSDimitry Andric #endif 101968d75effSDimitry Andric } 102068d75effSDimitry Andric 102168d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 102268d75effSDimitry Andric void __lsan_do_leak_check() { 102368d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 102468d75effSDimitry Andric if (common_flags()->detect_leaks) 102568d75effSDimitry Andric __lsan::DoLeakCheck(); 102668d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 102768d75effSDimitry Andric } 102868d75effSDimitry Andric 102968d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE 103068d75effSDimitry Andric int __lsan_do_recoverable_leak_check() { 103168d75effSDimitry Andric #if CAN_SANITIZE_LEAKS 103268d75effSDimitry Andric if (common_flags()->detect_leaks) 103368d75effSDimitry Andric return __lsan::DoRecoverableLeakCheck(); 103468d75effSDimitry Andric #endif // CAN_SANITIZE_LEAKS 103568d75effSDimitry Andric return 0; 103668d75effSDimitry Andric } 103768d75effSDimitry Andric 1038e8d8bef9SDimitry Andric SANITIZER_INTERFACE_WEAK_DEF(const char *, __lsan_default_options, void) { 103968d75effSDimitry Andric return ""; 104068d75effSDimitry Andric } 104168d75effSDimitry Andric 1042e8d8bef9SDimitry Andric #if !SANITIZER_SUPPORTS_WEAK_HOOKS 104368d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE 104468d75effSDimitry Andric int __lsan_is_turned_off() { 104568d75effSDimitry Andric return 0; 104668d75effSDimitry Andric } 104768d75effSDimitry Andric 104868d75effSDimitry Andric SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE 104968d75effSDimitry Andric const char *__lsan_default_suppressions() { 105068d75effSDimitry Andric return ""; 105168d75effSDimitry Andric } 105268d75effSDimitry Andric #endif 105368d75effSDimitry Andric } // extern "C" 1054