blob: 440115517cead01dd8639435d758aa500360f759 [file] [log] [blame]
//===--- 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