lsan_common.cc revision 28e41fed078ce7a457a1ab8de1b07407266695fb
1//=-- lsan_common.cc ------------------------------------------------------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file is a part of LeakSanitizer. 11// Implementation of common leak checking functionality. 12// 13//===----------------------------------------------------------------------===// 14 15#include "lsan_common.h" 16 17#include "sanitizer_common/sanitizer_common.h" 18#include "sanitizer_common/sanitizer_flags.h" 19#include "sanitizer_common/sanitizer_placement_new.h" 20#include "sanitizer_common/sanitizer_stackdepot.h" 21#include "sanitizer_common/sanitizer_stacktrace.h" 22#include "sanitizer_common/sanitizer_stoptheworld.h" 23#include "sanitizer_common/sanitizer_suppressions.h" 24#include "sanitizer_common/sanitizer_report_decorator.h" 25 26#if CAN_SANITIZE_LEAKS 27namespace __lsan { 28 29// This mutex is used to prevent races between DoLeakCheck and IgnoreObject. 30BlockingMutex global_mutex(LINKER_INITIALIZED); 31 32THREADLOCAL int disable_counter; 33bool DisabledInThisThread() { return disable_counter > 0; } 34 35Flags lsan_flags; 36 37static void InitializeFlags() { 38 Flags *f = flags(); 39 // Default values. 40 f->report_objects = false; 41 f->resolution = 0; 42 f->max_leaks = 0; 43 f->exitcode = 23; 44 f->suppressions=""; 45 f->use_registers = true; 46 f->use_globals = true; 47 f->use_stacks = true; 48 f->use_tls = true; 49 f->use_unaligned = false; 50 f->verbosity = 0; 51 f->log_pointers = false; 52 f->log_threads = false; 53 54 const char *options = GetEnv("LSAN_OPTIONS"); 55 if (options) { 56 ParseFlag(options, &f->use_registers, "use_registers"); 57 ParseFlag(options, &f->use_globals, "use_globals"); 58 ParseFlag(options, &f->use_stacks, "use_stacks"); 59 ParseFlag(options, &f->use_tls, "use_tls"); 60 ParseFlag(options, &f->use_unaligned, "use_unaligned"); 61 ParseFlag(options, &f->report_objects, "report_objects"); 62 ParseFlag(options, &f->resolution, "resolution"); 63 CHECK_GE(&f->resolution, 0); 64 ParseFlag(options, &f->max_leaks, "max_leaks"); 65 CHECK_GE(&f->max_leaks, 0); 66 ParseFlag(options, &f->verbosity, "verbosity"); 67 ParseFlag(options, &f->log_pointers, "log_pointers"); 68 ParseFlag(options, &f->log_threads, "log_threads"); 69 ParseFlag(options, &f->exitcode, "exitcode"); 70 ParseFlag(options, &f->suppressions, "suppressions"); 71 } 72} 73 74SuppressionContext *suppression_ctx; 75 76void InitializeSuppressions() { 77 CHECK(!suppression_ctx); 78 ALIGNED(64) static char placeholder_[sizeof(SuppressionContext)]; 79 suppression_ctx = new(placeholder_) SuppressionContext; 80 char *suppressions_from_file; 81 uptr buffer_size; 82 if (ReadFileToBuffer(flags()->suppressions, &suppressions_from_file, 83 &buffer_size, 1 << 26 /* max_len */)) 84 suppression_ctx->Parse(suppressions_from_file); 85 if (flags()->suppressions[0] && !buffer_size) { 86 Printf("LeakSanitizer: failed to read suppressions file '%s'\n", 87 flags()->suppressions); 88 Die(); 89 } 90 if (&__lsan_default_suppressions) 91 suppression_ctx->Parse(__lsan_default_suppressions()); 92} 93 94void InitCommonLsan() { 95 InitializeFlags(); 96 if (common_flags()->detect_leaks) { 97 // Initialization which can fail or print warnings should only be done if 98 // LSan is actually enabled. 99 InitializeSuppressions(); 100 InitializePlatformSpecificModules(); 101 } 102} 103 104class Decorator: private __sanitizer::AnsiColorDecorator { 105 public: 106 Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { } 107 const char *Error() { return Red(); } 108 const char *Leak() { return Blue(); } 109 const char *End() { return Default(); } 110}; 111 112static inline bool CanBeAHeapPointer(uptr p) { 113 // Since our heap is located in mmap-ed memory, we can assume a sensible lower 114 // bound on heap addresses. 115 const uptr kMinAddress = 4 * 4096; 116 if (p < kMinAddress) return false; 117#ifdef __x86_64__ 118 // Accept only canonical form user-space addresses. 119 return ((p >> 47) == 0); 120#else 121 return true; 122#endif 123} 124 125// Scans the memory range, looking for byte patterns that point into allocator 126// chunks. Marks those chunks with |tag| and adds them to |frontier|. 127// There are two usage modes for this function: finding reachable or ignored 128// chunks (|tag| = kReachable or kIgnored) and finding indirectly leaked chunks 129// (|tag| = kIndirectlyLeaked). In the second case, there's no flood fill, 130// so |frontier| = 0. 131void ScanRangeForPointers(uptr begin, uptr end, 132 Frontier *frontier, 133 const char *region_type, ChunkTag tag) { 134 const uptr alignment = flags()->pointer_alignment(); 135 if (flags()->log_pointers) 136 Report("Scanning %s range %p-%p.\n", region_type, begin, end); 137 uptr pp = begin; 138 if (pp % alignment) 139 pp = pp + alignment - pp % alignment; 140 for (; pp + sizeof(void *) <= end; pp += alignment) { // NOLINT 141 void *p = *reinterpret_cast<void **>(pp); 142 if (!CanBeAHeapPointer(reinterpret_cast<uptr>(p))) continue; 143 uptr chunk = PointsIntoChunk(p); 144 if (!chunk) continue; 145 // Pointers to self don't count. This matters when tag == kIndirectlyLeaked. 146 if (chunk == begin) continue; 147 LsanMetadata m(chunk); 148 // Reachable beats ignored beats leaked. 149 if (m.tag() == kReachable) continue; 150 if (m.tag() == kIgnored && tag != kReachable) continue; 151 m.set_tag(tag); 152 if (flags()->log_pointers) 153 Report("%p: found %p pointing into chunk %p-%p of size %zu.\n", pp, p, 154 chunk, chunk + m.requested_size(), m.requested_size()); 155 if (frontier) 156 frontier->push_back(chunk); 157 } 158} 159 160void ForEachExtraStackRangeCb(uptr begin, uptr end, void* arg) { 161 Frontier *frontier = reinterpret_cast<Frontier *>(arg); 162 ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable); 163} 164 165// Scans thread data (stacks and TLS) for heap pointers. 166static void ProcessThreads(SuspendedThreadsList const &suspended_threads, 167 Frontier *frontier) { 168 InternalScopedBuffer<uptr> registers(SuspendedThreadsList::RegisterCount()); 169 uptr registers_begin = reinterpret_cast<uptr>(registers.data()); 170 uptr registers_end = registers_begin + registers.size(); 171 for (uptr i = 0; i < suspended_threads.thread_count(); i++) { 172 uptr os_id = static_cast<uptr>(suspended_threads.GetThreadID(i)); 173 if (flags()->log_threads) Report("Processing thread %d.\n", os_id); 174 uptr stack_begin, stack_end, tls_begin, tls_end, cache_begin, cache_end; 175 bool thread_found = GetThreadRangesLocked(os_id, &stack_begin, &stack_end, 176 &tls_begin, &tls_end, 177 &cache_begin, &cache_end); 178 if (!thread_found) { 179 // If a thread can't be found in the thread registry, it's probably in the 180 // process of destruction. Log this event and move on. 181 if (flags()->log_threads) 182 Report("Thread %d not found in registry.\n", os_id); 183 continue; 184 } 185 uptr sp; 186 bool have_registers = 187 (suspended_threads.GetRegistersAndSP(i, registers.data(), &sp) == 0); 188 if (!have_registers) { 189 Report("Unable to get registers from thread %d.\n"); 190 // If unable to get SP, consider the entire stack to be reachable. 191 sp = stack_begin; 192 } 193 194 if (flags()->use_registers && have_registers) 195 ScanRangeForPointers(registers_begin, registers_end, frontier, 196 "REGISTERS", kReachable); 197 198 if (flags()->use_stacks) { 199 if (flags()->log_threads) 200 Report("Stack at %p-%p, SP = %p.\n", stack_begin, stack_end, sp); 201 if (sp < stack_begin || sp >= stack_end) { 202 // SP is outside the recorded stack range (e.g. the thread is running a 203 // signal handler on alternate stack). Again, consider the entire stack 204 // range to be reachable. 205 if (flags()->log_threads) 206 Report("WARNING: stack pointer not in stack range.\n"); 207 } else { 208 // Shrink the stack range to ignore out-of-scope values. 209 stack_begin = sp; 210 } 211 ScanRangeForPointers(stack_begin, stack_end, frontier, "STACK", 212 kReachable); 213 ForEachExtraStackRange(os_id, ForEachExtraStackRangeCb, frontier); 214 } 215 216 if (flags()->use_tls) { 217 if (flags()->log_threads) Report("TLS at %p-%p.\n", tls_begin, tls_end); 218 if (cache_begin == cache_end) { 219 ScanRangeForPointers(tls_begin, tls_end, frontier, "TLS", kReachable); 220 } else { 221 // Because LSan should not be loaded with dlopen(), we can assume 222 // that allocator cache will be part of static TLS image. 223 CHECK_LE(tls_begin, cache_begin); 224 CHECK_GE(tls_end, cache_end); 225 if (tls_begin < cache_begin) 226 ScanRangeForPointers(tls_begin, cache_begin, frontier, "TLS", 227 kReachable); 228 if (tls_end > cache_end) 229 ScanRangeForPointers(cache_end, tls_end, frontier, "TLS", kReachable); 230 } 231 } 232 } 233} 234 235static void FloodFillTag(Frontier *frontier, ChunkTag tag) { 236 while (frontier->size()) { 237 uptr next_chunk = frontier->back(); 238 frontier->pop_back(); 239 LsanMetadata m(next_chunk); 240 ScanRangeForPointers(next_chunk, next_chunk + m.requested_size(), frontier, 241 "HEAP", tag); 242 } 243} 244 245// ForEachChunk callback. If the chunk is marked as leaked, marks all chunks 246// which are reachable from it as indirectly leaked. 247static void MarkIndirectlyLeakedCb(uptr chunk, void *arg) { 248 chunk = GetUserBegin(chunk); 249 LsanMetadata m(chunk); 250 if (m.allocated() && m.tag() != kReachable) { 251 ScanRangeForPointers(chunk, chunk + m.requested_size(), 252 /* frontier */ 0, "HEAP", kIndirectlyLeaked); 253 } 254} 255 256// ForEachChunk callback. If chunk is marked as ignored, adds its address to 257// frontier. 258static void CollectIgnoredCb(uptr chunk, void *arg) { 259 CHECK(arg); 260 chunk = GetUserBegin(chunk); 261 LsanMetadata m(chunk); 262 if (m.allocated() && m.tag() == kIgnored) 263 reinterpret_cast<Frontier *>(arg)->push_back(chunk); 264} 265 266// Sets the appropriate tag on each chunk. 267static void ClassifyAllChunks(SuspendedThreadsList const &suspended_threads) { 268 // Holds the flood fill frontier. 269 Frontier frontier(GetPageSizeCached()); 270 271 if (flags()->use_globals) 272 ProcessGlobalRegions(&frontier); 273 ProcessThreads(suspended_threads, &frontier); 274 FloodFillTag(&frontier, kReachable); 275 // The check here is relatively expensive, so we do this in a separate flood 276 // fill. That way we can skip the check for chunks that are reachable 277 // otherwise. 278 if (flags()->log_pointers) 279 Report("Processing platform-specific allocations.\n"); 280 ProcessPlatformSpecificAllocations(&frontier); 281 FloodFillTag(&frontier, kReachable); 282 283 if (flags()->log_pointers) 284 Report("Scanning ignored chunks.\n"); 285 CHECK_EQ(0, frontier.size()); 286 ForEachChunk(CollectIgnoredCb, &frontier); 287 FloodFillTag(&frontier, kIgnored); 288 289 // Iterate over leaked chunks and mark those that are reachable from other 290 // leaked chunks. 291 if (flags()->log_pointers) 292 Report("Scanning leaked chunks.\n"); 293 ForEachChunk(MarkIndirectlyLeakedCb, 0 /* arg */); 294} 295 296static void PrintStackTraceById(u32 stack_trace_id) { 297 CHECK(stack_trace_id); 298 uptr size = 0; 299 const uptr *trace = StackDepotGet(stack_trace_id, &size); 300 StackTrace::PrintStack(trace, size); 301} 302 303// ForEachChunk callback. Aggregates unreachable chunks into a LeakReport. 304static void CollectLeaksCb(uptr chunk, void *arg) { 305 CHECK(arg); 306 LeakReport *leak_report = reinterpret_cast<LeakReport *>(arg); 307 chunk = GetUserBegin(chunk); 308 LsanMetadata m(chunk); 309 if (!m.allocated()) return; 310 if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked) { 311 uptr resolution = flags()->resolution; 312 if (resolution > 0) { 313 uptr size = 0; 314 const uptr *trace = StackDepotGet(m.stack_trace_id(), &size); 315 size = Min(size, resolution); 316 leak_report->Add(StackDepotPut(trace, size), m.requested_size(), m.tag()); 317 } else { 318 leak_report->Add(m.stack_trace_id(), m.requested_size(), m.tag()); 319 } 320 } 321} 322 323// ForEachChunkCallback. Prints addresses of unreachable chunks. 324static void PrintLeakedCb(uptr chunk, void *arg) { 325 chunk = GetUserBegin(chunk); 326 LsanMetadata m(chunk); 327 if (!m.allocated()) return; 328 if (m.tag() == kDirectlyLeaked || m.tag() == kIndirectlyLeaked) { 329 Printf("%s leaked %zu byte object at %p.\n", 330 m.tag() == kDirectlyLeaked ? "Directly" : "Indirectly", 331 m.requested_size(), chunk); 332 } 333} 334 335static void PrintMatchedSuppressions() { 336 InternalMmapVector<Suppression *> matched(1); 337 suppression_ctx->GetMatched(&matched); 338 if (!matched.size()) 339 return; 340 const char *line = "-----------------------------------------------------"; 341 Printf("%s\n", line); 342 Printf("Suppressions used:\n"); 343 Printf(" count bytes template\n"); 344 for (uptr i = 0; i < matched.size(); i++) 345 Printf("%7zu %10zu %s\n", static_cast<uptr>(matched[i]->hit_count), 346 matched[i]->weight, matched[i]->templ); 347 Printf("%s\n\n", line); 348} 349 350static void PrintLeaked() { 351 Printf("\n"); 352 Printf("Reporting individual objects:\n"); 353 ForEachChunk(PrintLeakedCb, 0 /* arg */); 354} 355 356struct DoLeakCheckParam { 357 bool success; 358 LeakReport leak_report; 359}; 360 361static void DoLeakCheckCallback(const SuspendedThreadsList &suspended_threads, 362 void *arg) { 363 DoLeakCheckParam *param = reinterpret_cast<DoLeakCheckParam *>(arg); 364 CHECK(param); 365 CHECK(!param->success); 366 CHECK(param->leak_report.IsEmpty()); 367 ClassifyAllChunks(suspended_threads); 368 ForEachChunk(CollectLeaksCb, ¶m->leak_report); 369 if (!param->leak_report.IsEmpty() && flags()->report_objects) 370 PrintLeaked(); 371 param->success = true; 372} 373 374void DoLeakCheck() { 375 EnsureMainThreadIDIsCorrect(); 376 BlockingMutexLock l(&global_mutex); 377 static bool already_done; 378 if (already_done) return; 379 already_done = true; 380 if (&__lsan_is_turned_off && __lsan_is_turned_off()) 381 return; 382 383 DoLeakCheckParam param; 384 param.success = false; 385 LockThreadRegistry(); 386 LockAllocator(); 387 StopTheWorld(DoLeakCheckCallback, ¶m); 388 UnlockAllocator(); 389 UnlockThreadRegistry(); 390 391 if (!param.success) { 392 Report("LeakSanitizer has encountered a fatal error.\n"); 393 Die(); 394 } 395 uptr have_unsuppressed = param.leak_report.ApplySuppressions(); 396 if (have_unsuppressed) { 397 Decorator d; 398 Printf("\n" 399 "=================================================================" 400 "\n"); 401 Printf("%s", d.Error()); 402 Report("ERROR: LeakSanitizer: detected memory leaks\n"); 403 Printf("%s", d.End()); 404 param.leak_report.PrintLargest(flags()->max_leaks); 405 } 406 if (have_unsuppressed || (flags()->verbosity >= 1)) { 407 PrintMatchedSuppressions(); 408 param.leak_report.PrintSummary(); 409 } 410 if (have_unsuppressed && flags()->exitcode) 411 internal__exit(flags()->exitcode); 412} 413 414static Suppression *GetSuppressionForAddr(uptr addr) { 415 static const uptr kMaxAddrFrames = 16; 416 InternalScopedBuffer<AddressInfo> addr_frames(kMaxAddrFrames); 417 for (uptr i = 0; i < kMaxAddrFrames; i++) new (&addr_frames[i]) AddressInfo(); 418 uptr addr_frames_num = Symbolizer::Get()->SymbolizeCode( 419 addr, addr_frames.data(), kMaxAddrFrames); 420 for (uptr i = 0; i < addr_frames_num; i++) { 421 Suppression* s; 422 if (suppression_ctx->Match(addr_frames[i].function, SuppressionLeak, &s) || 423 suppression_ctx->Match(addr_frames[i].file, SuppressionLeak, &s) || 424 suppression_ctx->Match(addr_frames[i].module, SuppressionLeak, &s)) 425 return s; 426 } 427 return 0; 428} 429 430static Suppression *GetSuppressionForStack(u32 stack_trace_id) { 431 uptr size = 0; 432 const uptr *trace = StackDepotGet(stack_trace_id, &size); 433 for (uptr i = 0; i < size; i++) { 434 Suppression *s = 435 GetSuppressionForAddr(StackTrace::GetPreviousInstructionPc(trace[i])); 436 if (s) return s; 437 } 438 return 0; 439} 440 441///// LeakReport implementation. ///// 442 443// A hard limit on the number of distinct leaks, to avoid quadratic complexity 444// in LeakReport::Add(). We don't expect to ever see this many leaks in 445// real-world applications. 446// FIXME: Get rid of this limit by changing the implementation of LeakReport to 447// use a hash table. 448const uptr kMaxLeaksConsidered = 5000; 449 450void LeakReport::Add(u32 stack_trace_id, uptr leaked_size, ChunkTag tag) { 451 CHECK(tag == kDirectlyLeaked || tag == kIndirectlyLeaked); 452 bool is_directly_leaked = (tag == kDirectlyLeaked); 453 for (uptr i = 0; i < leaks_.size(); i++) 454 if (leaks_[i].stack_trace_id == stack_trace_id && 455 leaks_[i].is_directly_leaked == is_directly_leaked) { 456 leaks_[i].hit_count++; 457 leaks_[i].total_size += leaked_size; 458 return; 459 } 460 if (leaks_.size() == kMaxLeaksConsidered) return; 461 Leak leak = { /* hit_count */ 1, leaked_size, stack_trace_id, 462 is_directly_leaked, /* is_suppressed */ false }; 463 leaks_.push_back(leak); 464} 465 466static bool LeakComparator(const Leak &leak1, const Leak &leak2) { 467 if (leak1.is_directly_leaked == leak2.is_directly_leaked) 468 return leak1.total_size > leak2.total_size; 469 else 470 return leak1.is_directly_leaked; 471} 472 473void LeakReport::PrintLargest(uptr num_leaks_to_print) { 474 CHECK(leaks_.size() <= kMaxLeaksConsidered); 475 Printf("\n"); 476 if (leaks_.size() == kMaxLeaksConsidered) 477 Printf("Too many leaks! Only the first %zu leaks encountered will be " 478 "reported.\n", 479 kMaxLeaksConsidered); 480 481 uptr unsuppressed_count = 0; 482 for (uptr i = 0; i < leaks_.size(); i++) 483 if (!leaks_[i].is_suppressed) unsuppressed_count++; 484 if (num_leaks_to_print > 0 && num_leaks_to_print < unsuppressed_count) 485 Printf("The %zu largest leak(s):\n", num_leaks_to_print); 486 InternalSort(&leaks_, leaks_.size(), LeakComparator); 487 uptr leaks_printed = 0; 488 Decorator d; 489 for (uptr i = 0; i < leaks_.size(); i++) { 490 if (leaks_[i].is_suppressed) continue; 491 Printf("%s", d.Leak()); 492 Printf("%s leak of %zu byte(s) in %zu object(s) allocated from:\n", 493 leaks_[i].is_directly_leaked ? "Direct" : "Indirect", 494 leaks_[i].total_size, leaks_[i].hit_count); 495 Printf("%s", d.End()); 496 PrintStackTraceById(leaks_[i].stack_trace_id); 497 leaks_printed++; 498 if (leaks_printed == num_leaks_to_print) break; 499 } 500 if (leaks_printed < unsuppressed_count) { 501 uptr remaining = unsuppressed_count - leaks_printed; 502 Printf("Omitting %zu more leak(s).\n", remaining); 503 } 504} 505 506void LeakReport::PrintSummary() { 507 CHECK(leaks_.size() <= kMaxLeaksConsidered); 508 uptr bytes = 0, allocations = 0; 509 for (uptr i = 0; i < leaks_.size(); i++) { 510 if (leaks_[i].is_suppressed) continue; 511 bytes += leaks_[i].total_size; 512 allocations += leaks_[i].hit_count; 513 } 514 InternalScopedBuffer<char> summary(kMaxSummaryLength); 515 internal_snprintf(summary.data(), summary.size(), 516 "%zu byte(s) leaked in %zu allocation(s).", bytes, 517 allocations); 518 ReportErrorSummary(summary.data()); 519} 520 521uptr LeakReport::ApplySuppressions() { 522 uptr unsuppressed_count = 0; 523 for (uptr i = 0; i < leaks_.size(); i++) { 524 Suppression *s = GetSuppressionForStack(leaks_[i].stack_trace_id); 525 if (s) { 526 s->weight += leaks_[i].total_size; 527 s->hit_count += leaks_[i].hit_count; 528 leaks_[i].is_suppressed = true; 529 } else { 530 unsuppressed_count++; 531 } 532 } 533 return unsuppressed_count; 534} 535} // namespace __lsan 536#endif // CAN_SANITIZE_LEAKS 537 538using namespace __lsan; // NOLINT 539 540extern "C" { 541SANITIZER_INTERFACE_ATTRIBUTE 542void __lsan_ignore_object(const void *p) { 543#if CAN_SANITIZE_LEAKS 544 if (!common_flags()->detect_leaks) 545 return; 546 // Cannot use PointsIntoChunk or LsanMetadata here, since the allocator is not 547 // locked. 548 BlockingMutexLock l(&global_mutex); 549 IgnoreObjectResult res = IgnoreObjectLocked(p); 550 if (res == kIgnoreObjectInvalid && flags()->verbosity >= 2) 551 Report("__lsan_ignore_object(): no heap object found at %p", p); 552 if (res == kIgnoreObjectAlreadyIgnored && flags()->verbosity >= 2) 553 Report("__lsan_ignore_object(): " 554 "heap object at %p is already being ignored\n", p); 555 if (res == kIgnoreObjectSuccess && flags()->verbosity >= 3) 556 Report("__lsan_ignore_object(): ignoring heap object at %p\n", p); 557#endif // CAN_SANITIZE_LEAKS 558} 559 560SANITIZER_INTERFACE_ATTRIBUTE 561void __lsan_disable() { 562#if CAN_SANITIZE_LEAKS 563 __lsan::disable_counter++; 564#endif 565} 566 567SANITIZER_INTERFACE_ATTRIBUTE 568void __lsan_enable() { 569#if CAN_SANITIZE_LEAKS 570 if (!__lsan::disable_counter && common_flags()->detect_leaks) { 571 Report("Unmatched call to __lsan_enable().\n"); 572 Die(); 573 } 574 __lsan::disable_counter--; 575#endif 576} 577 578SANITIZER_INTERFACE_ATTRIBUTE 579void __lsan_do_leak_check() { 580#if CAN_SANITIZE_LEAKS 581 if (common_flags()->detect_leaks) 582 __lsan::DoLeakCheck(); 583#endif // CAN_SANITIZE_LEAKS 584} 585 586#if !SANITIZER_SUPPORTS_WEAK_HOOKS 587SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE 588int __lsan_is_turned_off() { 589 return 0; 590} 591#endif 592} // extern "C" 593