1//=-- lsan_common.cpp -----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file is a part of LeakSanitizer.
10// Implementation of common leak checking functionality.
11//
12//===----------------------------------------------------------------------===//
13
14#include "lsan_common.h"
15
16#include "sanitizer_common/sanitizer_common.h"
17#include "sanitizer_common/sanitizer_flag_parser.h"
18#include "sanitizer_common/sanitizer_flags.h"
19#include "sanitizer_common/sanitizer_placement_new.h"
20#include "sanitizer_common/sanitizer_procmaps.h"
21#include "sanitizer_common/sanitizer_report_decorator.h"
22#include "sanitizer_common/sanitizer_stackdepot.h"
23#include "sanitizer_common/sanitizer_stacktrace.h"
24#include "sanitizer_common/sanitizer_suppressions.h"
25#include "sanitizer_common/sanitizer_thread_registry.h"
26#include "sanitizer_common/sanitizer_tls_get_addr.h"
27
28#if CAN_SANITIZE_LEAKS
29
30# if SANITIZER_APPLE
31// https://github.com/apple-oss-distributions/objc4/blob/8701d5672d3fd3cd817aeb84db1077aafe1a1604/runtime/objc-runtime-new.h#L127
32# if SANITIZER_IOS && !SANITIZER_IOSSIM
33# define OBJC_DATA_MASK 0x0000007ffffffff8UL
34# else
35# define OBJC_DATA_MASK 0x00007ffffffffff8UL
36# endif
37# endif
38
39namespace __lsan {
40
41// This mutex is used to prevent races between DoLeakCheck and IgnoreObject, and
42// also to protect the global list of root regions.
43static Mutex global_mutex;
44
45void LockGlobal() SANITIZER_ACQUIRE(global_mutex) { global_mutex.Lock(); }
46void UnlockGlobal() SANITIZER_RELEASE(global_mutex) { global_mutex.Unlock(); }
47
48Flags lsan_flags;
49
50void DisableCounterUnderflow() {
51 if (common_flags()->detect_leaks) {
52 Report(format: "Unmatched call to __lsan_enable().\n");
53 Die();
54 }
55}
56
57void Flags::SetDefaults() {
58# define LSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
59# include "lsan_flags.inc"
60# undef LSAN_FLAG
61}
62
63void RegisterLsanFlags(FlagParser *parser, Flags *f) {
64# define LSAN_FLAG(Type, Name, DefaultValue, Description) \
65 RegisterFlag(parser, #Name, Description, &f->Name);
66# include "lsan_flags.inc"
67# undef LSAN_FLAG
68}
69
70# define LOG_POINTERS(...) \
71 do { \
72 if (flags()->log_pointers) \
73 Report(__VA_ARGS__); \
74 } while (0)
75
76# define LOG_THREADS(...) \
77 do { \
78 if (flags()->log_threads) \
79 Report(__VA_ARGS__); \
80 } while (0)
81
82class LeakSuppressionContext {
83 bool parsed = false;
84 SuppressionContext context;
85 bool suppressed_stacks_sorted = true;
86 InternalMmapVector<u32> suppressed_stacks;
87 const LoadedModule *suppress_module = nullptr;
88
89 void LazyInit();
90 Suppression *GetSuppressionForAddr(uptr addr);
91 bool SuppressInvalid(const StackTrace &stack);
92 bool SuppressByRule(const StackTrace &stack, uptr hit_count, uptr total_size);
93
94 public:
95 LeakSuppressionContext(const char *supprression_types[],
96 int suppression_types_num)
97 : context(supprression_types, suppression_types_num) {}
98
99 bool Suppress(u32 stack_trace_id, uptr hit_count, uptr total_size);
100
101 const InternalMmapVector<u32> &GetSortedSuppressedStacks() {
102 if (!suppressed_stacks_sorted) {
103 suppressed_stacks_sorted = true;
104 SortAndDedup(v&: suppressed_stacks);
105 }
106 return suppressed_stacks;
107 }
108 void PrintMatchedSuppressions();
109};
110
111alignas(64) static char suppression_placeholder[sizeof(LeakSuppressionContext)];
112static LeakSuppressionContext *suppression_ctx = nullptr;
113static const char kSuppressionLeak[] = "leak";
114static const char *kSuppressionTypes[] = {kSuppressionLeak};
115static const char kStdSuppressions[] =
116# if SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT
117 // For more details refer to the SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT
118 // definition.
119 "leak:*pthread_exit*\n"
120# endif // SANITIZER_SUPPRESS_LEAK_ON_PTHREAD_EXIT
121# if SANITIZER_APPLE
122 // For Darwin and os_log/os_trace: https://reviews.llvm.org/D35173
123 "leak:*_os_trace*\n"
124# if SANITIZER_ARM64
125 // Apple Aarch64 leaks in dyld on startup.
126 // See https://github.com/llvm/llvm-project/issues/115992.
127 "leak:*_fetchInitializingClassList*\n"
128 // Apple Aarch64 leaks when using thread locals.
129 "leak:*dyld4::RuntimeState::_instantiateTLVs*\n"
130# endif
131# endif
132 // TLS leak in some glibc versions, described in
133 // https://sourceware.org/bugzilla/show_bug.cgi?id=12650.
134 "leak:*tls_get_addr*\n"
135 "leak:*dlerror*\n";
136
137void InitializeSuppressions() {
138 CHECK_EQ(nullptr, suppression_ctx);
139 suppression_ctx = new (suppression_placeholder)
140 LeakSuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes));
141}
142
143void LeakSuppressionContext::LazyInit() {
144 if (!parsed) {
145 parsed = true;
146 context.ParseFromFile(filename: flags()->suppressions);
147 if (&__lsan_default_suppressions)
148 context.Parse(str: __lsan_default_suppressions());
149 context.Parse(str: kStdSuppressions);
150 if (flags()->use_tls && flags()->use_ld_allocations)
151 suppress_module = GetLinker();
152 }
153}
154
155Suppression *LeakSuppressionContext::GetSuppressionForAddr(uptr addr) {
156 Suppression *s = nullptr;
157
158 // Suppress by module name.
159 const char *module_name = Symbolizer::GetOrInit()->GetModuleNameForPc(pc: addr);
160 if (!module_name)
161 module_name = "<unknown module>";
162 if (context.Match(str: module_name, type: kSuppressionLeak, s: &s))
163 return s;
164
165 // Suppress by file or function name.
166 SymbolizedStackHolder symbolized_stack(
167 Symbolizer::GetOrInit()->SymbolizePC(address: addr));
168 const SymbolizedStack *frames = symbolized_stack.get();
169 for (const SymbolizedStack *cur = frames; cur; cur = cur->next) {
170 if (context.Match(str: cur->info.function, type: kSuppressionLeak, s: &s) ||
171 context.Match(str: cur->info.file, type: kSuppressionLeak, s: &s)) {
172 break;
173 }
174 }
175 return s;
176}
177
178static uptr GetCallerPC(const StackTrace &stack) {
179 // The top frame is our malloc/calloc/etc. The next frame is the caller.
180 if (stack.size >= 2)
181 return stack.trace[1];
182 return 0;
183}
184
185# if SANITIZER_APPLE
186// Several pointers in the Objective-C runtime (method cache and class_rw_t,
187// for example) are tagged with additional bits we need to strip.
188static inline void *TransformPointer(void *p) {
189 uptr ptr = reinterpret_cast<uptr>(p);
190 return reinterpret_cast<void *>(ptr & OBJC_DATA_MASK);
191}
192# endif
193
194// On Linux, treats all chunks allocated from ld-linux.so as reachable, which
195// covers dynamically allocated TLS blocks, internal dynamic loader's loaded
196// modules accounting etc.
197// Dynamic TLS blocks contain the TLS variables of dynamically loaded modules.
198// They are allocated with a __libc_memalign() call in allocate_and_init()
199// (elf/dl-tls.c). Glibc won't tell us the address ranges occupied by those
200// blocks, but we can make sure they come from our own allocator by intercepting
201// __libc_memalign(). On top of that, there is no easy way to reach them. Their
202// addresses are stored in a dynamically allocated array (the DTV) which is
203// referenced from the static TLS. Unfortunately, we can't just rely on the DTV
204// being reachable from the static TLS, and the dynamic TLS being reachable from
205// the DTV. This is because the initial DTV is allocated before our interception
206// mechanism kicks in, and thus we don't recognize it as allocated memory. We
207// can't special-case it either, since we don't know its size.
208// Our solution is to include in the root set all allocations made from
209// ld-linux.so (which is where allocate_and_init() is implemented). This is
210// guaranteed to include all dynamic TLS blocks (and possibly other allocations
211// which we don't care about).
212// On all other platforms, this simply checks to ensure that the caller pc is
213// valid before reporting chunks as leaked.
214bool LeakSuppressionContext::SuppressInvalid(const StackTrace &stack) {
215 uptr caller_pc = GetCallerPC(stack);
216 // If caller_pc is unknown, this chunk may be allocated in a coroutine. Mark
217 // it as reachable, as we can't properly report its allocation stack anyway.
218 return !caller_pc ||
219 (suppress_module && suppress_module->containsAddress(address: caller_pc));
220}
221
222bool LeakSuppressionContext::SuppressByRule(const StackTrace &stack,
223 uptr hit_count, uptr total_size) {
224 for (uptr i = 0; i < stack.size; i++) {
225 Suppression *s = GetSuppressionForAddr(
226 addr: StackTrace::GetPreviousInstructionPc(pc: stack.trace[i]));
227 if (s) {
228 s->weight += total_size;
229 atomic_fetch_add(a: &s->hit_count, v: hit_count, mo: memory_order_relaxed);
230 return true;
231 }
232 }
233 return false;
234}
235
236bool LeakSuppressionContext::Suppress(u32 stack_trace_id, uptr hit_count,
237 uptr total_size) {
238 LazyInit();
239 StackTrace stack = StackDepotGet(id: stack_trace_id);
240 if (!SuppressInvalid(stack) && !SuppressByRule(stack, hit_count, total_size))
241 return false;
242 suppressed_stacks_sorted = false;
243 suppressed_stacks.push_back(element: stack_trace_id);
244 return true;
245}
246
247static LeakSuppressionContext *GetSuppressionContext() {
248 CHECK(suppression_ctx);
249 return suppression_ctx;
250}
251
252void InitCommonLsan() {
253 if (common_flags()->detect_leaks) {
254 // Initialization which can fail or print warnings should only be done if
255 // LSan is actually enabled.
256 InitializeSuppressions();
257 InitializePlatformSpecificModules();
258 }
259}
260
261class Decorator : public __sanitizer::SanitizerCommonDecorator {
262 public:
263 Decorator() : SanitizerCommonDecorator() {}
264 const char *Error() { return Red(); }
265 const char *Leak() { return Blue(); }
266};
267
268static inline bool MaybeUserPointer(uptr p) {
269 // Since our heap is located in mmap-ed memory, we can assume a sensible lower
270 // bound on heap addresses.
271 const uptr kMinAddress = 4 * 4096;
272 if (p < kMinAddress)
273 return false;
274# if defined(__x86_64__)
275 // TODO: support LAM48 and 5 level page tables.
276 // LAM_U57 mask format
277 // * top byte: 0x81 because the format is: [0] [6-bit tag] [0]
278 // * top-1 byte: 0xff because it should be 0
279 // * top-2 byte: 0x80 because Linux uses 128 TB VMA ending at 0x7fffffffffff
280 constexpr uptr kLAM_U57Mask = 0x81ff80;
281 constexpr uptr kPointerMask = kLAM_U57Mask << 40;
282 return ((p & kPointerMask) == 0);
283# elif defined(__mips64)
284 return ((p >> 40) == 0);
285# elif defined(__aarch64__)
286 // TBI (Top Byte Ignore) feature of AArch64: bits [63:56] are ignored in
287 // address translation and can be used to store a tag.
288 constexpr uptr kPointerMask = 255ULL << 48;
289 // Accept up to 48 bit VMA.
290 return ((p & kPointerMask) == 0);
291# elif defined(__loongarch_lp64)
292 // Allow 47-bit user-space VMA at current.
293 return ((p >> 47) == 0);
294# else
295 return true;
296# endif
297}
298
299namespace {
300struct DirectMemoryAccessor {
301 void Init(uptr begin, uptr end) {};
302 void *LoadPtr(uptr p) const { return *reinterpret_cast<void **>(p); }
303};
304
305struct CopyMemoryAccessor {
306 void Init(uptr begin, uptr end) {
307 this->begin = begin;
308 buffer.clear();
309 buffer.resize(new_size: end - begin);
310 MemCpyAccessible(dest: buffer.data(), src: reinterpret_cast<void *>(begin),
311 n: buffer.size());
312 };
313
314 void *LoadPtr(uptr p) const {
315 uptr offset = p - begin;
316 CHECK_LE(offset + sizeof(void *), reinterpret_cast<uptr>(buffer.size()));
317 return *reinterpret_cast<void **>(offset +
318 reinterpret_cast<uptr>(buffer.data()));
319 }
320
321 private:
322 uptr begin;
323 InternalMmapVector<char> buffer;
324};
325} // namespace
326
327// Scans the memory range, looking for byte patterns that point into allocator
328// chunks. Marks those chunks with |tag| and adds them to |frontier|.
329// There are two usage modes for this function: finding reachable chunks
330// (|tag| = kReachable) and finding indirectly leaked chunks
331// (|tag| = kIndirectlyLeaked). In the second case, there's no flood fill,
332// so |frontier| = 0.
333template <class Accessor>
334void ScanForPointers(uptr begin, uptr end, Frontier *frontier,
335 const char *region_type, ChunkTag tag,
336 Accessor &accessor) {
337 CHECK(tag == kReachable || tag == kIndirectlyLeaked);
338 const uptr alignment = flags()->pointer_alignment();
339 LOG_POINTERS("Scanning %s range %p-%p.\n", region_type, (void *)begin,
340 (void *)end);
341 accessor.Init(begin, end);
342 uptr pp = begin;
343 if (pp % alignment)
344 pp = pp + alignment - pp % alignment;
345 for (; pp + sizeof(void *) <= end; pp += alignment) {
346 void *p = accessor.LoadPtr(pp);
347# if SANITIZER_APPLE
348 p = TransformPointer(p);
349# endif
350 if (!MaybeUserPointer(p: reinterpret_cast<uptr>(p)))
351 continue;
352 uptr chunk = PointsIntoChunk(p);
353 if (!chunk)
354 continue;
355 // Pointers to self don't count. This matters when tag == kIndirectlyLeaked.
356 if (chunk == begin)
357 continue;
358 LsanMetadata m(chunk);
359 if (m.tag() == kReachable || m.tag() == kIgnored)
360 continue;
361
362 // Do this check relatively late so we can log only the interesting cases.
363 if (!flags()->use_poisoned && WordIsPoisoned(addr: pp)) {
364 LOG_POINTERS(
365 "%p is poisoned: ignoring %p pointing into chunk %p-%p of size "
366 "%zu.\n",
367 (void *)pp, p, (void *)chunk, (void *)(chunk + m.requested_size()),
368 m.requested_size());
369 continue;
370 }
371
372 m.set_tag(tag);
373 LOG_POINTERS("%p: found %p pointing into chunk %p-%p of size %zu.\n",
374 (void *)pp, p, (void *)chunk,
375 (void *)(chunk + m.requested_size()), m.requested_size());
376 if (frontier)
377 frontier->push_back(element: chunk);
378 }
379}
380
381void ScanRangeForPointers(uptr begin, uptr end, Frontier *frontier,
382 const char *region_type, ChunkTag tag) {
383 DirectMemoryAccessor accessor;
384 ScanForPointers(begin, end, frontier, region_type, tag, accessor);
385}
386
387// Scans a global range for pointers
388void ScanGlobalRange(uptr begin, uptr end, Frontier *frontier) {
389 uptr allocator_begin = 0, allocator_end = 0;
390 GetAllocatorGlobalRange(begin: &allocator_begin, end: &allocator_end);
391 if (begin <= allocator_begin && allocator_begin < end) {
392 CHECK_LE(allocator_begin, allocator_end);
393 CHECK_LE(allocator_end, end);
394 if (begin < allocator_begin)
395 ScanRangeForPointers(begin, end: allocator_begin, frontier, region_type: "GLOBAL",
396 tag: kReachable);
397 if (allocator_end < end)
398 ScanRangeForPointers(begin: allocator_end, end, frontier, region_type: "GLOBAL", tag: kReachable);
399 } else {
400 ScanRangeForPointers(begin, end, frontier, region_type: "GLOBAL", tag: kReachable);
401 }
402}
403
404template <class Accessor>
405void ScanRanges(const InternalMmapVector<Range> &ranges, Frontier *frontier,
406 const char *region_type, Accessor &accessor) {
407 for (uptr i = 0; i < ranges.size(); i++) {
408 ScanForPointers(ranges[i].begin, ranges[i].end, frontier, region_type,
409 kReachable, accessor);
410 }
411}
412
413void ScanExtraStackRanges(const InternalMmapVector<Range> &ranges,
414 Frontier *frontier) {
415 DirectMemoryAccessor accessor;
416 ScanRanges(ranges, frontier, region_type: "FAKE STACK", accessor);
417}
418
419# if SANITIZER_FUCHSIA
420
421// Fuchsia handles all threads together with its own callback.
422static void ProcessThreads(SuspendedThreadsList const &, Frontier *, ThreadID,
423 uptr) {}
424
425# else
426
427# if SANITIZER_ANDROID
428// FIXME: Move this out into *libcdep.cpp
429extern "C" SANITIZER_WEAK_ATTRIBUTE void __libc_iterate_dynamic_tls(
430 pid_t, void (*cb)(void *, void *, uptr, void *), void *);
431# endif
432
433static void ProcessThreadRegistry(Frontier *frontier) {
434 InternalMmapVector<uptr> ptrs;
435 GetAdditionalThreadContextPtrsLocked(ptrs: &ptrs);
436
437 for (uptr i = 0; i < ptrs.size(); ++i) {
438 void *ptr = reinterpret_cast<void *>(ptrs[i]);
439 uptr chunk = PointsIntoChunk(p: ptr);
440 if (!chunk)
441 continue;
442 LsanMetadata m(chunk);
443 if (!m.allocated())
444 continue;
445
446 // Mark as reachable and add to frontier.
447 LOG_POINTERS("Treating pointer %p from ThreadContext as reachable\n", ptr);
448 m.set_tag(kReachable);
449 frontier->push_back(element: chunk);
450 }
451}
452
453// Scans thread data (stacks and TLS) for heap pointers.
454template <class Accessor>
455static void ProcessThread(ThreadID os_id, uptr sp,
456 const InternalMmapVector<uptr> &registers,
457 InternalMmapVector<Range> &extra_ranges,
458 Frontier *frontier, Accessor &accessor) {
459 // `extra_ranges` is outside of the function and the loop to reused mapped
460 // memory.
461 CHECK(extra_ranges.empty());
462 LOG_THREADS("Processing thread %llu.\n", os_id);
463 uptr stack_begin, stack_end, tls_begin, tls_end, cache_begin, cache_end;
464 DTLS *dtls;
465 bool thread_found =
466 GetThreadRangesLocked(os_id, stack_begin: &stack_begin, stack_end: &stack_end, tls_begin: &tls_begin,
467 tls_end: &tls_end, cache_begin: &cache_begin, cache_end: &cache_end, dtls: &dtls);
468 if (!thread_found) {
469 // If a thread can't be found in the thread registry, it's probably in the
470 // process of destruction. Log this event and move on.
471 LOG_THREADS("Thread %llu not found in registry.\n", os_id);
472 return;
473 }
474
475 if (!sp)
476 sp = stack_begin;
477
478 if (flags()->use_registers) {
479 uptr registers_begin = reinterpret_cast<uptr>(registers.data());
480 uptr registers_end =
481 reinterpret_cast<uptr>(registers.data() + registers.size());
482 ScanForPointers(registers_begin, registers_end, frontier, "REGISTERS",
483 kReachable, accessor);
484 }
485
486 if (flags()->use_stacks) {
487 LOG_THREADS("Stack at %p-%p (SP = %p).\n", (void *)stack_begin,
488 (void *)stack_end, (void *)sp);
489 if (sp < stack_begin || sp >= stack_end) {
490 // SP is outside the recorded stack range (e.g. the thread is running a
491 // signal handler on alternate stack, or swapcontext was used).
492 // Again, consider the entire stack range to be reachable.
493 LOG_THREADS("WARNING: stack pointer not in stack range.\n");
494 uptr page_size = GetPageSizeCached();
495 int skipped = 0;
496 while (stack_begin < stack_end &&
497 !IsAccessibleMemoryRange(beg: stack_begin, size: 1)) {
498 skipped++;
499 stack_begin += page_size;
500 }
501 LOG_THREADS("Skipped %d guard page(s) to obtain stack %p-%p.\n", skipped,
502 (void *)stack_begin, (void *)stack_end);
503 } else {
504 // Shrink the stack range to ignore out-of-scope values.
505 stack_begin = sp;
506 }
507 ScanForPointers(stack_begin, stack_end, frontier, "STACK", kReachable,
508 accessor);
509 GetThreadExtraStackRangesLocked(os_id, ranges: &extra_ranges);
510 ScanRanges(extra_ranges, frontier, "FAKE STACK", accessor);
511 }
512
513 if (flags()->use_tls) {
514 if (tls_begin) {
515 LOG_THREADS("TLS at %p-%p.\n", (void *)tls_begin, (void *)tls_end);
516 // If the tls and cache ranges don't overlap, scan full tls range,
517 // otherwise, only scan the non-overlapping portions
518 if (cache_begin == cache_end || tls_end < cache_begin ||
519 tls_begin > cache_end) {
520 ScanForPointers(tls_begin, tls_end, frontier, "TLS", kReachable,
521 accessor);
522 } else {
523 if (tls_begin < cache_begin)
524 ScanForPointers(tls_begin, cache_begin, frontier, "TLS", kReachable,
525 accessor);
526 if (tls_end > cache_end)
527 ScanForPointers(cache_end, tls_end, frontier, "TLS", kReachable,
528 accessor);
529 }
530 }
531# if SANITIZER_ANDROID
532 extra_ranges.clear();
533 auto *cb = +[](void *dtls_begin, void *dtls_end, uptr /*dso_idd*/,
534 void *arg) -> void {
535 reinterpret_cast<InternalMmapVector<Range> *>(arg)->push_back(
536 {reinterpret_cast<uptr>(dtls_begin),
537 reinterpret_cast<uptr>(dtls_end)});
538 };
539 ScanRanges(extra_ranges, frontier, "DTLS", accessor);
540 // FIXME: There might be a race-condition here (and in Bionic) if the
541 // thread is suspended in the middle of updating its DTLS. IOWs, we
542 // could scan already freed memory. (probably fine for now)
543 __libc_iterate_dynamic_tls(os_id, cb, frontier);
544# else
545 if (dtls && !DTLSInDestruction(dtls)) {
546 ForEachDVT(dtls, [&](const DTLS::DTV &dtv, int id) {
547 uptr dtls_beg = dtv.beg;
548 uptr dtls_end = dtls_beg + dtv.size;
549 if (dtls_beg < dtls_end) {
550 LOG_THREADS("DTLS %d at %p-%p.\n", id, (void *)dtls_beg,
551 (void *)dtls_end);
552 ScanForPointers(dtls_beg, dtls_end, frontier, "DTLS", kReachable,
553 accessor);
554 }
555 });
556 } else {
557 // We are handling a thread with DTLS under destruction. Log about
558 // this and continue.
559 LOG_THREADS("Thread %llu has DTLS under destruction.\n", os_id);
560 }
561# endif
562 }
563}
564
565static void ProcessThreads(SuspendedThreadsList const &suspended_threads,
566 Frontier *frontier, ThreadID caller_tid,
567 uptr caller_sp) {
568 InternalMmapVector<ThreadID> done_threads;
569 InternalMmapVector<uptr> registers;
570 InternalMmapVector<Range> extra_ranges;
571 for (uptr i = 0; i < suspended_threads.ThreadCount(); i++) {
572 registers.clear();
573 extra_ranges.clear();
574
575 const ThreadID os_id = suspended_threads.GetThreadID(index: i);
576 uptr sp = 0;
577 PtraceRegistersStatus have_registers =
578 suspended_threads.GetRegistersAndSP(index: i, buffer: &registers, sp: &sp);
579 if (have_registers != REGISTERS_AVAILABLE) {
580 VReport(1, "Unable to get registers from thread %llu.\n", os_id);
581 // If unable to get SP, consider the entire stack to be reachable unless
582 // GetRegistersAndSP failed with ESRCH.
583 if (have_registers == REGISTERS_UNAVAILABLE_FATAL)
584 continue;
585 sp = 0;
586 }
587
588 if (os_id == caller_tid)
589 sp = caller_sp;
590
591 DirectMemoryAccessor accessor;
592 ProcessThread(os_id, sp, registers, extra_ranges, frontier, accessor);
593 if (flags()->use_detached)
594 done_threads.push_back(element: os_id);
595 }
596
597 if (flags()->use_detached) {
598 CopyMemoryAccessor accessor;
599 InternalMmapVector<ThreadID> known_threads;
600 GetRunningThreadsLocked(threads: &known_threads);
601 Sort(v: done_threads.data(), size: done_threads.size());
602 for (ThreadID os_id : known_threads) {
603 registers.clear();
604 extra_ranges.clear();
605
606 uptr i = InternalLowerBound(v: done_threads, val: os_id);
607 if (i >= done_threads.size() || done_threads[i] != os_id) {
608 uptr sp = (os_id == caller_tid) ? caller_sp : 0;
609 ProcessThread(os_id, sp, registers, extra_ranges, frontier, accessor);
610 }
611 }
612 }
613
614 // Add pointers reachable from ThreadContexts
615 ProcessThreadRegistry(frontier);
616}
617
618# endif // SANITIZER_FUCHSIA
619
620// A map that contains [region_begin, region_end) pairs.
621using RootRegions = DenseMap<detail::DenseMapPair<uptr, uptr>, uptr>;
622
623static RootRegions &GetRootRegionsLocked() {
624 global_mutex.CheckLocked();
625 static RootRegions *regions = nullptr;
626 alignas(RootRegions) static char placeholder[sizeof(RootRegions)];
627 if (!regions)
628 regions = new (placeholder) RootRegions();
629 return *regions;
630}
631
632bool HasRootRegions() { return !GetRootRegionsLocked().empty(); }
633
634void ScanRootRegions(Frontier *frontier,
635 const InternalMmapVectorNoCtor<Region> &mapped_regions) {
636 if (!flags()->use_root_regions)
637 return;
638
639 InternalMmapVector<Region> regions;
640 GetRootRegionsLocked().forEach(fn: [&](const auto &kv) {
641 regions.push_back(element: {kv.first.first, kv.first.second});
642 return true;
643 });
644
645 InternalMmapVector<Region> intersection;
646 Intersect(a: mapped_regions, b: regions, output&: intersection);
647
648 for (const Region &r : intersection) {
649 LOG_POINTERS("Root region intersects with mapped region at %p-%p\n",
650 (void *)r.begin, (void *)r.end);
651 ScanRangeForPointers(begin: r.begin, end: r.end, frontier, region_type: "ROOT", tag: kReachable);
652 }
653}
654
655// Scans root regions for heap pointers.
656static void ProcessRootRegions(Frontier *frontier) {
657 if (!flags()->use_root_regions || !HasRootRegions())
658 return;
659 MemoryMappingLayout proc_maps(/*cache_enabled*/ true);
660 MemoryMappedSegment segment;
661 InternalMmapVector<Region> mapped_regions;
662 while (proc_maps.Next(segment: &segment))
663 if (segment.IsReadable())
664 mapped_regions.push_back(element: {.begin: segment.start, .end: segment.end});
665 ScanRootRegions(frontier, mapped_regions);
666}
667
668static void FloodFillTag(Frontier *frontier, ChunkTag tag) {
669 while (frontier->size()) {
670 uptr next_chunk = frontier->back();
671 frontier->pop_back();
672 LsanMetadata m(next_chunk);
673 ScanRangeForPointers(begin: next_chunk, end: next_chunk + m.requested_size(), frontier,
674 region_type: "HEAP", tag);
675 }
676}
677
678// ForEachChunk callback. If the chunk is marked as leaked, marks all chunks
679// which are reachable from it as indirectly leaked.
680static void MarkIndirectlyLeakedCb(uptr chunk, void *arg) {
681 chunk = GetUserBegin(chunk);
682 LsanMetadata m(chunk);
683 if (m.allocated() && m.tag() != kReachable) {
684 ScanRangeForPointers(begin: chunk, end: chunk + m.requested_size(),
685 /* frontier */ nullptr, region_type: "HEAP", tag: kIndirectlyLeaked);
686 }
687}
688
689static void IgnoredSuppressedCb(uptr chunk, void *arg) {
690 CHECK(arg);
691 chunk = GetUserBegin(chunk);
692 LsanMetadata m(chunk);
693 if (!m.allocated() || m.tag() == kIgnored)
694 return;
695
696 const InternalMmapVector<u32> &suppressed =
697 *static_cast<const InternalMmapVector<u32> *>(arg);
698 uptr idx = InternalLowerBound(v: suppressed, val: m.stack_trace_id());
699 if (idx >= suppressed.size() || m.stack_trace_id() != suppressed[idx])
700 return;
701
702 LOG_POINTERS("Suppressed: chunk %p-%p of size %zu.\n", (void *)chunk,
703 (void *)(chunk + m.requested_size()), m.requested_size());
704 m.set_tag(kIgnored);
705}
706
707// ForEachChunk callback. If chunk is marked as ignored, adds its address to
708// frontier.
709static void CollectIgnoredCb(uptr chunk, void *arg) {
710 CHECK(arg);
711 chunk = GetUserBegin(chunk);
712 LsanMetadata m(chunk);
713 if (m.allocated() && m.tag() == kIgnored) {
714 LOG_POINTERS("Ignored: chunk %p-%p of size %zu.\n", (void *)chunk,
715 (void *)(chunk + m.requested_size()), m.requested_size());
716 reinterpret_cast<Frontier *>(arg)->push_back(element: chunk);
717 }
718}
719
720// Sets the appropriate tag on each chunk.
721static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads,
722 Frontier *frontier, ThreadID caller_tid,
723 uptr caller_sp) {
724 const InternalMmapVector<u32> &suppressed_stacks =
725 GetSuppressionContext()->GetSortedSuppressedStacks();
726 if (!suppressed_stacks.empty()) {
727 ForEachChunk(callback: IgnoredSuppressedCb,
728 arg: const_cast<InternalMmapVector<u32> *>(&suppressed_stacks));
729 }
730 ForEachChunk(callback: CollectIgnoredCb, arg: frontier);
731 ProcessGlobalRegions(frontier);
732 ProcessThreads(suspended_threads, frontier, caller_tid, caller_sp);
733 ProcessRootRegions(frontier);
734 FloodFillTag(frontier, tag: kReachable);
735
736 // The check here is relatively expensive, so we do this in a separate flood
737 // fill. That way we can skip the check for chunks that are reachable
738 // otherwise.
739 LOG_POINTERS("Processing platform-specific allocations.\n");
740 ProcessPlatformSpecificAllocations(frontier);
741 FloodFillTag(frontier, tag: kReachable);
742
743 // Iterate over leaked chunks and mark those that are reachable from other
744 // leaked chunks.
745 LOG_POINTERS("Scanning leaked chunks.\n");
746 ForEachChunk(callback: MarkIndirectlyLeakedCb, arg: nullptr);
747}
748
749// ForEachChunk callback. Resets the tags to pre-leak-check state.
750static void ResetTagsCb(uptr chunk, void *arg) {
751 (void)arg;
752 chunk = GetUserBegin(chunk);
753 LsanMetadata m(chunk);
754 if (m.allocated() && m.tag() != kIgnored)
755 m.set_tag(kDirectlyLeaked);
756}
757
758// ForEachChunk callback. Aggregates information about unreachable chunks into
759// a LeakReport.
760static void CollectLeaksCb(uptr chunk, void *arg) {
761 CHECK(arg);
762 LeakedChunks *leaks = reinterpret_cast<LeakedChunks *>(arg);
763 chunk = GetUserBegin(chunk);
764 LsanMetadata m(chunk);
765 if (!m.allocated())
766 return;
767 if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked)
768 leaks->push_back(element: {.chunk: chunk, .stack_trace_id: m.stack_trace_id(), .leaked_size: m.requested_size(), .tag: m.tag()});
769}
770
771void LeakSuppressionContext::PrintMatchedSuppressions() {
772 InternalMmapVector<Suppression *> matched;
773 context.GetMatched(matched: &matched);
774 if (!matched.size())
775 return;
776 const char *line = "-----------------------------------------------------";
777 Printf(format: "%s\n", line);
778 Printf(format: "Suppressions used:\n");
779 Printf(format: " count bytes template\n");
780 for (uptr i = 0; i < matched.size(); i++) {
781 Printf(format: "%7zu %10zu %s\n",
782 static_cast<uptr>(atomic_load_relaxed(a: &matched[i]->hit_count)),
783 matched[i]->weight, matched[i]->templ);
784 }
785 Printf(format: "%s\n\n", line);
786}
787
788# if SANITIZER_FUCHSIA
789
790// Fuchsia provides a libc interface that guarantees all threads are
791// covered, and SuspendedThreadList is never really used.
792static bool ReportUnsuspendedThreads(const SuspendedThreadsList &) {
793 return true;
794}
795
796# else // !SANITIZER_FUCHSIA
797
798static bool ReportUnsuspendedThreads(
799 const SuspendedThreadsList &suspended_threads) {
800 InternalMmapVector<ThreadID> threads(suspended_threads.ThreadCount());
801 for (uptr i = 0; i < suspended_threads.ThreadCount(); ++i)
802 threads[i] = suspended_threads.GetThreadID(index: i);
803
804 Sort(v: threads.data(), size: threads.size());
805
806 InternalMmapVector<ThreadID> known_threads;
807 GetRunningThreadsLocked(threads: &known_threads);
808
809 bool succeded = true;
810 for (auto os_id : known_threads) {
811 uptr i = InternalLowerBound(v: threads, val: os_id);
812 if (i >= threads.size() || threads[i] != os_id) {
813 succeded = false;
814 Report(
815 format: "Running thread %zu was not suspended. False leaks are possible.\n",
816 (usize)os_id);
817 }
818 }
819 return succeded;
820}
821
822# endif // !SANITIZER_FUCHSIA
823
824static void CheckForLeaksCallback(const SuspendedThreadsList &suspended_threads,
825 void *arg) {
826 CheckForLeaksParam *param = reinterpret_cast<CheckForLeaksParam *>(arg);
827 CHECK(param);
828 CHECK(!param->success);
829 if (!ReportUnsuspendedThreads(suspended_threads)) {
830 switch (flags()->thread_suspend_fail) {
831 case 0:
832 param->success = true;
833 return;
834 case 1:
835 break;
836 case 2:
837 // Will crash on return.
838 return;
839 }
840 }
841 ClassifyAllChunks(suspended_threads, frontier: &param->frontier, caller_tid: param->caller_tid,
842 caller_sp: param->caller_sp);
843 ForEachChunk(callback: CollectLeaksCb, arg: &param->leaks);
844 // Clean up for subsequent leak checks. This assumes we did not overwrite any
845 // kIgnored tags.
846 ForEachChunk(callback: ResetTagsCb, arg: nullptr);
847 param->success = true;
848}
849
850static bool PrintResults(LeakReport &report) {
851 uptr unsuppressed_count = report.UnsuppressedLeakCount();
852 if (unsuppressed_count) {
853 Decorator d;
854 Printf(
855 format: "\n"
856 "================================================================="
857 "\n");
858 Printf(format: "%s", d.Error());
859 Report(format: "ERROR: LeakSanitizer: detected memory leaks\n");
860 Printf(format: "%s", d.Default());
861 report.ReportTopLeaks(max_leaks: flags()->max_leaks);
862 }
863 if (common_flags()->print_suppressions)
864 GetSuppressionContext()->PrintMatchedSuppressions();
865 if (unsuppressed_count)
866 report.PrintSummary();
867 if ((unsuppressed_count && common_flags()->verbosity >= 2) ||
868 flags()->log_threads)
869 PrintThreads();
870 return unsuppressed_count;
871}
872
873static bool CheckForLeaksOnce() {
874 if (&__lsan_is_turned_off && __lsan_is_turned_off()) {
875 VReport(1, "LeakSanitizer is disabled\n");
876 return false;
877 }
878 VReport(1, "LeakSanitizer: checking for leaks\n");
879 // Inside LockStuffAndStopTheWorld we can't run symbolizer, so we can't match
880 // suppressions. However if a stack id was previously suppressed, it should be
881 // suppressed in future checks as well.
882 for (int i = 0;; ++i) {
883 EnsureMainThreadIDIsCorrect();
884 CheckForLeaksParam param;
885 // Capture calling thread's stack pointer early, to avoid false negatives.
886 // Old frame with dead pointers might be overlapped by new frame inside
887 // CheckForLeaks which does not use bytes with pointers before the
888 // threads are suspended and stack pointers captured.
889 param.caller_tid = GetTid();
890 param.caller_sp = reinterpret_cast<uptr>(__builtin_frame_address(0));
891 LockStuffAndStopTheWorld(callback: CheckForLeaksCallback, argument: &param);
892 if (!param.success) {
893 Report(format: "LeakSanitizer has encountered a fatal error.\n");
894 Report(
895 format: "HINT: For debugging, try setting environment variable "
896 "LSAN_OPTIONS=verbosity=1:log_threads=1\n");
897 Report(
898 format: "HINT: LeakSanitizer does not work under ptrace (strace, gdb, "
899 "etc)\n");
900 Report(
901 format: "HINT: LeakSanitizer requires ptrace_scope to be disabled (e.g. echo "
902 "0 > /proc/sys/kernel/yama/ptrace_scope)\n");
903 Die();
904 }
905 LeakReport leak_report;
906 leak_report.AddLeakedChunks(chunks: param.leaks);
907
908 // No new suppressions stacks, so rerun will not help and we can report.
909 if (!leak_report.ApplySuppressions())
910 return PrintResults(report&: leak_report);
911
912 // No indirect leaks to report, so we are done here.
913 if (!leak_report.IndirectUnsuppressedLeakCount())
914 return PrintResults(report&: leak_report);
915
916 if (i >= 8) {
917 Report(format: "WARNING: LeakSanitizer gave up on indirect leaks suppression.\n");
918 return PrintResults(report&: leak_report);
919 }
920
921 // We found a new previously unseen suppressed call stack. Rerun to make
922 // sure it does not hold indirect leaks.
923 VReport(1, "Rerun with %zu suppressed stacks.",
924 GetSuppressionContext()->GetSortedSuppressedStacks().size());
925 }
926}
927
928static bool CheckForLeaks() {
929 int leaking_tries = 0;
930 for (int i = 0; i < flags()->tries; ++i) leaking_tries += CheckForLeaksOnce();
931 return leaking_tries == flags()->tries;
932}
933
934static bool has_reported_leaks = false;
935bool HasReportedLeaks() { return has_reported_leaks; }
936
937void DoLeakCheck() {
938 Lock l(&global_mutex);
939 static bool already_done;
940 if (already_done)
941 return;
942 already_done = true;
943 has_reported_leaks = CheckForLeaks();
944 if (has_reported_leaks)
945 HandleLeaks();
946}
947
948static int DoRecoverableLeakCheck() {
949 Lock l(&global_mutex);
950 bool have_leaks = CheckForLeaks();
951 return have_leaks ? 1 : 0;
952}
953
954void DoRecoverableLeakCheckVoid() { DoRecoverableLeakCheck(); }
955
956///// LeakReport implementation. /////
957
958// A hard limit on the number of distinct leaks, to avoid quadratic complexity
959// in LeakReport::AddLeakedChunk(). We don't expect to ever see this many leaks
960// in real-world applications.
961// FIXME: Get rid of this limit by moving logic into DedupLeaks.
962const uptr kMaxLeaksConsidered = 5000;
963
964void LeakReport::AddLeakedChunks(const LeakedChunks &chunks) {
965 for (const LeakedChunk &leak : chunks) {
966 uptr chunk = leak.chunk;
967 u32 stack_trace_id = leak.stack_trace_id;
968 uptr leaked_size = leak.leaked_size;
969 ChunkTag tag = leak.tag;
970 CHECK(tag == kDirectlyLeaked || tag == kIndirectlyLeaked);
971
972 if (u32 resolution = flags()->resolution) {
973 StackTrace stack = StackDepotGet(id: stack_trace_id);
974 stack.size = Min(a: stack.size, b: resolution);
975 stack_trace_id = StackDepotPut(stack);
976 }
977
978 bool is_directly_leaked = (tag == kDirectlyLeaked);
979 uptr i;
980 for (i = 0; i < leaks_.size(); i++) {
981 if (leaks_[i].stack_trace_id == stack_trace_id &&
982 leaks_[i].is_directly_leaked == is_directly_leaked) {
983 leaks_[i].hit_count++;
984 leaks_[i].total_size += leaked_size;
985 break;
986 }
987 }
988 if (i == leaks_.size()) {
989 if (leaks_.size() == kMaxLeaksConsidered)
990 return;
991 Leak leak = {.id: next_id_++, /* hit_count */ 1,
992 .total_size: leaked_size, .stack_trace_id: stack_trace_id,
993 .is_directly_leaked: is_directly_leaked, /* is_suppressed */ false};
994 leaks_.push_back(element: leak);
995 }
996 if (flags()->report_objects) {
997 LeakedObject obj = {.leak_id: leaks_[i].id, .addr: GetUserAddr(chunk), .size: leaked_size};
998 leaked_objects_.push_back(element: obj);
999 }
1000 }
1001}
1002
1003static bool LeakComparator(const Leak &leak1, const Leak &leak2) {
1004 if (leak1.is_directly_leaked == leak2.is_directly_leaked)
1005 return leak1.total_size > leak2.total_size;
1006 else
1007 return leak1.is_directly_leaked;
1008}
1009
1010void LeakReport::ReportTopLeaks(uptr num_leaks_to_report) {
1011 CHECK(leaks_.size() <= kMaxLeaksConsidered);
1012 Printf(format: "\n");
1013 if (leaks_.size() == kMaxLeaksConsidered)
1014 Printf(
1015 format: "Too many leaks! Only the first %zu leaks encountered will be "
1016 "reported.\n",
1017 kMaxLeaksConsidered);
1018
1019 uptr unsuppressed_count = UnsuppressedLeakCount();
1020 if (num_leaks_to_report > 0 && num_leaks_to_report < unsuppressed_count)
1021 Printf(format: "The %zu top leak(s):\n", num_leaks_to_report);
1022 Sort(v: leaks_.data(), size: leaks_.size(), comp: &LeakComparator);
1023 uptr leaks_reported = 0;
1024 for (uptr i = 0; i < leaks_.size(); i++) {
1025 if (leaks_[i].is_suppressed)
1026 continue;
1027 PrintReportForLeak(index: i);
1028 leaks_reported++;
1029 if (leaks_reported == num_leaks_to_report)
1030 break;
1031 }
1032 if (leaks_reported < unsuppressed_count) {
1033 uptr remaining = unsuppressed_count - leaks_reported;
1034 Printf(format: "Omitting %zu more leak(s).\n", remaining);
1035 }
1036}
1037
1038void LeakReport::PrintReportForLeak(uptr index) {
1039 Decorator d;
1040 Printf(format: "%s", d.Leak());
1041 Printf(format: "%s leak of %zu byte(s) in %zu object(s) allocated from:\n",
1042 leaks_[index].is_directly_leaked ? "Direct" : "Indirect",
1043 leaks_[index].total_size, leaks_[index].hit_count);
1044 Printf(format: "%s", d.Default());
1045
1046 CHECK(leaks_[index].stack_trace_id);
1047 StackDepotGet(id: leaks_[index].stack_trace_id).Print();
1048
1049 if (flags()->report_objects) {
1050 Printf(format: "Objects leaked above:\n");
1051 PrintLeakedObjectsForLeak(index);
1052 Printf(format: "\n");
1053 }
1054}
1055
1056void LeakReport::PrintLeakedObjectsForLeak(uptr index) {
1057 u32 leak_id = leaks_[index].id;
1058 for (uptr j = 0; j < leaked_objects_.size(); j++) {
1059 if (leaked_objects_[j].leak_id == leak_id)
1060 Printf(format: "%p (%zu bytes)\n", (void *)leaked_objects_[j].addr,
1061 leaked_objects_[j].size);
1062 }
1063}
1064
1065void LeakReport::PrintSummary() {
1066 CHECK(leaks_.size() <= kMaxLeaksConsidered);
1067 uptr bytes = 0, allocations = 0;
1068 for (uptr i = 0; i < leaks_.size(); i++) {
1069 if (leaks_[i].is_suppressed)
1070 continue;
1071 bytes += leaks_[i].total_size;
1072 allocations += leaks_[i].hit_count;
1073 }
1074 InternalScopedString summary;
1075 summary.AppendF(format: "%zu byte(s) leaked in %zu allocation(s).", bytes,
1076 allocations);
1077 ReportErrorSummary(error_message: summary.data());
1078}
1079
1080uptr LeakReport::ApplySuppressions() {
1081 LeakSuppressionContext *suppressions = GetSuppressionContext();
1082 uptr new_suppressions = 0;
1083 for (uptr i = 0; i < leaks_.size(); i++) {
1084 if (suppressions->Suppress(stack_trace_id: leaks_[i].stack_trace_id, hit_count: leaks_[i].hit_count,
1085 total_size: leaks_[i].total_size)) {
1086 leaks_[i].is_suppressed = true;
1087 ++new_suppressions;
1088 }
1089 }
1090 return new_suppressions;
1091}
1092
1093uptr LeakReport::UnsuppressedLeakCount() {
1094 uptr result = 0;
1095 for (uptr i = 0; i < leaks_.size(); i++)
1096 if (!leaks_[i].is_suppressed)
1097 result++;
1098 return result;
1099}
1100
1101uptr LeakReport::IndirectUnsuppressedLeakCount() {
1102 uptr result = 0;
1103 for (uptr i = 0; i < leaks_.size(); i++)
1104 if (!leaks_[i].is_suppressed && !leaks_[i].is_directly_leaked)
1105 result++;
1106 return result;
1107}
1108
1109} // namespace __lsan
1110#else // CAN_SANITIZE_LEAKS
1111namespace __lsan {
1112void InitCommonLsan() {}
1113void DoLeakCheck() {}
1114void DoRecoverableLeakCheckVoid() {}
1115void DisableInThisThread() {}
1116void EnableInThisThread() {}
1117} // namespace __lsan
1118#endif // CAN_SANITIZE_LEAKS
1119
1120using namespace __lsan;
1121
1122extern "C" {
1123SANITIZER_INTERFACE_ATTRIBUTE
1124void __lsan_ignore_object(const void *p) {
1125#if CAN_SANITIZE_LEAKS
1126 if (!common_flags()->detect_leaks)
1127 return;
1128 // Cannot use PointsIntoChunk or LsanMetadata here, since the allocator is not
1129 // locked.
1130 Lock l(&global_mutex);
1131 IgnoreObjectResult res = IgnoreObject(p);
1132 if (res == kIgnoreObjectInvalid)
1133 VReport(1, "__lsan_ignore_object(): no heap object found at %p\n", p);
1134 if (res == kIgnoreObjectAlreadyIgnored)
1135 VReport(1,
1136 "__lsan_ignore_object(): "
1137 "heap object at %p is already being ignored\n",
1138 p);
1139 if (res == kIgnoreObjectSuccess)
1140 VReport(1, "__lsan_ignore_object(): ignoring heap object at %p\n", p);
1141#endif // CAN_SANITIZE_LEAKS
1142}
1143
1144SANITIZER_INTERFACE_ATTRIBUTE
1145void __lsan_register_root_region(const void *begin, uptr size) {
1146#if CAN_SANITIZE_LEAKS
1147 VReport(1, "Registered root region at %p of size %zu\n", begin, size);
1148 uptr b = reinterpret_cast<uptr>(begin);
1149 uptr e = b + size;
1150 CHECK_LT(b, e);
1151
1152 Lock l(&global_mutex);
1153 ++GetRootRegionsLocked()[{b, e}];
1154#endif // CAN_SANITIZE_LEAKS
1155}
1156
1157SANITIZER_INTERFACE_ATTRIBUTE
1158void __lsan_unregister_root_region(const void *begin, uptr size) {
1159#if CAN_SANITIZE_LEAKS
1160 uptr b = reinterpret_cast<uptr>(begin);
1161 uptr e = b + size;
1162 CHECK_LT(b, e);
1163 VReport(1, "Unregistered root region at %p of size %zu\n", begin, size);
1164
1165 {
1166 Lock l(&global_mutex);
1167 if (auto *f = GetRootRegionsLocked().find(Key: {b, e})) {
1168 if (--(f->second) == 0)
1169 GetRootRegionsLocked().erase(I: f);
1170 return;
1171 }
1172 }
1173 Report(
1174 format: "__lsan_unregister_root_region(): region at %p of size %zu has not "
1175 "been registered.\n",
1176 begin, size);
1177 Die();
1178#endif // CAN_SANITIZE_LEAKS
1179}
1180
1181SANITIZER_INTERFACE_ATTRIBUTE
1182void __lsan_disable() {
1183#if CAN_SANITIZE_LEAKS
1184 __lsan::DisableInThisThread();
1185#endif
1186}
1187
1188SANITIZER_INTERFACE_ATTRIBUTE
1189void __lsan_enable() {
1190#if CAN_SANITIZE_LEAKS
1191 __lsan::EnableInThisThread();
1192#endif
1193}
1194
1195SANITIZER_INTERFACE_ATTRIBUTE
1196void __lsan_do_leak_check() {
1197#if CAN_SANITIZE_LEAKS
1198 if (common_flags()->detect_leaks)
1199 __lsan::DoLeakCheck();
1200#endif // CAN_SANITIZE_LEAKS
1201}
1202
1203SANITIZER_INTERFACE_ATTRIBUTE
1204int __lsan_do_recoverable_leak_check() {
1205#if CAN_SANITIZE_LEAKS
1206 if (common_flags()->detect_leaks)
1207 return __lsan::DoRecoverableLeakCheck();
1208#endif // CAN_SANITIZE_LEAKS
1209 return 0;
1210}
1211
1212SANITIZER_INTERFACE_WEAK_DEF(const char *, __lsan_default_options, void) {
1213 return "";
1214}
1215
1216#if !SANITIZER_SUPPORTS_WEAK_HOOKS
1217SANITIZER_INTERFACE_WEAK_DEF(int, __lsan_is_turned_off, void) {
1218 return 0;
1219}
1220
1221SANITIZER_INTERFACE_WEAK_DEF(const char *, __lsan_default_suppressions, void) {
1222 return "";
1223}
1224#endif
1225} // extern "C"
1226