/[chrome]/trunk/src/chrome/common/mac/objc_zombie.mm
Chromium logo

Contents of /trunk/src/chrome/common/mac/objc_zombie.mm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 288943 - (show annotations)
Tue Aug 12 12:03:48 2014 UTC (2 years, 2 months ago) by tapted@chromium.org
File size: 13835 byte(s)
Add ScopedObjCClassSwizzler in base/mac, absorbs objc_method_swizzle and ScopedClassSwizzler

ScopedClassSwizzler from ui/test is wanted for new tests where it can't
currently be accessed. It also re-implements a concept in
chrome/common/mac/objc_method_swizzle.*

This change adds base::mac::ScopedObjCClassSwizzler, merges concepts
from objc_method_swizzle, and adjusts chrome_browser_application_mac.mm
to use the new swizzler.

The test from objc_method_swizzle is adapted and extended for the scoped
swizzler.

BUG=378134
TEST=base_unittests

Review URL: https://codereview.chromium.org/345243007
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/common/mac/objc_zombie.h"
6
7 #include <AvailabilityMacros.h>
8
9 #include <execinfo.h>
10 #import <objc/runtime.h>
11
12 #include <algorithm>
13
14 #include "base/debug/crash_logging.h"
15 #include "base/debug/stack_trace.h"
16 #include "base/lazy_instance.h"
17 #include "base/logging.h"
18 #include "base/posix/eintr_wrapper.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/synchronization/lock.h"
21 #include "chrome/common/crash_keys.h"
22
23 #if !defined(OS_IOS) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6)
24 // Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet.
25 // The docs say it exists since 10.6 however.
26 OBJC_EXPORT void *objc_destructInstance(id obj);
27 #endif
28
29 // Deallocated objects are re-classed as |CrZombie|. No superclass
30 // because then the class would have to override many/most of the
31 // inherited methods (|NSObject| is like a category magnet!).
32 // Without the __attribute__, clang's -Wobjc-root-class warns on the missing
33 // superclass.
34 __attribute__((objc_root_class))
35 @interface CrZombie {
36 Class isa;
37 }
38 @end
39
40 // Objects with enough space are made into "fat" zombies, which
41 // directly remember which class they were until reallocated.
42 @interface CrFatZombie : CrZombie {
43 @public
44 Class wasa;
45 }
46 @end
47
48 namespace {
49
50 // The depth of backtrace to store with zombies. This directly influences
51 // the amount of memory required to track zombies, so should be kept as
52 // small as is useful. Unfortunately, too small and it won't poke through
53 // deep autorelease and event loop stacks.
54 // NOTE(shess): Breakpad currently restricts values to 255 bytes. The
55 // trace is hex-encoded with "0x" prefix and " " separators, meaning
56 // the maximum number of 32-bit items which can be encoded is 23.
57 const size_t kBacktraceDepth = 20;
58
59 // The original implementation for |-[NSObject dealloc]|.
60 IMP g_originalDeallocIMP = NULL;
61
62 // Classes which freed objects become. |g_fatZombieSize| is the
63 // minimum object size which can be made into a fat zombie (which can
64 // remember which class it was before free, even after falling off the
65 // treadmill).
66 Class g_zombieClass = Nil; // cached [CrZombie class]
67 Class g_fatZombieClass = Nil; // cached [CrFatZombie class]
68 size_t g_fatZombieSize = 0;
69
70 // Whether to zombie all freed objects, or only those which return YES
71 // from |-shouldBecomeCrZombie|.
72 BOOL g_zombieAllObjects = NO;
73
74 // Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|.
75 base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
76
77 // How many zombies to keep before freeing, and the current head of
78 // the circular buffer.
79 size_t g_zombieCount = 0;
80 size_t g_zombieIndex = 0;
81
82 typedef struct {
83 id object; // The zombied object.
84 Class wasa; // Value of |object->isa| before we replaced it.
85 void* trace[kBacktraceDepth]; // Backtrace at point of deallocation.
86 size_t traceDepth; // Actual depth of trace[].
87 } ZombieRecord;
88
89 ZombieRecord* g_zombies = NULL;
90
91 // Replacement |-dealloc| which turns objects into zombies and places
92 // them into |g_zombies| to be freed later.
93 void ZombieDealloc(id self, SEL _cmd) {
94 // This code should only be called when it is implementing |-dealloc|.
95 DCHECK_EQ(_cmd, @selector(dealloc));
96
97 // Use the original |-dealloc| if the object doesn't wish to be
98 // zombied.
99 if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) {
100 g_originalDeallocIMP(self, _cmd);
101 return;
102 }
103
104 Class wasa = object_getClass(self);
105 const size_t size = class_getInstanceSize(wasa);
106
107 // Destroy the instance by calling C++ destructors and clearing it
108 // to something unlikely to work well if someone references it.
109 // NOTE(shess): |object_dispose()| will call this again when the
110 // zombie falls off the treadmill! But by then |isa| will be a
111 // class without C++ destructors or associative references, so it
112 // won't hurt anything.
113 objc_destructInstance(self);
114 memset(self, '!', size);
115
116 // If the instance is big enough, make it into a fat zombie and have
117 // it remember the old |isa|. Otherwise make it a regular zombie.
118 // Setting |isa| rather than using |object_setClass()| because that
119 // function is implemented with a memory barrier. The runtime's
120 // |_internal_object_dispose()| (in objc-class.m) does this, so it
121 // should be safe (messaging free'd objects shouldn't be expected to
122 // be thread-safe in the first place).
123 #pragma clang diagnostic push // clang warns about direct access to isa.
124 #pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage"
125 if (size >= g_fatZombieSize) {
126 self->isa = g_fatZombieClass;
127 static_cast<CrFatZombie*>(self)->wasa = wasa;
128 } else {
129 self->isa = g_zombieClass;
130 }
131 #pragma clang diagnostic pop
132
133 // The new record to swap into |g_zombies|. If |g_zombieCount| is
134 // zero, then |self| will be freed immediately.
135 ZombieRecord zombieToFree = {self, wasa};
136 zombieToFree.traceDepth =
137 std::max(backtrace(zombieToFree.trace, kBacktraceDepth), 0);
138
139 // Don't involve the lock when creating zombies without a treadmill.
140 if (g_zombieCount > 0) {
141 base::AutoLock pin(g_lock.Get());
142
143 // Check the count again in a thread-safe manner.
144 if (g_zombieCount > 0) {
145 // Put the current object on the treadmill and keep the previous
146 // occupant.
147 std::swap(zombieToFree, g_zombies[g_zombieIndex]);
148
149 // Bump the index forward.
150 g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount;
151 }
152 }
153
154 // Do the free out here to prevent any chance of deadlock.
155 if (zombieToFree.object)
156 object_dispose(zombieToFree.object);
157 }
158
159 // Search the treadmill for |object| and fill in |*record| if found.
160 // Returns YES if found.
161 BOOL GetZombieRecord(id object, ZombieRecord* record) {
162 // Holding the lock is reasonable because this should be fast, and
163 // the process is going to crash presently anyhow.
164 base::AutoLock pin(g_lock.Get());
165 for (size_t i = 0; i < g_zombieCount; ++i) {
166 if (g_zombies[i].object == object) {
167 *record = g_zombies[i];
168 return YES;
169 }
170 }
171 return NO;
172 }
173
174 // Dump the symbols. This is pulled out into a function to make it
175 // easy to use DCHECK to dump only in debug builds.
176 BOOL DumpDeallocTrace(const void* const* array, int size) {
177 // Async-signal safe version of fputs, consistent with StackTrace::Print().
178 const char* message = "Backtrace from -dealloc:\n";
179 ignore_result(HANDLE_EINTR(write(STDERR_FILENO, message, strlen(message))));
180 base::debug::StackTrace(array, size).Print();
181
182 return YES;
183 }
184
185 // Log a message to a freed object. |wasa| is the object's original
186 // class. |aSelector| is the selector which the calling code was
187 // attempting to send. |viaSelector| is the selector of the
188 // dispatch-related method which is being invoked to send |aSelector|
189 // (for instance, -respondsToSelector:).
190 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
191 ZombieRecord record;
192 BOOL found = GetZombieRecord(object, &record);
193
194 // The object's class can be in the zombie record, but if that is
195 // not available it can also be in the object itself (in most cases).
196 Class wasa = Nil;
197 if (found) {
198 wasa = record.wasa;
199 } else if (object_getClass(object) == g_fatZombieClass) {
200 wasa = static_cast<CrFatZombie*>(object)->wasa;
201 }
202 const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
203
204 std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s",
205 wasaName, object, sel_getName(aSelector));
206 if (viaSelector != NULL) {
207 const char* viaName = sel_getName(viaSelector);
208 base::StringAppendF(&aString, " (via -%s)", viaName);
209 }
210
211 // Set a value for breakpad to report.
212 base::debug::SetCrashKeyValue(crash_keys::mac::kZombie, aString);
213
214 // Encode trace into a breakpad key.
215 if (found) {
216 base::debug::SetCrashKeyFromAddresses(
217 crash_keys::mac::kZombieTrace, record.trace, record.traceDepth);
218 }
219
220 // Log -dealloc backtrace in debug builds then crash with a useful
221 // stack trace.
222 if (found && record.traceDepth) {
223 DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
224 } else {
225 DLOG(WARNING) << "Unable to generate backtrace from -dealloc.";
226 }
227 DLOG(FATAL) << aString;
228
229 // This is how about:crash is implemented. Using instead of
230 // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
231 // stack more immediately obvious in crash dumps.
232 int* zero = NULL;
233 *zero = 0;
234 }
235
236 // Initialize our globals, returning YES on success.
237 BOOL ZombieInit() {
238 static BOOL initialized = NO;
239 if (initialized)
240 return YES;
241
242 Class rootClass = [NSObject class];
243 g_originalDeallocIMP =
244 class_getMethodImplementation(rootClass, @selector(dealloc));
245 // objc_getClass() so CrZombie doesn't need +class.
246 g_zombieClass = objc_getClass("CrZombie");
247 g_fatZombieClass = objc_getClass("CrFatZombie");
248 g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
249
250 if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
251 return NO;
252
253 initialized = YES;
254 return YES;
255 }
256
257 } // namespace
258
259 @implementation CrZombie
260
261 // The Objective-C runtime needs to be able to call this successfully.
262 + (void)initialize {
263 }
264
265 // Any method not explicitly defined will end up here, forcing a
266 // crash.
267 - (id)forwardingTargetForSelector:(SEL)aSelector {
268 ZombieObjectCrash(self, aSelector, NULL);
269 return nil;
270 }
271
272 // Override a few methods often used for dynamic dispatch to log the
273 // message the caller is attempting to send, rather than the utility
274 // method being used to send it.
275 - (BOOL)respondsToSelector:(SEL)aSelector {
276 ZombieObjectCrash(self, aSelector, _cmd);
277 return NO;
278 }
279
280 - (id)performSelector:(SEL)aSelector {
281 ZombieObjectCrash(self, aSelector, _cmd);
282 return nil;
283 }
284
285 - (id)performSelector:(SEL)aSelector withObject:(id)anObject {
286 ZombieObjectCrash(self, aSelector, _cmd);
287 return nil;
288 }
289
290 - (id)performSelector:(SEL)aSelector
291 withObject:(id)anObject
292 withObject:(id)anotherObject {
293 ZombieObjectCrash(self, aSelector, _cmd);
294 return nil;
295 }
296
297 - (void)performSelector:(SEL)aSelector
298 withObject:(id)anArgument
299 afterDelay:(NSTimeInterval)delay {
300 ZombieObjectCrash(self, aSelector, _cmd);
301 }
302
303 @end
304
305 @implementation CrFatZombie
306
307 // This implementation intentionally left empty.
308
309 @end
310
311 @implementation NSObject (CrZombie)
312
313 - (BOOL)shouldBecomeCrZombie {
314 return NO;
315 }
316
317 @end
318
319 namespace ObjcEvilDoers {
320
321 bool ZombieEnable(bool zombieAllObjects,
322 size_t zombieCount) {
323 // Only allow enable/disable on the main thread, just to keep things
324 // simple.
325 DCHECK([NSThread isMainThread]);
326
327 if (!ZombieInit())
328 return false;
329
330 g_zombieAllObjects = zombieAllObjects;
331
332 // Replace the implementation of -[NSObject dealloc].
333 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
334 if (!m)
335 return false;
336
337 const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
338 DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
339 prevDeallocIMP == (IMP)ZombieDealloc);
340
341 // Grab the current set of zombies. This is thread-safe because
342 // only the main thread can change these.
343 const size_t oldCount = g_zombieCount;
344 ZombieRecord* oldZombies = g_zombies;
345
346 {
347 base::AutoLock pin(g_lock.Get());
348
349 // Save the old index in case zombies need to be transferred.
350 size_t oldIndex = g_zombieIndex;
351
352 // Create the new zombie treadmill, disabling zombies in case of
353 // failure.
354 g_zombieIndex = 0;
355 g_zombieCount = zombieCount;
356 g_zombies = NULL;
357 if (g_zombieCount) {
358 g_zombies =
359 static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
360 if (!g_zombies) {
361 NOTREACHED();
362 g_zombies = oldZombies;
363 g_zombieCount = oldCount;
364 g_zombieIndex = oldIndex;
365 ZombieDisable();
366 return false;
367 }
368 }
369
370 // If the count is changing, allow some of the zombies to continue
371 // shambling forward.
372 const size_t sharedCount = std::min(oldCount, zombieCount);
373 if (sharedCount) {
374 // Get index of the first shared zombie.
375 oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
376
377 for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
378 DCHECK_LT(g_zombieIndex, g_zombieCount);
379 DCHECK_LT(oldIndex, oldCount);
380 std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
381 oldIndex = (oldIndex + 1) % oldCount;
382 }
383 g_zombieIndex %= g_zombieCount;
384 }
385 }
386
387 // Free the old treadmill and any remaining zombies.
388 if (oldZombies) {
389 for (size_t i = 0; i < oldCount; ++i) {
390 if (oldZombies[i].object)
391 object_dispose(oldZombies[i].object);
392 }
393 free(oldZombies);
394 }
395
396 return true;
397 }
398
399 void ZombieDisable() {
400 // Only allow enable/disable on the main thread, just to keep things
401 // simple.
402 DCHECK([NSThread isMainThread]);
403
404 // |ZombieInit()| was never called.
405 if (!g_originalDeallocIMP)
406 return;
407
408 // Put back the original implementation of -[NSObject dealloc].
409 Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
410 DCHECK(m);
411 method_setImplementation(m, g_originalDeallocIMP);
412
413 // Can safely grab this because it only happens on the main thread.
414 const size_t oldCount = g_zombieCount;
415 ZombieRecord* oldZombies = g_zombies;
416
417 {
418 base::AutoLock pin(g_lock.Get()); // In case any -dealloc are in progress.
419 g_zombieCount = 0;
420 g_zombies = NULL;
421 }
422
423 // Free any remaining zombies.
424 if (oldZombies) {
425 for (size_t i = 0; i < oldCount; ++i) {
426 if (oldZombies[i].object)
427 object_dispose(oldZombies[i].object);
428 }
429 free(oldZombies);
430 }
431 }
432
433 } // namespace ObjcEvilDoers

Properties

Name Value
svn:eol-style LF

Powered by ViewVC 1.1.5 ViewVC Help