]> git.saurik.com Git - apple/objc4.git/blob - runtime/objc-initialize.mm
objc4-723.tar.gz
[apple/objc4.git] / runtime / objc-initialize.mm
1 /*
2 * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /***********************************************************************
25 * objc-initialize.m
26 * +initialize support
27 **********************************************************************/
28
29 /***********************************************************************
30 * Thread-safety during class initialization (GrP 2001-9-24)
31 *
32 * Initial state: CLS_INITIALIZING and CLS_INITIALIZED both clear.
33 * During initialization: CLS_INITIALIZING is set
34 * After initialization: CLS_INITIALIZING clear and CLS_INITIALIZED set.
35 * CLS_INITIALIZING and CLS_INITIALIZED are never set at the same time.
36 * CLS_INITIALIZED is never cleared once set.
37 *
38 * Only one thread is allowed to actually initialize a class and send
39 * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
40 *
41 * Additionally, threads trying to send messages to a class must wait for
42 * +initialize to finish. During initialization of a class, that class's
43 * method cache is kept empty. objc_msgSend will revert to
44 * class_lookupMethodAndLoadCache, which checks CLS_INITIALIZED before
45 * messaging. If CLS_INITIALIZED is clear but CLS_INITIALIZING is set,
46 * the thread must block, unless it is the thread that started
47 * initializing the class in the first place.
48 *
49 * Each thread keeps a list of classes it's initializing.
50 * The global classInitLock is used to synchronize changes to CLS_INITIALIZED
51 * and CLS_INITIALIZING: the transition to CLS_INITIALIZING must be
52 * an atomic test-and-set with respect to itself and the transition
53 * to CLS_INITIALIZED.
54 * The global classInitWaitCond is used to block threads waiting for an
55 * initialization to complete. The classInitLock synchronizes
56 * condition checking and the condition variable.
57 **********************************************************************/
58
59 /***********************************************************************
60 * +initialize deadlock case when a class is marked initializing while
61 * its superclass is initialized. Solved by completely initializing
62 * superclasses before beginning to initialize a class.
63 *
64 * OmniWeb class hierarchy:
65 * OBObject
66 * | ` OBPostLoader
67 * OFObject
68 * / \
69 * OWAddressEntry OWController
70 * |
71 * OWConsoleController
72 *
73 * Thread 1 (evil testing thread):
74 * initialize OWAddressEntry
75 * super init OFObject
76 * super init OBObject
77 * [OBObject initialize] runs OBPostLoader, which inits lots of classes...
78 * initialize OWConsoleController
79 * super init OWController - wait for Thread 2 to finish OWController init
80 *
81 * Thread 2 (normal OmniWeb thread):
82 * initialize OWController
83 * super init OFObject - wait for Thread 1 to finish OFObject init
84 *
85 * deadlock!
86 *
87 * Solution: fully initialize super classes before beginning to initialize
88 * a subclass. Then the initializing+initialized part of the class hierarchy
89 * will be a contiguous subtree starting at the root, so other threads
90 * can't jump into the middle between two initializing classes, and we won't
91 * get stuck while a superclass waits for its subclass which waits for the
92 * superclass.
93 **********************************************************************/
94
95 #include "objc-private.h"
96 #include "message.h"
97 #include "objc-initialize.h"
98
99 /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and
100 * is signalled when any class is done initializing.
101 * Threads that are waiting for a class to finish initializing wait on this. */
102 monitor_t classInitLock;
103
104
105 /***********************************************************************
106 * struct _objc_initializing_classes
107 * Per-thread list of classes currently being initialized by that thread.
108 * During initialization, that thread is allowed to send messages to that
109 * class, but other threads have to wait.
110 * The list is a simple array of metaclasses (the metaclass stores
111 * the initialization state).
112 **********************************************************************/
113 typedef struct _objc_initializing_classes {
114 int classesAllocated;
115 Class *metaclasses;
116 } _objc_initializing_classes;
117
118
119 /***********************************************************************
120 * _fetchInitializingClassList
121 * Return the list of classes being initialized by this thread.
122 * If create == YES, create the list when no classes are being initialized by this thread.
123 * If create == NO, return nil when no classes are being initialized by this thread.
124 **********************************************************************/
125 static _objc_initializing_classes *_fetchInitializingClassList(bool create)
126 {
127 _objc_pthread_data *data;
128 _objc_initializing_classes *list;
129 Class *classes;
130
131 data = _objc_fetch_pthread_data(create);
132 if (data == nil) return nil;
133
134 list = data->initializingClasses;
135 if (list == nil) {
136 if (!create) {
137 return nil;
138 } else {
139 list = (_objc_initializing_classes *)
140 calloc(1, sizeof(_objc_initializing_classes));
141 data->initializingClasses = list;
142 }
143 }
144
145 classes = list->metaclasses;
146 if (classes == nil) {
147 // If _objc_initializing_classes exists, allocate metaclass array,
148 // even if create == NO.
149 // Allow 4 simultaneous class inits on this thread before realloc.
150 list->classesAllocated = 4;
151 classes = (Class *)
152 calloc(list->classesAllocated, sizeof(Class));
153 list->metaclasses = classes;
154 }
155 return list;
156 }
157
158
159 /***********************************************************************
160 * _destroyInitializingClassList
161 * Deallocate memory used by the given initialization list.
162 * Any part of the list may be nil.
163 * Called from _objc_pthread_destroyspecific().
164 **********************************************************************/
165
166 void _destroyInitializingClassList(struct _objc_initializing_classes *list)
167 {
168 if (list != nil) {
169 if (list->metaclasses != nil) {
170 free(list->metaclasses);
171 }
172 free(list);
173 }
174 }
175
176
177 /***********************************************************************
178 * _thisThreadIsInitializingClass
179 * Return TRUE if this thread is currently initializing the given class.
180 **********************************************************************/
181 bool _thisThreadIsInitializingClass(Class cls)
182 {
183 int i;
184
185 _objc_initializing_classes *list = _fetchInitializingClassList(NO);
186 if (list) {
187 cls = cls->getMeta();
188 for (i = 0; i < list->classesAllocated; i++) {
189 if (cls == list->metaclasses[i]) return YES;
190 }
191 }
192
193 // no list or not found in list
194 return NO;
195 }
196
197
198 /***********************************************************************
199 * _setThisThreadIsInitializingClass
200 * Record that this thread is currently initializing the given class.
201 * This thread will be allowed to send messages to the class, but
202 * other threads will have to wait.
203 **********************************************************************/
204 static void _setThisThreadIsInitializingClass(Class cls)
205 {
206 int i;
207 _objc_initializing_classes *list = _fetchInitializingClassList(YES);
208 cls = cls->getMeta();
209
210 // paranoia: explicitly disallow duplicates
211 for (i = 0; i < list->classesAllocated; i++) {
212 if (cls == list->metaclasses[i]) {
213 _objc_fatal("thread is already initializing this class!");
214 return; // already the initializer
215 }
216 }
217
218 for (i = 0; i < list->classesAllocated; i++) {
219 if (! list->metaclasses[i]) {
220 list->metaclasses[i] = cls;
221 return;
222 }
223 }
224
225 // class list is full - reallocate
226 list->classesAllocated = list->classesAllocated * 2 + 1;
227 list->metaclasses = (Class *)
228 realloc(list->metaclasses,
229 list->classesAllocated * sizeof(Class));
230 // zero out the new entries
231 list->metaclasses[i++] = cls;
232 for ( ; i < list->classesAllocated; i++) {
233 list->metaclasses[i] = nil;
234 }
235 }
236
237
238 /***********************************************************************
239 * _setThisThreadIsNotInitializingClass
240 * Record that this thread is no longer initializing the given class.
241 **********************************************************************/
242 static void _setThisThreadIsNotInitializingClass(Class cls)
243 {
244 int i;
245
246 _objc_initializing_classes *list = _fetchInitializingClassList(NO);
247 if (list) {
248 cls = cls->getMeta();
249 for (i = 0; i < list->classesAllocated; i++) {
250 if (cls == list->metaclasses[i]) {
251 list->metaclasses[i] = nil;
252 return;
253 }
254 }
255 }
256
257 // no list or not found in list
258 _objc_fatal("thread is not initializing this class!");
259 }
260
261
262 typedef struct PendingInitialize {
263 Class subclass;
264 struct PendingInitialize *next;
265 } PendingInitialize;
266
267 static NXMapTable *pendingInitializeMap;
268
269 /***********************************************************************
270 * _finishInitializing
271 * cls has completed its +initialize method, and so has its superclass.
272 * Mark cls as initialized as well, then mark any of cls's subclasses
273 * that have already finished their own +initialize methods.
274 **********************************************************************/
275 static void _finishInitializing(Class cls, Class supercls)
276 {
277 PendingInitialize *pending;
278
279 classInitLock.assertLocked();
280 assert(!supercls || supercls->isInitialized());
281
282 if (PrintInitializing) {
283 _objc_inform("INITIALIZE: thread %p: %s is fully +initialized",
284 pthread_self(), cls->nameForLogging());
285 }
286
287 // mark this class as fully +initialized
288 cls->setInitialized();
289 classInitLock.notifyAll();
290 _setThisThreadIsNotInitializingClass(cls);
291
292 // mark any subclasses that were merely waiting for this class
293 if (!pendingInitializeMap) return;
294 pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
295 if (!pending) return;
296
297 NXMapRemove(pendingInitializeMap, cls);
298
299 // Destroy the pending table if it's now empty, to save memory.
300 if (NXCountMapTable(pendingInitializeMap) == 0) {
301 NXFreeMapTable(pendingInitializeMap);
302 pendingInitializeMap = nil;
303 }
304
305 while (pending) {
306 PendingInitialize *next = pending->next;
307 if (pending->subclass) _finishInitializing(pending->subclass, cls);
308 free(pending);
309 pending = next;
310 }
311 }
312
313
314 /***********************************************************************
315 * _finishInitializingAfter
316 * cls has completed its +initialize method, but its superclass has not.
317 * Wait until supercls finishes before marking cls as initialized.
318 **********************************************************************/
319 static void _finishInitializingAfter(Class cls, Class supercls)
320 {
321 PendingInitialize *pending;
322
323 classInitLock.assertLocked();
324
325 if (PrintInitializing) {
326 _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully "
327 "+initialized after superclass +[%s initialize] completes",
328 pthread_self(), cls->nameForLogging(),
329 supercls->nameForLogging());
330 }
331
332 if (!pendingInitializeMap) {
333 pendingInitializeMap =
334 NXCreateMapTable(NXPtrValueMapPrototype, 10);
335 // fixme pre-size this table for CF/NSObject +initialize
336 }
337
338 pending = (PendingInitialize *)malloc(sizeof(*pending));
339 pending->subclass = cls;
340 pending->next = (PendingInitialize *)
341 NXMapGet(pendingInitializeMap, supercls);
342 NXMapInsert(pendingInitializeMap, supercls, pending);
343 }
344
345
346 // Provide helpful messages in stack traces.
347 OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
348 void waitForInitializeToComplete(Class cls)
349 asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize");
350 OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
351 void callInitialize(Class cls)
352 asm("_CALLING_SOME_+initialize_METHOD");
353
354
355 void waitForInitializeToComplete(Class cls)
356 {
357 if (PrintInitializing) {
358 _objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] "
359 "completes", pthread_self(), cls->nameForLogging());
360 }
361
362 monitor_locker_t lock(classInitLock);
363 while (!cls->isInitialized()) {
364 classInitLock.wait();
365 }
366 asm("");
367 }
368
369
370 void callInitialize(Class cls)
371 {
372 ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
373 asm("");
374 }
375
376
377 /***********************************************************************
378 * classHasTrivialInitialize
379 * Returns true if the class has no +initialize implementation or
380 * has a +initialize implementation that looks empty.
381 * Any root class +initialize implemetation is assumed to be trivial.
382 **********************************************************************/
383 static bool classHasTrivialInitialize(Class cls)
384 {
385 if (cls->isRootClass() || cls->isRootMetaclass()) return true;
386
387 Class rootCls = cls->ISA()->ISA()->superclass;
388
389 IMP rootImp = lookUpImpOrNil(rootCls->ISA(), SEL_initialize, rootCls,
390 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
391 IMP imp = lookUpImpOrNil(cls->ISA(), SEL_initialize, cls,
392 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
393 return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp);
394 }
395
396
397 /***********************************************************************
398 * lockAndFinishInitializing
399 * Mark a class as finished initializing and notify waiters, or queue for later.
400 * If the superclass is also done initializing, then update
401 * the info bits and notify waiting threads.
402 * If not, update them later. (This can happen if this +initialize
403 * was itself triggered from inside a superclass +initialize.)
404 **********************************************************************/
405 static void lockAndFinishInitializing(Class cls, Class supercls)
406 {
407 monitor_locker_t lock(classInitLock);
408 if (!supercls || supercls->isInitialized()) {
409 _finishInitializing(cls, supercls);
410 } else {
411 _finishInitializingAfter(cls, supercls);
412 }
413 }
414
415
416 /***********************************************************************
417 * performForkChildInitialize
418 * +initialize after fork() is problematic. It's possible for the
419 * fork child process to call some +initialize that would deadlock waiting
420 * for another +initialize in the parent process.
421 * We wouldn't know how much progress it made therein, so we can't
422 * act as if +initialize completed nor can we restart +initialize
423 * from scratch.
424 *
425 * Instead we proceed introspectively. If the class has some
426 * +initialize implementation, we halt. If the class has no
427 * +initialize implementation of its own, we continue. Root
428 * class +initialize is assumed to be empty if it exists.
429 *
430 * We apply this rule even if the child's +initialize does not appear
431 * to be blocked by anything. This prevents races wherein the +initialize
432 * deadlock only rarely hits. Instead we disallow it even when we "won"
433 * the race.
434 *
435 * Exception: processes that are single-threaded when fork() is called
436 * have no restrictions on +initialize in the child. Examples: sshd and httpd.
437 *
438 * Classes that wish to implement +initialize and be callable after
439 * fork() must use an atfork() handler to provoke +initialize in fork prepare.
440 **********************************************************************/
441
442 // Called before halting when some +initialize
443 // method can't be called after fork().
444 BREAKPOINT_FUNCTION(
445 void objc_initializeAfterForkError(Class cls)
446 );
447
448 void performForkChildInitialize(Class cls, Class supercls)
449 {
450 if (classHasTrivialInitialize(cls)) {
451 if (PrintInitializing) {
452 _objc_inform("INITIALIZE: thread %p: skipping trivial +[%s "
453 "initialize] in fork() child process",
454 pthread_self(), cls->nameForLogging());
455 }
456 lockAndFinishInitializing(cls, supercls);
457 }
458 else {
459 if (PrintInitializing) {
460 _objc_inform("INITIALIZE: thread %p: refusing to call +[%s "
461 "initialize] in fork() child process because "
462 "it may have been in progress when fork() was called",
463 pthread_self(), cls->nameForLogging());
464 }
465 _objc_inform_now_and_on_crash
466 ("+[%s initialize] may have been in progress in another thread "
467 "when fork() was called.",
468 cls->nameForLogging());
469 objc_initializeAfterForkError(cls);
470 _objc_fatal
471 ("+[%s initialize] may have been in progress in another thread "
472 "when fork() was called. We cannot safely call it or "
473 "ignore it in the fork() child process. Crashing instead. "
474 "Set a breakpoint on objc_initializeAfterForkError to debug.",
475 cls->nameForLogging());
476 }
477 }
478
479
480 /***********************************************************************
481 * class_initialize. Send the '+initialize' message on demand to any
482 * uninitialized class. Force initialization of superclasses first.
483 **********************************************************************/
484 void _class_initialize(Class cls)
485 {
486 assert(!cls->isMetaClass());
487
488 Class supercls;
489 bool reallyInitialize = NO;
490
491 // Make sure super is done initializing BEFORE beginning to initialize cls.
492 // See note about deadlock above.
493 supercls = cls->superclass;
494 if (supercls && !supercls->isInitialized()) {
495 _class_initialize(supercls);
496 }
497
498 // Try to atomically set CLS_INITIALIZING.
499 {
500 monitor_locker_t lock(classInitLock);
501 if (!cls->isInitialized() && !cls->isInitializing()) {
502 cls->setInitializing();
503 reallyInitialize = YES;
504 }
505 }
506
507 if (reallyInitialize) {
508 // We successfully set the CLS_INITIALIZING bit. Initialize the class.
509
510 // Record that we're initializing this class so we can message it.
511 _setThisThreadIsInitializingClass(cls);
512
513 if (MultithreadedForkChild) {
514 // LOL JK we don't really call +initialize methods after fork().
515 performForkChildInitialize(cls, supercls);
516 return;
517 }
518
519 // Send the +initialize message.
520 // Note that +initialize is sent to the superclass (again) if
521 // this class doesn't implement +initialize. 2157218
522 if (PrintInitializing) {
523 _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
524 pthread_self(), cls->nameForLogging());
525 }
526
527 // Exceptions: A +initialize call that throws an exception
528 // is deemed to be a complete and successful +initialize.
529 //
530 // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
531 // bootstrapping problem of this versus CF's call to
532 // objc_exception_set_functions().
533 #if __OBJC2__
534 @try
535 #endif
536 {
537 callInitialize(cls);
538
539 if (PrintInitializing) {
540 _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
541 pthread_self(), cls->nameForLogging());
542 }
543 }
544 #if __OBJC2__
545 @catch (...) {
546 if (PrintInitializing) {
547 _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
548 "threw an exception",
549 pthread_self(), cls->nameForLogging());
550 }
551 @throw;
552 }
553 @finally
554 #endif
555 {
556 // Done initializing.
557 lockAndFinishInitializing(cls, supercls);
558 }
559 return;
560 }
561
562 else if (cls->isInitializing()) {
563 // We couldn't set INITIALIZING because INITIALIZING was already set.
564 // If this thread set it earlier, continue normally.
565 // If some other thread set it, block until initialize is done.
566 // It's ok if INITIALIZING changes to INITIALIZED while we're here,
567 // because we safely check for INITIALIZED inside the lock
568 // before blocking.
569 if (_thisThreadIsInitializingClass(cls)) {
570 return;
571 } else if (!MultithreadedForkChild) {
572 waitForInitializeToComplete(cls);
573 return;
574 } else {
575 // We're on the child side of fork(), facing a class that
576 // was initializing by some other thread when fork() was called.
577 _setThisThreadIsInitializingClass(cls);
578 performForkChildInitialize(cls, supercls);
579 }
580 }
581
582 else if (cls->isInitialized()) {
583 // Set CLS_INITIALIZING failed because someone else already
584 // initialized the class. Continue normally.
585 // NOTE this check must come AFTER the ISINITIALIZING case.
586 // Otherwise: Another thread is initializing this class. ISINITIALIZED
587 // is false. Skip this clause. Then the other thread finishes
588 // initialization and sets INITIALIZING=no and INITIALIZED=yes.
589 // Skip the ISINITIALIZING clause. Die horribly.
590 return;
591 }
592
593 else {
594 // We shouldn't be here.
595 _objc_fatal("thread-safe class init in objc runtime is buggy!");
596 }
597 }