| //===--- Leaks.mm -----------------------------------------------*- C++ -*-===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // See Leaks.h for a description of this leaks detector. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER |
| |
| #include "Leaks.h" |
| #include "swift/Basic/Lazy.h" |
| #include "swift/Runtime/HeapObject.h" |
| #include "swift/Runtime/Metadata.h" |
| #import <objc/objc.h> |
| #import <objc/runtime.h> |
| #import <Foundation/Foundation.h> |
| #include <set> |
| #include <cstdio> |
| extern "C" { |
| #include <pthread.h> |
| } |
| |
| using namespace swift; |
| |
| //===----------------------------------------------------------------------===// |
| // Extra Interfaces |
| //===----------------------------------------------------------------------===// |
| |
| static void _swift_leaks_stopTrackingObjCObject(id obj); |
| static void _swift_leaks_startTrackingObjCObject(id obj); |
| |
| //===----------------------------------------------------------------------===// |
| // State |
| //===----------------------------------------------------------------------===// |
| |
| /// A set of allocated swift only objects that we are tracking for leaks. |
| static Lazy<std::set<HeapObject *>> TrackedSwiftObjects; |
| |
| /// A set of allocated objc objects that we are tracking for leaks. |
| static Lazy<std::set<id>> TrackedObjCObjects; |
| |
| /// Whether or not we should be collecting objects. |
| static bool ShouldTrackObjects = false; |
| |
| /// A course grain lock that we use to synchronize our leak dictionary. |
| static pthread_mutex_t LeaksMutex = PTHREAD_MUTEX_INITIALIZER; |
| |
| /// Where we store the dealloc, alloc, and allocWithZone functions we swizzled. |
| static IMP old_dealloc_fun; |
| static IMP old_alloc_fun; |
| static IMP old_allocWithZone_fun; |
| |
| //===----------------------------------------------------------------------===// |
| // Init and Deinit Code |
| //===----------------------------------------------------------------------===// |
| |
| static void __swift_leaks_dealloc(id self, SEL _cmd) { |
| _swift_leaks_stopTrackingObjCObject(self); |
| ((void (*)(id, SEL))old_dealloc_fun)(self, _cmd); |
| } |
| |
| static id __swift_leaks_alloc(id self, SEL _cmd) { |
| id result = ((id (*)(id, SEL))old_alloc_fun)(self, _cmd); |
| _swift_leaks_startTrackingObjCObject(result); |
| return result; |
| } |
| |
| static id __swift_leaks_allocWithZone(id self, SEL _cmd, id zone) { |
| id result = ((id (*)(id, SEL, id))old_allocWithZone_fun)(self, _cmd, zone); |
| _swift_leaks_startTrackingObjCObject(result); |
| return result; |
| } |
| |
| void _swift_leaks_startTrackingObjects(const char *name) { |
| pthread_mutex_lock(&LeaksMutex); |
| |
| // First clear our tracked objects set. |
| TrackedSwiftObjects->clear(); |
| TrackedObjCObjects->clear(); |
| |
| // Set that we should track objects. |
| ShouldTrackObjects = true; |
| |
| // Swizzle out -(void)dealloc, +(id)alloc, and +(id)allocWithZone: for our |
| // custom implementations. |
| IMP new_dealloc_fun = (IMP)__swift_leaks_dealloc; |
| IMP new_alloc_fun = (IMP)__swift_leaks_alloc; |
| IMP new_allocWithZone_fun = (IMP)__swift_leaks_allocWithZone; |
| |
| Method deallocMethod = |
| class_getInstanceMethod([NSObject class], @selector(dealloc)); |
| Method allocMethod = class_getClassMethod([NSObject class], @selector(alloc)); |
| Method allocWithZoneMethod = |
| class_getClassMethod([NSObject class], @selector(allocWithZone:)); |
| |
| old_dealloc_fun = method_setImplementation(deallocMethod, new_dealloc_fun); |
| old_alloc_fun = method_setImplementation(allocMethod, new_alloc_fun); |
| old_allocWithZone_fun = |
| method_setImplementation(allocWithZoneMethod, new_allocWithZone_fun); |
| |
| pthread_mutex_unlock(&LeaksMutex); |
| } |
| |
| /// This assumes that the LeaksMutex is already being held. |
| static void dumpSwiftHeapObjects() { |
| const char *comma = ""; |
| for (HeapObject *Obj : *TrackedSwiftObjects) { |
| const HeapMetadata *Metadata = Obj->metadata; |
| |
| fprintf(stderr, "%s", comma); |
| comma = ","; |
| |
| if (!Metadata) { |
| fprintf(stderr, "{\"type\": \"null\"}"); |
| continue; |
| } |
| |
| const char *kindDescriptor = ""; |
| switch (Metadata->getKind()) { |
| #define METADATAKIND(name, value) \ |
| case MetadataKind::name: \ |
| kindDescriptor = #name; \ |
| break; |
| #include "swift/ABI/MetadataKind.def" |
| default: |
| kindDescriptor = "unknown"; |
| break; |
| } |
| |
| if (auto *NTD = |
| Metadata->getTypeContextDescriptor()) { |
| fprintf(stderr, "{" |
| "\"type\": \"nominal\", " |
| "\"name\": \"%s\", " |
| "\"kind\": \"%s\"" |
| "}", |
| NTD->Name.get(), kindDescriptor); |
| continue; |
| } |
| |
| fprintf(stderr, "{\"type\": \"unknown\", \"kind\": \"%s\"}", |
| kindDescriptor); |
| } |
| } |
| |
| /// This assumes that the LeaksMutex is already being held. |
| static void dumpObjCHeapObjects() { |
| const char *comma = ""; |
| for (id Obj : *TrackedObjCObjects) { |
| // Just print out the class of Obj. |
| fprintf(stderr, "%s\"%s\"", comma, object_getClassName(Obj)); |
| comma = ","; |
| } |
| } |
| |
| int _swift_leaks_stopTrackingObjects(const char *name) { |
| pthread_mutex_lock(&LeaksMutex); |
| unsigned Result = TrackedSwiftObjects->size() + TrackedObjCObjects->size(); |
| |
| fprintf(stderr, "{\"name\":\"%s\", \"swift_count\": %u, \"objc_count\": %u, " |
| "\"swift_objects\": [", |
| name, unsigned(TrackedSwiftObjects->size()), |
| unsigned(TrackedObjCObjects->size())); |
| dumpSwiftHeapObjects(); |
| fprintf(stderr, "], \"objc_objects\": ["); |
| dumpObjCHeapObjects(); |
| fprintf(stderr, "]}\n"); |
| |
| fflush(stderr); |
| TrackedSwiftObjects->clear(); |
| TrackedObjCObjects->clear(); |
| ShouldTrackObjects = false; |
| |
| // Undo our swizzling. |
| Method deallocMethod = |
| class_getInstanceMethod([NSObject class], @selector(dealloc)); |
| Method allocMethod = class_getClassMethod([NSObject class], @selector(alloc)); |
| Method allocWithZoneMethod = |
| class_getClassMethod([NSObject class], @selector(allocWithZone:)); |
| |
| method_setImplementation(deallocMethod, old_dealloc_fun); |
| method_setImplementation(allocMethod, old_alloc_fun); |
| method_setImplementation(allocWithZoneMethod, old_allocWithZone_fun); |
| |
| pthread_mutex_unlock(&LeaksMutex); |
| return Result; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Tracking Code |
| //===----------------------------------------------------------------------===// |
| |
| void _swift_leaks_startTrackingObject(HeapObject *Object) { |
| pthread_mutex_lock(&LeaksMutex); |
| if (ShouldTrackObjects) { |
| TrackedSwiftObjects->insert(Object); |
| } |
| pthread_mutex_unlock(&LeaksMutex); |
| } |
| |
| void _swift_leaks_stopTrackingObject(HeapObject *Object) { |
| pthread_mutex_lock(&LeaksMutex); |
| TrackedSwiftObjects->erase(Object); |
| pthread_mutex_unlock(&LeaksMutex); |
| } |
| |
| static void _swift_leaks_startTrackingObjCObject(id Object) { |
| pthread_mutex_lock(&LeaksMutex); |
| if (ShouldTrackObjects) { |
| TrackedObjCObjects->insert(Object); |
| } |
| pthread_mutex_unlock(&LeaksMutex); |
| } |
| |
| static void _swift_leaks_stopTrackingObjCObject(id Object) { |
| pthread_mutex_lock(&LeaksMutex); |
| TrackedObjCObjects->erase(Object); |
| pthread_mutex_unlock(&LeaksMutex); |
| } |
| |
| #else |
| static char DummyDecl = ''; |
| #endif |