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