Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / common / mac / objc_zombie.mm
blobab62cae7d53162c50b8feee1982c1d4aad9b582c
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.
5 #import "chrome/common/mac/objc_zombie.h"
7 #include <AvailabilityMacros.h>
9 #include <execinfo.h>
10 #import <objc/runtime.h>
12 #include <algorithm>
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/strings/stringprintf.h"
19 #include "base/synchronization/lock.h"
20 #include "chrome/common/crash_keys.h"
21 #import "chrome/common/mac/objc_method_swizzle.h"
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
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;
38 @end
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;
46 @end
48 namespace {
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;
59 // The original implementation for |-[NSObject dealloc]|.
60 IMP g_originalDeallocIMP = NULL;
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;
70 // Whether to zombie all freed objects, or only those which return YES
71 // from |-shouldBecomeCrZombie|.
72 BOOL g_zombieAllObjects = NO;
74 // Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|.
75 base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
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;
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;
89 ZombieRecord* g_zombies = NULL;
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));
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   }
104   Class wasa = object_getClass(self);
105   const size_t size = class_getInstanceSize(wasa);
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);
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
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);
139   // Don't involve the lock when creating zombies without a treadmill.
140   if (g_zombieCount > 0) {
141     base::AutoLock pin(g_lock.Get());
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]);
149       // Bump the index forward.
150       g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount;
151     }
152   }
154   // Do the free out here to prevent any chance of deadlock.
155   if (zombieToFree.object)
156     object_dispose(zombieToFree.object);
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;
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   fprintf(stderr, "Backtrace from -dealloc:\n");
178   base::debug::StackTrace(array, size).Print();
180   return YES;
183 // Log a message to a freed object.  |wasa| is the object's original
184 // class.  |aSelector| is the selector which the calling code was
185 // attempting to send.  |viaSelector| is the selector of the
186 // dispatch-related method which is being invoked to send |aSelector|
187 // (for instance, -respondsToSelector:).
188 void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
189   ZombieRecord record;
190   BOOL found = GetZombieRecord(object, &record);
192   // The object's class can be in the zombie record, but if that is
193   // not available it can also be in the object itself (in most cases).
194   Class wasa = Nil;
195   if (found) {
196     wasa = record.wasa;
197   } else if (object_getClass(object) == g_fatZombieClass) {
198     wasa = static_cast<CrFatZombie*>(object)->wasa;
199   }
200   const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
202   std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s",
203       wasaName, object, sel_getName(aSelector));
204   if (viaSelector != NULL) {
205     const char* viaName = sel_getName(viaSelector);
206     base::StringAppendF(&aString, " (via -%s)", viaName);
207   }
209   // Set a value for breakpad to report.
210   base::debug::SetCrashKeyValue(crash_keys::mac::kZombie, aString);
212   // Encode trace into a breakpad key.
213   if (found) {
214     base::debug::SetCrashKeyFromAddresses(
215         crash_keys::mac::kZombieTrace, record.trace, record.traceDepth);
216   }
218   // Log -dealloc backtrace in debug builds then crash with a useful
219   // stack trace.
220   if (found && record.traceDepth) {
221     DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
222   } else {
223     DLOG(INFO) << "Unable to generate backtrace from -dealloc.";
224   }
225   DLOG(FATAL) << aString;
227   // This is how about:crash is implemented.  Using instead of
228   // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
229   // stack more immediately obvious in crash dumps.
230   int* zero = NULL;
231   *zero = 0;
234 // Initialize our globals, returning YES on success.
235 BOOL ZombieInit() {
236   static BOOL initialized = NO;
237   if (initialized)
238     return YES;
240   Class rootClass = [NSObject class];
241   g_originalDeallocIMP =
242       class_getMethodImplementation(rootClass, @selector(dealloc));
243   // objc_getClass() so CrZombie doesn't need +class.
244   g_zombieClass = objc_getClass("CrZombie");
245   g_fatZombieClass = objc_getClass("CrFatZombie");
246   g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
248   if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
249     return NO;
251   initialized = YES;
252   return YES;
255 }  // namespace
257 @implementation CrZombie
259 // The Objective-C runtime needs to be able to call this successfully.
260 + (void)initialize {
263 // Any method not explicitly defined will end up here, forcing a
264 // crash.
265 - (id)forwardingTargetForSelector:(SEL)aSelector {
266   ZombieObjectCrash(self, aSelector, NULL);
267   return nil;
270 // Override a few methods often used for dynamic dispatch to log the
271 // message the caller is attempting to send, rather than the utility
272 // method being used to send it.
273 - (BOOL)respondsToSelector:(SEL)aSelector {
274   ZombieObjectCrash(self, aSelector, _cmd);
275   return NO;
278 - (id)performSelector:(SEL)aSelector {
279   ZombieObjectCrash(self, aSelector, _cmd);
280   return nil;
283 - (id)performSelector:(SEL)aSelector withObject:(id)anObject {
284   ZombieObjectCrash(self, aSelector, _cmd);
285   return nil;
288 - (id)performSelector:(SEL)aSelector
289            withObject:(id)anObject
290            withObject:(id)anotherObject {
291   ZombieObjectCrash(self, aSelector, _cmd);
292   return nil;
295 - (void)performSelector:(SEL)aSelector
296              withObject:(id)anArgument
297              afterDelay:(NSTimeInterval)delay {
298   ZombieObjectCrash(self, aSelector, _cmd);
301 @end
303 @implementation CrFatZombie
305 // This implementation intentionally left empty.
307 @end
309 @implementation NSObject (CrZombie)
311 - (BOOL)shouldBecomeCrZombie {
312   return NO;
315 @end
317 namespace ObjcEvilDoers {
319 bool ZombieEnable(bool zombieAllObjects,
320                   size_t zombieCount) {
321   // Only allow enable/disable on the main thread, just to keep things
322   // simple.
323   DCHECK([NSThread isMainThread]);
325   if (!ZombieInit())
326     return false;
328   g_zombieAllObjects = zombieAllObjects;
330   // Replace the implementation of -[NSObject dealloc].
331   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
332   if (!m)
333     return false;
335   const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
336   DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
337          prevDeallocIMP == (IMP)ZombieDealloc);
339   // Grab the current set of zombies.  This is thread-safe because
340   // only the main thread can change these.
341   const size_t oldCount = g_zombieCount;
342   ZombieRecord* oldZombies = g_zombies;
344   {
345     base::AutoLock pin(g_lock.Get());
347     // Save the old index in case zombies need to be transferred.
348     size_t oldIndex = g_zombieIndex;
350     // Create the new zombie treadmill, disabling zombies in case of
351     // failure.
352     g_zombieIndex = 0;
353     g_zombieCount = zombieCount;
354     g_zombies = NULL;
355     if (g_zombieCount) {
356       g_zombies =
357           static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
358       if (!g_zombies) {
359         NOTREACHED();
360         g_zombies = oldZombies;
361         g_zombieCount = oldCount;
362         g_zombieIndex = oldIndex;
363         ZombieDisable();
364         return false;
365       }
366     }
368     // If the count is changing, allow some of the zombies to continue
369     // shambling forward.
370     const size_t sharedCount = std::min(oldCount, zombieCount);
371     if (sharedCount) {
372       // Get index of the first shared zombie.
373       oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
375       for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
376         DCHECK_LT(g_zombieIndex, g_zombieCount);
377         DCHECK_LT(oldIndex, oldCount);
378         std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
379         oldIndex = (oldIndex + 1) % oldCount;
380       }
381       g_zombieIndex %= g_zombieCount;
382     }
383   }
385   // Free the old treadmill and any remaining zombies.
386   if (oldZombies) {
387     for (size_t i = 0; i < oldCount; ++i) {
388       if (oldZombies[i].object)
389         object_dispose(oldZombies[i].object);
390     }
391     free(oldZombies);
392   }
394   return true;
397 void ZombieDisable() {
398   // Only allow enable/disable on the main thread, just to keep things
399   // simple.
400   DCHECK([NSThread isMainThread]);
402   // |ZombieInit()| was never called.
403   if (!g_originalDeallocIMP)
404     return;
406   // Put back the original implementation of -[NSObject dealloc].
407   Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
408   DCHECK(m);
409   method_setImplementation(m, g_originalDeallocIMP);
411   // Can safely grab this because it only happens on the main thread.
412   const size_t oldCount = g_zombieCount;
413   ZombieRecord* oldZombies = g_zombies;
415   {
416     base::AutoLock pin(g_lock.Get());  // In case any -dealloc are in progress.
417     g_zombieCount = 0;
418     g_zombies = NULL;
419   }
421   // Free any remaining zombies.
422   if (oldZombies) {
423     for (size_t i = 0; i < oldCount; ++i) {
424       if (oldZombies[i].object)
425         object_dispose(oldZombies[i].object);
426     }
427     free(oldZombies);
428   }
431 }  // namespace ObjcEvilDoers