//===--- 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 #import #import #include #include extern "C" { #include } 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> TrackedSwiftObjects; /// A set of allocated objc objects that we are tracking for leaks. static Lazy> 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 = '\0'; #endif