Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/cocoa/objc_zombie.h" | 5 #import "chrome/browser/ui/cocoa/objc_zombie.h" |
| 6 | 6 |
| 7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
| 8 #include <mach-o/dyld.h> | 8 #include <mach-o/dyld.h> |
| 9 #include <mach-o/nlist.h> | 9 #include <mach-o/nlist.h> |
| 10 | 10 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 26 // Objects with enough space are made into "fat" zombies, which | 26 // Objects with enough space are made into "fat" zombies, which |
| 27 // directly remember which class they were until reallocated. | 27 // directly remember which class they were until reallocated. |
| 28 @interface CrFatZombie : CrZombie { | 28 @interface CrFatZombie : CrZombie { |
| 29 @public | 29 @public |
| 30 Class wasa; | 30 Class wasa; |
| 31 } | 31 } |
| 32 @end | 32 @end |
| 33 | 33 |
| 34 namespace { | 34 namespace { |
| 35 | 35 |
| 36 // |object_cxxDestruct()| is an Objective-C runtime function which | 36 // Function which destroys the contents of an object without freeing |
| 37 // traverses the object's class tree for ".cxxdestruct" methods which | 37 // the object. On 10.5 this is |object_cxxDestruct()|, which |
| 38 // are run to call C++ destructors as part of |-dealloc|. The | 38 // traverses the class hierarchy to run the C++ destructors. On 10.6 |
| 39 // function is not public, so must be looked up using nlist. | 39 // this is |objc_destructInstance()| which calls |
| 40 // |object_cxxDestruct()| and removes associative references. | |
| 40 typedef void DestructFn(id obj); | 41 typedef void DestructFn(id obj); |
|
Mark Mentovai
2011/05/28 13:20:46
Technically the function pointer types are incompa
Scott Hess - ex-Googler
2011/05/31 20:22:30
Augmented the comment to that end.
| |
| 41 DestructFn* g_object_cxxDestruct = NULL; | 42 DestructFn* g_objectDestruct = NULL; |
| 42 | 43 |
| 43 // The original implementation for |-[NSObject dealloc]|. | 44 // The original implementation for |-[NSObject dealloc]|. |
| 44 IMP g_originalDeallocIMP = NULL; | 45 IMP g_originalDeallocIMP = NULL; |
| 45 | 46 |
| 46 // Classes which freed objects become. |g_fatZombieSize| is the | 47 // Classes which freed objects become. |g_fatZombieSize| is the |
| 47 // minimum object size which can be made into a fat zombie (which can | 48 // minimum object size which can be made into a fat zombie (which can |
| 48 // remember which class it was before free, even after falling off the | 49 // remember which class it was before free, even after falling off the |
| 49 // treadmill). | 50 // treadmill). |
| 50 Class g_zombieClass = Nil; // cached [CrZombie class] | 51 Class g_zombieClass = Nil; // cached [CrZombie class] |
| 51 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] | 52 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 63 size_t g_zombieCount = 0; | 64 size_t g_zombieCount = 0; |
| 64 size_t g_zombieIndex = 0; | 65 size_t g_zombieIndex = 0; |
| 65 | 66 |
| 66 typedef struct { | 67 typedef struct { |
| 67 id object; // The zombied object. | 68 id object; // The zombied object. |
| 68 Class wasa; // Value of |object->isa| before we replaced it. | 69 Class wasa; // Value of |object->isa| before we replaced it. |
| 69 } ZombieRecord; | 70 } ZombieRecord; |
| 70 | 71 |
| 71 ZombieRecord* g_zombies = NULL; | 72 ZombieRecord* g_zombies = NULL; |
| 72 | 73 |
| 73 // Lookup the private |object_cxxDestruct| function and return a | 74 // Lookup the private object destruction function and return a pointer |
| 74 // pointer to it. Returns |NULL| on failure. | 75 // to it. Returns |NULL| on failure. |
| 75 DestructFn* LookupObjectCxxDestruct() { | 76 DestructFn* LookupObjectDestruct() { |
| 76 #if ARCH_CPU_64_BITS | 77 #if ARCH_CPU_64_BITS |
|
Mark Mentovai
2011/05/28 13:20:46
Put this below the dlsym (but keep it before the n
Scott Hess - ex-Googler
2011/05/31 20:22:30
You sure? I think it's the right long-term decisi
Mark Mentovai
2011/05/31 21:00:57
shess wrote:
Scott Hess - ex-Googler
2011/06/07 21:24:51
Done. AFAICT, nlist() isn't present for 64-bit, a
| |
| 77 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 | 78 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 |
| 78 // will suffice. http://crbug.com/44021 . | 79 // will suffice. http://crbug.com/44021 . |
| 79 NOTIMPLEMENTED(); | 80 NOTIMPLEMENTED(); |
| 80 return NULL; | 81 return NULL; |
| 81 #endif | 82 #endif |
| 82 | 83 |
| 84 void* fn = dlsym(RTLD_DEFAULT, "objc_destructInstance"); | |
|
Mark Mentovai
2011/05/29 22:02:31
RTLD_DEFAULT kind of sucks for a few reasons. It’l
Scott Hess - ex-Googler
2011/05/31 20:22:30
Done. I do not know what I'm doing in this area,
| |
| 85 if (fn) | |
| 86 return (DestructFn*)fn; | |
|
Scott Hess - ex-Googler
2011/05/28 05:44:35
I need to clean up the casting in this function, a
Scott Hess - ex-Googler
2011/05/31 20:22:30
OK, casting is so annoying that I'm not sure I'm d
| |
| 87 | |
| 83 struct nlist nl[3]; | 88 struct nlist nl[3]; |
|
Mark Mentovai
2011/05/29 22:02:31
If we can’t find objc_destructInstance with dlsym
Scott Hess - ex-Googler
2011/05/31 20:22:30
Earlier when I put in the whitelist, I left it way
Mark Mentovai
2011/05/31 21:00:57
shess wrote:
| |
| 84 bzero(&nl, sizeof(nl)); | 89 bzero(&nl, sizeof(nl)); |
| 85 | 90 |
| 86 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; | 91 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; |
| 87 | 92 |
| 88 // My ability to calculate the base for offsets is apparently poor. | 93 // My ability to calculate the base for offsets is apparently poor. |
| 89 // Use |class_addIvar| as a known reference point. | 94 // Use |class_addIvar| as a known reference point. |
| 90 nl[1].n_un.n_name = (char*)"_class_addIvar"; | 95 nl[1].n_un.n_name = (char*)"_class_addIvar"; |
| 91 | 96 |
| 92 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || | 97 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || |
| 93 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) | 98 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) |
| 94 return NULL; | 99 return NULL; |
| 95 | 100 |
| 96 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); | 101 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); |
| 97 } | 102 } |
| 98 | 103 |
| 99 // Replacement |-dealloc| which turns objects into zombies and places | 104 // Replacement |-dealloc| which turns objects into zombies and places |
| 100 // them into |g_zombies| to be freed later. | 105 // them into |g_zombies| to be freed later. |
| 101 void ZombieDealloc(id self, SEL _cmd) { | 106 void ZombieDealloc(id self, SEL _cmd) { |
| 102 // This code should only be called when it is implementing |-dealloc|. | 107 // This code should only be called when it is implementing |-dealloc|. |
| 103 DCHECK_EQ(_cmd, @selector(dealloc)); | 108 DCHECK_EQ(_cmd, @selector(dealloc)); |
| 104 | 109 |
| 105 // Use the original |-dealloc| if the object doesn't wish to be | 110 // Use the original |-dealloc| if the object doesn't wish to be |
| 106 // zombied. | 111 // zombied. |
| 107 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { | 112 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { |
| 108 g_originalDeallocIMP(self, _cmd); | 113 g_originalDeallocIMP(self, _cmd); |
| 109 return; | 114 return; |
| 110 } | 115 } |
| 111 | 116 |
| 112 // Use the original |-dealloc| if |object_cxxDestruct| was never | 117 // Use the original |-dealloc| if |g_objectDestruct| was never |
| 113 // initialized, because otherwise C++ destructors won't be called. | 118 // initialized, because otherwise C++ destructors won't be called. |
| 114 // This case should be impossible, but doing it wrong would cause | 119 // This case should be impossible, but doing it wrong would cause |
| 115 // terrible problems. | 120 // terrible problems. |
| 116 DCHECK(g_object_cxxDestruct); | 121 DCHECK(g_objectDestruct); |
| 117 if (!g_object_cxxDestruct) { | 122 if (!g_objectDestruct) { |
| 118 g_originalDeallocIMP(self, _cmd); | 123 g_originalDeallocIMP(self, _cmd); |
| 119 return; | 124 return; |
| 120 } | 125 } |
| 121 | 126 |
| 122 Class wasa = object_getClass(self); | 127 Class wasa = object_getClass(self); |
| 123 const size_t size = class_getInstanceSize(wasa); | 128 const size_t size = class_getInstanceSize(wasa); |
| 124 | 129 |
| 125 // Destroy the instance by calling C++ destructors and clearing it | 130 // Destroy the instance by calling C++ destructors and clearing it |
| 126 // to something unlikely to work well if someone references it. | 131 // to something unlikely to work well if someone references it. |
| 127 (*g_object_cxxDestruct)(self); | 132 // NOTE(shess): |object_dispose()| will call this again when the |
| 133 // zombie falls off the treadmill! But by then |isa| will be a | |
| 134 // class without C++ destructors or associative references, so it | |
| 135 // won't hurt anything. | |
|
Scott Hess - ex-Googler
2011/05/28 05:44:35
Sorry, I think this comment snuck over when I brok
Mark Mentovai
2011/05/28 13:20:46
shess wrote:
| |
| 136 (*g_objectDestruct)(self); | |
| 128 memset(self, '!', size); | 137 memset(self, '!', size); |
| 129 | 138 |
| 130 // If the instance is big enough, make it into a fat zombie and have | 139 // If the instance is big enough, make it into a fat zombie and have |
| 131 // it remember the old |isa|. Otherwise make it a regular zombie. | 140 // it remember the old |isa|. Otherwise make it a regular zombie. |
| 132 // Setting |isa| rather than using |object_setClass()| because that | 141 // Setting |isa| rather than using |object_setClass()| because that |
| 133 // function is implemented with a memory barrier. The runtime's | 142 // function is implemented with a memory barrier. The runtime's |
| 134 // |_internal_object_dispose()| (in objc-class.m) does this, so it | 143 // |_internal_object_dispose()| (in objc-class.m) does this, so it |
| 135 // should be safe (messaging free'd objects shouldn't be expected to | 144 // should be safe (messaging free'd objects shouldn't be expected to |
| 136 // be thread-safe in the first place). | 145 // be thread-safe in the first place). |
| 137 if (size >= g_fatZombieSize) { | 146 if (size >= g_fatZombieSize) { |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 212 } | 221 } |
| 213 | 222 |
| 214 // Initialize our globals, returning YES on success. | 223 // Initialize our globals, returning YES on success. |
| 215 BOOL ZombieInit() { | 224 BOOL ZombieInit() { |
| 216 static BOOL initialized = NO; | 225 static BOOL initialized = NO; |
| 217 if (initialized) | 226 if (initialized) |
| 218 return YES; | 227 return YES; |
| 219 | 228 |
| 220 Class rootClass = [NSObject class]; | 229 Class rootClass = [NSObject class]; |
| 221 | 230 |
| 222 g_object_cxxDestruct = LookupObjectCxxDestruct(); | 231 g_objectDestruct = LookupObjectDestruct(); |
| 223 g_originalDeallocIMP = | 232 g_originalDeallocIMP = |
| 224 class_getMethodImplementation(rootClass, @selector(dealloc)); | 233 class_getMethodImplementation(rootClass, @selector(dealloc)); |
| 225 // objc_getClass() so CrZombie doesn't need +class. | 234 // objc_getClass() so CrZombie doesn't need +class. |
| 226 g_zombieClass = objc_getClass("CrZombie"); | 235 g_zombieClass = objc_getClass("CrZombie"); |
| 227 g_fatZombieClass = objc_getClass("CrFatZombie"); | 236 g_fatZombieClass = objc_getClass("CrFatZombie"); |
| 228 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); | 237 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); |
| 229 | 238 |
| 230 if (!g_object_cxxDestruct || !g_originalDeallocIMP || | 239 if (!g_objectDestruct || !g_originalDeallocIMP || |
| 231 !g_zombieClass || !g_fatZombieClass) | 240 !g_zombieClass || !g_fatZombieClass) |
| 232 return NO; | 241 return NO; |
| 233 | 242 |
| 234 initialized = YES; | 243 initialized = YES; |
| 235 return YES; | 244 return YES; |
| 236 } | 245 } |
| 237 | 246 |
| 238 } // namespace | 247 } // namespace |
| 239 | 248 |
| 240 @implementation CrZombie | 249 @implementation CrZombie |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 405 if (oldZombies) { | 414 if (oldZombies) { |
| 406 for (size_t i = 0; i < oldCount; ++i) { | 415 for (size_t i = 0; i < oldCount; ++i) { |
| 407 if (oldZombies[i].object) | 416 if (oldZombies[i].object) |
| 408 free(oldZombies[i].object); | 417 free(oldZombies[i].object); |
| 409 } | 418 } |
| 410 free(oldZombies); | 419 free(oldZombies); |
| 411 } | 420 } |
| 412 } | 421 } |
| 413 | 422 |
| 414 } // namespace ObjcEvilDoers | 423 } // namespace ObjcEvilDoers |
| OLD | NEW |