[compiler-rt] ed2c3f4 - [lsan][Darwin] Scan libdispatch and Foundation memory regions

Leonard Grey via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 14 13:47:14 PDT 2022


Author: Leonard Grey
Date: 2022-09-14T16:46:40-04:00
New Revision: ed2c3f46f5a74de9965c424a3a8ca99546b2c939

URL: https://github.com/llvm/llvm-project/commit/ed2c3f46f5a74de9965c424a3a8ca99546b2c939
DIFF: https://github.com/llvm/llvm-project/commit/ed2c3f46f5a74de9965c424a3a8ca99546b2c939.diff

LOG: [lsan][Darwin] Scan libdispatch and Foundation memory regions

libdispatch uses its own heap (_dispatch_main_heap) for some allocations, including the dispatch_continuation_t that holds a dispatch source's event handler.
Objective-C block trampolines (creating methods at runtime with a block as the implementations) use the VM_MEMORY_FOUNDATION region (see https://github.com/apple-oss-distributions/objc4/blob/8701d5672d3fd3cd817aeb84db1077aafe1a1604/runtime/objc-block-trampolines.mm#L371).

This change scans both regions to fix false positives. See tests for details; unfortunately I was unable to reduce the trampoline example with imp_implementationWithBlock on a new class, so I'm resorting to something close to the bug as seen in the wild.

Differential Revision: https://reviews.llvm.org/D129385

Added: 
    compiler-rt/test/lsan/TestCases/Darwin/dispatch_continuations.mm
    compiler-rt/test/lsan/TestCases/Darwin/trampoline.mm

Modified: 
    compiler-rt/lib/lsan/lsan_common_mac.cpp

Removed: 
    


################################################################################
diff  --git a/compiler-rt/lib/lsan/lsan_common_mac.cpp b/compiler-rt/lib/lsan/lsan_common_mac.cpp
index 26b623fb1d49..b6b15095744d 100644
--- a/compiler-rt/lib/lsan/lsan_common_mac.cpp
+++ b/compiler-rt/lib/lsan/lsan_common_mac.cpp
@@ -17,21 +17,36 @@
 
 #if CAN_SANITIZE_LEAKS && SANITIZER_APPLE
 
-#include "sanitizer_common/sanitizer_allocator_internal.h"
-#include "lsan_allocator.h"
+#  include <mach/mach.h>
+#  include <mach/vm_statistics.h>
+#  include <pthread.h>
 
-#include <pthread.h>
+#  include "lsan_allocator.h"
+#  include "sanitizer_common/sanitizer_allocator_internal.h"
+namespace __lsan {
 
-#include <mach/mach.h>
+enum class SeenRegion {
+  None = 0,
+  AllocOnce = 1 << 0,
+  LibDispatch = 1 << 1,
+  Foundation = 1 << 2,
+  All = AllocOnce | LibDispatch | Foundation
+};
+
+inline SeenRegion operator|(SeenRegion left, SeenRegion right) {
+  return static_cast<SeenRegion>(static_cast<int>(left) |
+                                 static_cast<int>(right));
+}
 
-// Only introduced in Mac OS X 10.9.
-#ifdef VM_MEMORY_OS_ALLOC_ONCE
-static const int kSanitizerVmMemoryOsAllocOnce = VM_MEMORY_OS_ALLOC_ONCE;
-#else
-static const int kSanitizerVmMemoryOsAllocOnce = 73;
-#endif
+inline SeenRegion &operator|=(SeenRegion &left, const SeenRegion &right) {
+  left = left | right;
+  return left;
+}
 
-namespace __lsan {
+struct RegionScanState {
+  SeenRegion seen_regions = SeenRegion::None;
+  bool in_libdispatch = false;
+};
 
 typedef struct {
   int disable_counter;
@@ -148,6 +163,7 @@ void ProcessPlatformSpecificAllocations(Frontier *frontier) {
 
   InternalMmapVectorNoCtor<RootRegion> const *root_regions = GetRootRegions();
 
+  RegionScanState scan_state;
   while (err == KERN_SUCCESS) {
     vm_size_t size = 0;
     unsigned depth = 1;
@@ -157,17 +173,35 @@ void ProcessPlatformSpecificAllocations(Frontier *frontier) {
                                (vm_region_info_t)&info, &count);
 
     uptr end_address = address + size;
-
-    // libxpc stashes some pointers in the Kernel Alloc Once page,
-    // make sure not to report those as leaks.
-    if (info.user_tag == kSanitizerVmMemoryOsAllocOnce) {
+    if (info.user_tag == VM_MEMORY_OS_ALLOC_ONCE) {
+      // libxpc stashes some pointers in the Kernel Alloc Once page,
+      // make sure not to report those as leaks.
+      scan_state.seen_regions |= SeenRegion::AllocOnce;
       ScanRangeForPointers(address, end_address, frontier, "GLOBAL",
                            kReachable);
+    } else if (info.user_tag == VM_MEMORY_FOUNDATION) {
+      // Objective-C block trampolines use the Foundation region.
+      scan_state.seen_regions |= SeenRegion::Foundation;
+      ScanRangeForPointers(address, end_address, frontier, "GLOBAL",
+                           kReachable);
+    } else if (info.user_tag == VM_MEMORY_LIBDISPATCH) {
+      // Dispatch continuations use the libdispatch region. Empirically, there
+      // can be more than one region with this tag, so we'll optimistically
+      // assume that they're continguous. Otherwise, we would need to scan every
+      // region to ensure we find them all.
+      scan_state.in_libdispatch = true;
+      ScanRangeForPointers(address, end_address, frontier, "GLOBAL",
+                           kReachable);
+    } else if (scan_state.in_libdispatch) {
+      scan_state.seen_regions |= SeenRegion::LibDispatch;
+      scan_state.in_libdispatch = false;
+    }
 
-      // Recursing over the full memory map is very slow, break out
-      // early if we don't need the full iteration.
-      if (!flags()->use_root_regions || !root_regions->size())
-        break;
+    // Recursing over the full memory map is very slow, break out
+    // early if we don't need the full iteration.
+    if (scan_state.seen_regions == SeenRegion::All &&
+        !(flags()->use_root_regions && root_regions->size() > 0)) {
+      break;
     }
 
     // This additional root region scan is required on Darwin in order to
@@ -199,6 +233,6 @@ void LockStuffAndStopTheWorld(StopTheWorldCallback callback,
   StopTheWorld(callback, argument);
 }
 
-} // namespace __lsan
+}  // namespace __lsan
 
 #endif // CAN_SANITIZE_LEAKS && SANITIZER_APPLE

diff  --git a/compiler-rt/test/lsan/TestCases/Darwin/dispatch_continuations.mm b/compiler-rt/test/lsan/TestCases/Darwin/dispatch_continuations.mm
new file mode 100644
index 000000000000..d0420d85bae7
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/Darwin/dispatch_continuations.mm
@@ -0,0 +1,24 @@
+// Test that dispatch continuation memory region is scanned.
+// RUN: %clangxx_lsan %s  -o %t -framework Foundation
+// RUN: %env_lsan_opts="report_objects=1" %run %t 2>&1 && echo "" | FileCheck %s
+
+#include <dispatch/dispatch.h>
+#include <sanitizer/lsan_interface.h>
+
+int main() {
+  // Reduced from `CFRunLoopCreate`
+  dispatch_queue_t fake_rl_queue = dispatch_get_global_queue(2, 0);
+  dispatch_source_t timer =
+      dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, fake_rl_queue);
+  dispatch_source_set_event_handler(timer, ^{
+                                    });
+  dispatch_source_set_timer(timer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER,
+                            321);
+  dispatch_resume(timer);
+  __lsan_do_leak_check();
+  dispatch_source_cancel(timer);
+  dispatch_release(timer);
+  return 0;
+}
+
+// CHECK-NOT: LeakSanitizer: detected memory leaks

diff  --git a/compiler-rt/test/lsan/TestCases/Darwin/trampoline.mm b/compiler-rt/test/lsan/TestCases/Darwin/trampoline.mm
new file mode 100644
index 000000000000..ce3182fbde8d
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/Darwin/trampoline.mm
@@ -0,0 +1,18 @@
+// Test that the memory region  that contains Objective-C block trampolines
+// is scanned.
+// FIXME: Find a way to reduce this without AppKit to remove Mac requirement.
+// UNSUPPORTED: ios
+// RUN: %clangxx_lsan %s  -o %t -framework Cocoa -fno-objc-arc
+// RUN: %env_lsan_opts="report_objects=1" %run %t 2>&1  && echo "" | FileCheck %s
+
+#import <Cocoa/Cocoa.h>
+
+#include <sanitizer/lsan_interface.h>
+
+int main() {
+  NSView *view =
+      [[[NSView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)] autorelease];
+  __lsan_do_leak_check();
+  return 0;
+}
+// CHECK-NOT: LeakSanitizer: detected memory leaks


        


More information about the llvm-commits mailing list