Chromium Code Reviews| Index: chrome/browser/ui/cocoa/objc_zombie.mm |
| diff --git a/chrome/browser/ui/cocoa/objc_zombie.mm b/chrome/browser/ui/cocoa/objc_zombie.mm |
| index 7dcbfc6b6b6bb79d6ebd63b65b782fe3bf860507..6ffa40f426e80c29695b0b0aa546d6ec230a643e 100644 |
| --- a/chrome/browser/ui/cocoa/objc_zombie.mm |
| +++ b/chrome/browser/ui/cocoa/objc_zombie.mm |
| @@ -33,12 +33,15 @@ |
| namespace { |
| -// |object_cxxDestruct()| is an Objective-C runtime function which |
| -// traverses the object's class tree for ".cxxdestruct" methods which |
| -// are run to call C++ destructors as part of |-dealloc|. The |
| -// function is not public, so must be looked up using nlist. |
| +// Function which destroys the contents of an object without freeing |
| +// the object. On 10.5 this is |object_cxxDestruct()|, which |
| +// traverses the class hierarchy to run the C++ destructors. On 10.6 |
| +// this is |objc_destructInstance()| which calls |
| +// |object_cxxDestruct()| and removes associative references. |
| +// |objc_destructInstance()| returns |void*|, pretending it has no |
| +// return value makes the code simpler. |
| typedef void DestructFn(id obj); |
| -DestructFn* g_object_cxxDestruct = NULL; |
| +DestructFn* g_objectDestruct = NULL; |
| // The original implementation for |-[NSObject dealloc]|. |
| IMP g_originalDeallocIMP = NULL; |
| @@ -70,30 +73,69 @@ typedef struct { |
| ZombieRecord* g_zombies = NULL; |
| -// Lookup the private |object_cxxDestruct| function and return a |
| -// pointer to it. Returns |NULL| on failure. |
| -DestructFn* LookupObjectCxxDestruct() { |
| -#if ARCH_CPU_64_BITS |
| - // TODO(shess): Port to 64-bit. I believe using struct nlist_64 |
| - // will suffice. http://crbug.com/44021 . |
| - NOTIMPLEMENTED(); |
| +const char* LookupObjcRuntimePath() { |
| + const void* addr = reinterpret_cast<void*>(&object_getClass); |
| + Dl_info info; |
| + |
| + // |dladdr()| doesn't document how long |info| will stay valid... |
| + if (dladdr(addr, &info)) |
| + return info.dli_fname; |
| + |
| return NULL; |
| -#endif |
| +} |
| + |
| +// Lookup |objc_destructInstance()| dynamically because it isn't |
| +// available on 10.5, but we link with the 10.5 SDK. |
| +DestructFn* LookupObjectDestruct_10_6() { |
| + const char* objcRuntimePath = LookupObjcRuntimePath(); |
|
Mark Mentovai
2011/06/07 21:42:57
Use C++ naming rules in C++ code: objc_runtime_pat
Scott Hess - ex-Googler
2011/06/07 23:48:21
Done.
|
| + if (!objcRuntimePath) |
| + return NULL; |
| + |
| + void* handle = dlopen(objcRuntimePath, RTLD_LAZY | RTLD_LOCAL); |
| + if (!handle) |
| + return NULL; |
| + |
| + void* fn = dlsym(handle, "objc_destructInstance"); |
| + |
| + // |fn| would normally be expected to become invalid after this |
| + // |dlclose()|, but since the |dlopen()| was on a library |
| + // containing an already-mapped symbol, it will remain valid. |
| + dlclose(handle); |
| + return reinterpret_cast<DestructFn*>(fn); |
| +} |
| + |
| +// Under 10.5 |object_cxxDestruct()| is used, unfortunately it is |
| +// |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it. |
| +DestructFn* LookupObjectDestruct_10_5() { |
| + // |nlist()| is only present for 32-bit. |
| +#if ARCH_CPU_32_BITS |
| + const char* objcRuntimePath = LookupObjcRuntimePath(); |
| + if (!objcRuntimePath) |
| + return NULL; |
| struct nlist nl[3]; |
| bzero(&nl, sizeof(nl)); |
| - nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; |
| + nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct"); |
| // My ability to calculate the base for offsets is apparently poor. |
| // Use |class_addIvar| as a known reference point. |
| - nl[1].n_un.n_name = (char*)"_class_addIvar"; |
| + nl[1].n_un.n_name = const_cast<char*>("_class_addIvar"); |
| - if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || |
| + if (nlist(objcRuntimePath, nl) < 0 || |
| nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) |
| return NULL; |
| - return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); |
| + // Back out offset to |class_addIvar()| to get the baseline, then |
| + // add back offset to |object_cxxDestruct()|. |
| + uintptr_t referenceAddr = reinterpret_cast<uintptr_t>(&class_addIvar); |
| + referenceAddr -= nl[1].n_value; |
| + referenceAddr += nl[0].n_value; |
| + |
| + return reinterpret_cast<DestructFn*>(referenceAddr); |
| +#endif |
| + |
| + return NULL; |
| } |
| // Replacement |-dealloc| which turns objects into zombies and places |
| @@ -109,12 +151,12 @@ void ZombieDealloc(id self, SEL _cmd) { |
| return; |
| } |
| - // Use the original |-dealloc| if |object_cxxDestruct| was never |
| + // Use the original |-dealloc| if |g_objectDestruct| was never |
| // initialized, because otherwise C++ destructors won't be called. |
| // This case should be impossible, but doing it wrong would cause |
| // terrible problems. |
| - DCHECK(g_object_cxxDestruct); |
| - if (!g_object_cxxDestruct) { |
| + DCHECK(g_objectDestruct); |
| + if (!g_objectDestruct) { |
| g_originalDeallocIMP(self, _cmd); |
| return; |
| } |
| @@ -124,10 +166,11 @@ void ZombieDealloc(id self, SEL _cmd) { |
| // Destroy the instance by calling C++ destructors and clearing it |
| // to something unlikely to work well if someone references it. |
| - // NOTE(shess): |object_dispose()| will call |object_cxxDestruct()| |
| - // again when the zombie falls off the treadmill! But by then |isa| |
| - // will be something without destructors, so it won't hurt anything. |
| - (*g_object_cxxDestruct)(self); |
| + // NOTE(shess): |object_dispose()| will call this again when the |
| + // zombie falls off the treadmill! But by then |isa| will be a |
| + // class without C++ destructors or associative references, so it |
| + // won't hurt anything. |
| + (*g_objectDestruct)(self); |
| memset(self, '!', size); |
| // If the instance is big enough, make it into a fat zombie and have |
| @@ -215,14 +258,26 @@ void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) { |
| } |
| // Initialize our globals, returning YES on success. |
| -BOOL ZombieInit() { |
| +BOOL ZombieInit(ObjcEvilDoers::Runtime runtime) { |
| static BOOL initialized = NO; |
| if (initialized) |
| return YES; |
| - Class rootClass = [NSObject class]; |
| + // TODO(shess): Since the current function is persistent, calling it |
| + // multiple times with different values will not change the setup. |
| + // For testing purposes it may be worthwhile to reset this function |
| + // on each call. |
| + if (runtime == ObjcEvilDoers::RUNTIME_10_5) { |
| + g_objectDestruct = LookupObjectDestruct_10_5(); |
| + } else if (runtime == ObjcEvilDoers::RUNTIME_10_6) { |
| + g_objectDestruct = LookupObjectDestruct_10_6(); |
| + } else if (runtime == ObjcEvilDoers::RUNTIME_GUESS) { |
| + g_objectDestruct = LookupObjectDestruct_10_6(); |
| + if (!g_objectDestruct) |
| + g_objectDestruct = LookupObjectDestruct_10_5(); |
| + } |
| - g_object_cxxDestruct = LookupObjectCxxDestruct(); |
| + Class rootClass = [NSObject class]; |
| g_originalDeallocIMP = |
| class_getMethodImplementation(rootClass, @selector(dealloc)); |
| // objc_getClass() so CrZombie doesn't need +class. |
| @@ -230,7 +285,7 @@ BOOL ZombieInit() { |
| g_fatZombieClass = objc_getClass("CrFatZombie"); |
| g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
| - if (!g_object_cxxDestruct || !g_originalDeallocIMP || |
| + if (!g_objectDestruct || !g_originalDeallocIMP || |
| !g_zombieClass || !g_fatZombieClass) |
| return NO; |
| @@ -302,13 +357,14 @@ BOOL ZombieInit() { |
| namespace ObjcEvilDoers { |
| -BOOL ZombieEnable(BOOL zombieAllObjects, |
| +BOOL ZombieEnable(ObjcEvilDoers::Runtime runtime, |
| + BOOL zombieAllObjects, |
| size_t zombieCount) { |
| // Only allow enable/disable on the main thread, just to keep things |
| // simple. |
| CHECK([NSThread isMainThread]); |
| - if (!ZombieInit()) |
| + if (!ZombieInit(runtime)) |
| return NO; |
| g_zombieAllObjects = zombieAllObjects; |