Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(175)

Side by Side Diff: chrome/browser/ui/cocoa/objc_zombie.mm

Issue 7084017: [Mac] Use object_destructInstance() if available. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Per-runtime initialization, unit test, misc. Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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
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.
41 // |objc_destructInstance()| returns |void*|, pretending it has no
42 // return value makes the code simpler.
40 typedef void DestructFn(id obj); 43 typedef void DestructFn(id obj);
41 DestructFn* g_object_cxxDestruct = NULL; 44 DestructFn* g_objectDestruct = NULL;
42 45
43 // The original implementation for |-[NSObject dealloc]|. 46 // The original implementation for |-[NSObject dealloc]|.
44 IMP g_originalDeallocIMP = NULL; 47 IMP g_originalDeallocIMP = NULL;
45 48
46 // Classes which freed objects become. |g_fatZombieSize| is the 49 // Classes which freed objects become. |g_fatZombieSize| is the
47 // minimum object size which can be made into a fat zombie (which can 50 // 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 51 // remember which class it was before free, even after falling off the
49 // treadmill). 52 // treadmill).
50 Class g_zombieClass = Nil; // cached [CrZombie class] 53 Class g_zombieClass = Nil; // cached [CrZombie class]
51 Class g_fatZombieClass = Nil; // cached [CrFatZombie class] 54 Class g_fatZombieClass = Nil; // cached [CrFatZombie class]
(...skipping 11 matching lines...) Expand all
63 size_t g_zombieCount = 0; 66 size_t g_zombieCount = 0;
64 size_t g_zombieIndex = 0; 67 size_t g_zombieIndex = 0;
65 68
66 typedef struct { 69 typedef struct {
67 id object; // The zombied object. 70 id object; // The zombied object.
68 Class wasa; // Value of |object->isa| before we replaced it. 71 Class wasa; // Value of |object->isa| before we replaced it.
69 } ZombieRecord; 72 } ZombieRecord;
70 73
71 ZombieRecord* g_zombies = NULL; 74 ZombieRecord* g_zombies = NULL;
72 75
73 // Lookup the private |object_cxxDestruct| function and return a 76 const char* LookupObjcRuntimePath() {
74 // pointer to it. Returns |NULL| on failure. 77 const void* addr = reinterpret_cast<void*>(&object_getClass);
75 DestructFn* LookupObjectCxxDestruct() { 78 Dl_info info;
76 #if ARCH_CPU_64_BITS 79
77 // TODO(shess): Port to 64-bit. I believe using struct nlist_64 80 // |dladdr()| doesn't document how long |info| will stay valid...
78 // will suffice. http://crbug.com/44021 . 81 if (dladdr(addr, &info))
79 NOTIMPLEMENTED(); 82 return info.dli_fname;
83
80 return NULL; 84 return NULL;
81 #endif 85 }
86
87 // Lookup |objc_destructInstance()| dynamically because it isn't
88 // available on 10.5, but we link with the 10.5 SDK.
89 DestructFn* LookupObjectDestruct_10_6() {
90 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.
91 if (!objcRuntimePath)
92 return NULL;
93
94 void* handle = dlopen(objcRuntimePath, RTLD_LAZY | RTLD_LOCAL);
95 if (!handle)
96 return NULL;
97
98 void* fn = dlsym(handle, "objc_destructInstance");
99
100 // |fn| would normally be expected to become invalid after this
101 // |dlclose()|, but since the |dlopen()| was on a library
102 // containing an already-mapped symbol, it will remain valid.
103 dlclose(handle);
104 return reinterpret_cast<DestructFn*>(fn);
105 }
106
107 // Under 10.5 |object_cxxDestruct()| is used, unfortunately it is
108 // |__private_extern__| in the runtime, meaning |dlsym()| cannot reach it.
109 DestructFn* LookupObjectDestruct_10_5() {
110 // |nlist()| is only present for 32-bit.
111 #if ARCH_CPU_32_BITS
112 const char* objcRuntimePath = LookupObjcRuntimePath();
113 if (!objcRuntimePath)
114 return NULL;
82 115
83 struct nlist nl[3]; 116 struct nlist nl[3];
84 bzero(&nl, sizeof(nl)); 117 bzero(&nl, sizeof(nl));
85 118
86 nl[0].n_un.n_name = (char*)"_object_cxxDestruct"; 119 nl[0].n_un.n_name = const_cast<char*>("_object_cxxDestruct");
87 120
88 // My ability to calculate the base for offsets is apparently poor. 121 // My ability to calculate the base for offsets is apparently poor.
89 // Use |class_addIvar| as a known reference point. 122 // Use |class_addIvar| as a known reference point.
90 nl[1].n_un.n_name = (char*)"_class_addIvar"; 123 nl[1].n_un.n_name = const_cast<char*>("_class_addIvar");
91 124
92 if (nlist("/usr/lib/libobjc.dylib", nl) < 0 || 125 if (nlist(objcRuntimePath, nl) < 0 ||
93 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) 126 nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF)
94 return NULL; 127 return NULL;
95 128
96 return (DestructFn*)((char*)&class_addIvar - nl[1].n_value + nl[0].n_value); 129 // Back out offset to |class_addIvar()| to get the baseline, then
130 // add back offset to |object_cxxDestruct()|.
131 uintptr_t referenceAddr = reinterpret_cast<uintptr_t>(&class_addIvar);
132 referenceAddr -= nl[1].n_value;
133 referenceAddr += nl[0].n_value;
134
135 return reinterpret_cast<DestructFn*>(referenceAddr);
136 #endif
137
138 return NULL;
97 } 139 }
98 140
99 // Replacement |-dealloc| which turns objects into zombies and places 141 // Replacement |-dealloc| which turns objects into zombies and places
100 // them into |g_zombies| to be freed later. 142 // them into |g_zombies| to be freed later.
101 void ZombieDealloc(id self, SEL _cmd) { 143 void ZombieDealloc(id self, SEL _cmd) {
102 // This code should only be called when it is implementing |-dealloc|. 144 // This code should only be called when it is implementing |-dealloc|.
103 DCHECK_EQ(_cmd, @selector(dealloc)); 145 DCHECK_EQ(_cmd, @selector(dealloc));
104 146
105 // Use the original |-dealloc| if the object doesn't wish to be 147 // Use the original |-dealloc| if the object doesn't wish to be
106 // zombied. 148 // zombied.
107 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) { 149 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) {
108 g_originalDeallocIMP(self, _cmd); 150 g_originalDeallocIMP(self, _cmd);
109 return; 151 return;
110 } 152 }
111 153
112 // Use the original |-dealloc| if |object_cxxDestruct| was never 154 // Use the original |-dealloc| if |g_objectDestruct| was never
113 // initialized, because otherwise C++ destructors won't be called. 155 // initialized, because otherwise C++ destructors won't be called.
114 // This case should be impossible, but doing it wrong would cause 156 // This case should be impossible, but doing it wrong would cause
115 // terrible problems. 157 // terrible problems.
116 DCHECK(g_object_cxxDestruct); 158 DCHECK(g_objectDestruct);
117 if (!g_object_cxxDestruct) { 159 if (!g_objectDestruct) {
118 g_originalDeallocIMP(self, _cmd); 160 g_originalDeallocIMP(self, _cmd);
119 return; 161 return;
120 } 162 }
121 163
122 Class wasa = object_getClass(self); 164 Class wasa = object_getClass(self);
123 const size_t size = class_getInstanceSize(wasa); 165 const size_t size = class_getInstanceSize(wasa);
124 166
125 // Destroy the instance by calling C++ destructors and clearing it 167 // Destroy the instance by calling C++ destructors and clearing it
126 // to something unlikely to work well if someone references it. 168 // to something unlikely to work well if someone references it.
127 // NOTE(shess): |object_dispose()| will call |object_cxxDestruct()| 169 // NOTE(shess): |object_dispose()| will call this again when the
128 // again when the zombie falls off the treadmill! But by then |isa| 170 // zombie falls off the treadmill! But by then |isa| will be a
129 // will be something without destructors, so it won't hurt anything. 171 // class without C++ destructors or associative references, so it
130 (*g_object_cxxDestruct)(self); 172 // won't hurt anything.
173 (*g_objectDestruct)(self);
131 memset(self, '!', size); 174 memset(self, '!', size);
132 175
133 // If the instance is big enough, make it into a fat zombie and have 176 // If the instance is big enough, make it into a fat zombie and have
134 // it remember the old |isa|. Otherwise make it a regular zombie. 177 // it remember the old |isa|. Otherwise make it a regular zombie.
135 // Setting |isa| rather than using |object_setClass()| because that 178 // Setting |isa| rather than using |object_setClass()| because that
136 // function is implemented with a memory barrier. The runtime's 179 // function is implemented with a memory barrier. The runtime's
137 // |_internal_object_dispose()| (in objc-class.m) does this, so it 180 // |_internal_object_dispose()| (in objc-class.m) does this, so it
138 // should be safe (messaging free'd objects shouldn't be expected to 181 // should be safe (messaging free'd objects shouldn't be expected to
139 // be thread-safe in the first place). 182 // be thread-safe in the first place).
140 if (size >= g_fatZombieSize) { 183 if (size >= g_fatZombieSize) {
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
208 LOG(ERROR) << [aString UTF8String]; 251 LOG(ERROR) << [aString UTF8String];
209 252
210 // This is how about:crash is implemented. Using instead of 253 // This is how about:crash is implemented. Using instead of
211 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of 254 // |baes::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
212 // stack more immediately obvious in crash dumps. 255 // stack more immediately obvious in crash dumps.
213 int* zero = NULL; 256 int* zero = NULL;
214 *zero = 0; 257 *zero = 0;
215 } 258 }
216 259
217 // Initialize our globals, returning YES on success. 260 // Initialize our globals, returning YES on success.
218 BOOL ZombieInit() { 261 BOOL ZombieInit(ObjcEvilDoers::Runtime runtime) {
219 static BOOL initialized = NO; 262 static BOOL initialized = NO;
220 if (initialized) 263 if (initialized)
221 return YES; 264 return YES;
222 265
266 // TODO(shess): Since the current function is persistent, calling it
267 // multiple times with different values will not change the setup.
268 // For testing purposes it may be worthwhile to reset this function
269 // on each call.
270 if (runtime == ObjcEvilDoers::RUNTIME_10_5) {
271 g_objectDestruct = LookupObjectDestruct_10_5();
272 } else if (runtime == ObjcEvilDoers::RUNTIME_10_6) {
273 g_objectDestruct = LookupObjectDestruct_10_6();
274 } else if (runtime == ObjcEvilDoers::RUNTIME_GUESS) {
275 g_objectDestruct = LookupObjectDestruct_10_6();
276 if (!g_objectDestruct)
277 g_objectDestruct = LookupObjectDestruct_10_5();
278 }
279
223 Class rootClass = [NSObject class]; 280 Class rootClass = [NSObject class];
224
225 g_object_cxxDestruct = LookupObjectCxxDestruct();
226 g_originalDeallocIMP = 281 g_originalDeallocIMP =
227 class_getMethodImplementation(rootClass, @selector(dealloc)); 282 class_getMethodImplementation(rootClass, @selector(dealloc));
228 // objc_getClass() so CrZombie doesn't need +class. 283 // objc_getClass() so CrZombie doesn't need +class.
229 g_zombieClass = objc_getClass("CrZombie"); 284 g_zombieClass = objc_getClass("CrZombie");
230 g_fatZombieClass = objc_getClass("CrFatZombie"); 285 g_fatZombieClass = objc_getClass("CrFatZombie");
231 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass); 286 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
232 287
233 if (!g_object_cxxDestruct || !g_originalDeallocIMP || 288 if (!g_objectDestruct || !g_originalDeallocIMP ||
234 !g_zombieClass || !g_fatZombieClass) 289 !g_zombieClass || !g_fatZombieClass)
235 return NO; 290 return NO;
236 291
237 initialized = YES; 292 initialized = YES;
238 return YES; 293 return YES;
239 } 294 }
240 295
241 } // namespace 296 } // namespace
242 297
243 @implementation CrZombie 298 @implementation CrZombie
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
295 @implementation NSObject (CrZombie) 350 @implementation NSObject (CrZombie)
296 351
297 - (BOOL)shouldBecomeCrZombie { 352 - (BOOL)shouldBecomeCrZombie {
298 return NO; 353 return NO;
299 } 354 }
300 355
301 @end 356 @end
302 357
303 namespace ObjcEvilDoers { 358 namespace ObjcEvilDoers {
304 359
305 BOOL ZombieEnable(BOOL zombieAllObjects, 360 BOOL ZombieEnable(ObjcEvilDoers::Runtime runtime,
361 BOOL zombieAllObjects,
306 size_t zombieCount) { 362 size_t zombieCount) {
307 // Only allow enable/disable on the main thread, just to keep things 363 // Only allow enable/disable on the main thread, just to keep things
308 // simple. 364 // simple.
309 CHECK([NSThread isMainThread]); 365 CHECK([NSThread isMainThread]);
310 366
311 if (!ZombieInit()) 367 if (!ZombieInit(runtime))
312 return NO; 368 return NO;
313 369
314 g_zombieAllObjects = zombieAllObjects; 370 g_zombieAllObjects = zombieAllObjects;
315 371
316 // Replace the implementation of -[NSObject dealloc]. 372 // Replace the implementation of -[NSObject dealloc].
317 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc)); 373 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
318 if (!m) 374 if (!m)
319 return NO; 375 return NO;
320 376
321 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc); 377 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
408 if (oldZombies) { 464 if (oldZombies) {
409 for (size_t i = 0; i < oldCount; ++i) { 465 for (size_t i = 0; i < oldCount; ++i) {
410 if (oldZombies[i].object) 466 if (oldZombies[i].object)
411 object_dispose(oldZombies[i].object); 467 object_dispose(oldZombies[i].object);
412 } 468 }
413 free(oldZombies); 469 free(oldZombies);
414 } 470 }
415 } 471 }
416 472
417 } // namespace ObjcEvilDoers 473 } // namespace ObjcEvilDoers
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698