]>
Commit | Line | Data |
---|---|---|
b3962a83 A |
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 | ||
7af964d1 A |
95 | #include "objc-private.h" |
96 | #include "message.h" | |
97 | #include "objc-initialize.h" | |
1807f628 | 98 | #include "DenseMapExtras.h" |
b3962a83 | 99 | |
7af964d1 A |
100 | /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and |
101 | * is signalled when any class is done initializing. | |
b3962a83 | 102 | * Threads that are waiting for a class to finish initializing wait on this. */ |
bd8dfcfc | 103 | monitor_t classInitLock; |
b3962a83 A |
104 | |
105 | ||
1807f628 A |
106 | struct _objc_willInitializeClassCallback { |
107 | _objc_func_willInitializeClass f; | |
108 | void *context; | |
109 | }; | |
110 | static GlobalSmallVector<_objc_willInitializeClassCallback, 1> willInitializeFuncs; | |
111 | ||
112 | ||
b3962a83 A |
113 | /*********************************************************************** |
114 | * struct _objc_initializing_classes | |
115 | * Per-thread list of classes currently being initialized by that thread. | |
116 | * During initialization, that thread is allowed to send messages to that | |
117 | * class, but other threads have to wait. | |
118 | * The list is a simple array of metaclasses (the metaclass stores | |
119 | * the initialization state). | |
120 | **********************************************************************/ | |
121 | typedef struct _objc_initializing_classes { | |
122 | int classesAllocated; | |
123 | Class *metaclasses; | |
124 | } _objc_initializing_classes; | |
125 | ||
126 | ||
127 | /*********************************************************************** | |
128 | * _fetchInitializingClassList | |
129 | * Return the list of classes being initialized by this thread. | |
130 | * If create == YES, create the list when no classes are being initialized by this thread. | |
7257e56c | 131 | * If create == NO, return nil when no classes are being initialized by this thread. |
b3962a83 | 132 | **********************************************************************/ |
c1e772c4 | 133 | static _objc_initializing_classes *_fetchInitializingClassList(bool create) |
b3962a83 A |
134 | { |
135 | _objc_pthread_data *data; | |
136 | _objc_initializing_classes *list; | |
137 | Class *classes; | |
138 | ||
139 | data = _objc_fetch_pthread_data(create); | |
7257e56c | 140 | if (data == nil) return nil; |
b3962a83 A |
141 | |
142 | list = data->initializingClasses; | |
7257e56c | 143 | if (list == nil) { |
b3962a83 | 144 | if (!create) { |
7257e56c | 145 | return nil; |
b3962a83 | 146 | } else { |
cd5f04f5 | 147 | list = (_objc_initializing_classes *) |
31875a97 | 148 | calloc(1, sizeof(_objc_initializing_classes)); |
b3962a83 A |
149 | data->initializingClasses = list; |
150 | } | |
151 | } | |
152 | ||
153 | classes = list->metaclasses; | |
7257e56c | 154 | if (classes == nil) { |
b3962a83 A |
155 | // If _objc_initializing_classes exists, allocate metaclass array, |
156 | // even if create == NO. | |
157 | // Allow 4 simultaneous class inits on this thread before realloc. | |
158 | list->classesAllocated = 4; | |
cd5f04f5 | 159 | classes = (Class *) |
31875a97 | 160 | calloc(list->classesAllocated, sizeof(Class)); |
b3962a83 A |
161 | list->metaclasses = classes; |
162 | } | |
163 | return list; | |
164 | } | |
165 | ||
166 | ||
167 | /*********************************************************************** | |
168 | * _destroyInitializingClassList | |
169 | * Deallocate memory used by the given initialization list. | |
7257e56c | 170 | * Any part of the list may be nil. |
b3962a83 A |
171 | * Called from _objc_pthread_destroyspecific(). |
172 | **********************************************************************/ | |
cd5f04f5 | 173 | |
b3962a83 A |
174 | void _destroyInitializingClassList(struct _objc_initializing_classes *list) |
175 | { | |
7257e56c A |
176 | if (list != nil) { |
177 | if (list->metaclasses != nil) { | |
31875a97 | 178 | free(list->metaclasses); |
b3962a83 | 179 | } |
31875a97 | 180 | free(list); |
b3962a83 A |
181 | } |
182 | } | |
183 | ||
184 | ||
185 | /*********************************************************************** | |
186 | * _thisThreadIsInitializingClass | |
187 | * Return TRUE if this thread is currently initializing the given class. | |
188 | **********************************************************************/ | |
c1e772c4 | 189 | bool _thisThreadIsInitializingClass(Class cls) |
b3962a83 A |
190 | { |
191 | int i; | |
192 | ||
193 | _objc_initializing_classes *list = _fetchInitializingClassList(NO); | |
194 | if (list) { | |
7257e56c | 195 | cls = cls->getMeta(); |
b3962a83 A |
196 | for (i = 0; i < list->classesAllocated; i++) { |
197 | if (cls == list->metaclasses[i]) return YES; | |
198 | } | |
199 | } | |
200 | ||
201 | // no list or not found in list | |
202 | return NO; | |
203 | } | |
204 | ||
205 | ||
206 | /*********************************************************************** | |
207 | * _setThisThreadIsInitializingClass | |
208 | * Record that this thread is currently initializing the given class. | |
209 | * This thread will be allowed to send messages to the class, but | |
210 | * other threads will have to wait. | |
211 | **********************************************************************/ | |
212 | static void _setThisThreadIsInitializingClass(Class cls) | |
213 | { | |
214 | int i; | |
215 | _objc_initializing_classes *list = _fetchInitializingClassList(YES); | |
7257e56c | 216 | cls = cls->getMeta(); |
b3962a83 A |
217 | |
218 | // paranoia: explicitly disallow duplicates | |
219 | for (i = 0; i < list->classesAllocated; i++) { | |
220 | if (cls == list->metaclasses[i]) { | |
221 | _objc_fatal("thread is already initializing this class!"); | |
222 | return; // already the initializer | |
223 | } | |
224 | } | |
225 | ||
226 | for (i = 0; i < list->classesAllocated; i++) { | |
7257e56c | 227 | if (! list->metaclasses[i]) { |
b3962a83 A |
228 | list->metaclasses[i] = cls; |
229 | return; | |
230 | } | |
231 | } | |
232 | ||
233 | // class list is full - reallocate | |
234 | list->classesAllocated = list->classesAllocated * 2 + 1; | |
cd5f04f5 | 235 | list->metaclasses = (Class *) |
31875a97 | 236 | realloc(list->metaclasses, |
cd5f04f5 | 237 | list->classesAllocated * sizeof(Class)); |
b3962a83 A |
238 | // zero out the new entries |
239 | list->metaclasses[i++] = cls; | |
240 | for ( ; i < list->classesAllocated; i++) { | |
7257e56c | 241 | list->metaclasses[i] = nil; |
b3962a83 A |
242 | } |
243 | } | |
244 | ||
245 | ||
246 | /*********************************************************************** | |
247 | * _setThisThreadIsNotInitializingClass | |
248 | * Record that this thread is no longer initializing the given class. | |
249 | **********************************************************************/ | |
250 | static void _setThisThreadIsNotInitializingClass(Class cls) | |
251 | { | |
252 | int i; | |
253 | ||
254 | _objc_initializing_classes *list = _fetchInitializingClassList(NO); | |
255 | if (list) { | |
7257e56c | 256 | cls = cls->getMeta(); |
b3962a83 A |
257 | for (i = 0; i < list->classesAllocated; i++) { |
258 | if (cls == list->metaclasses[i]) { | |
7257e56c | 259 | list->metaclasses[i] = nil; |
b3962a83 A |
260 | return; |
261 | } | |
262 | } | |
263 | } | |
264 | ||
265 | // no list or not found in list | |
266 | _objc_fatal("thread is not initializing this class!"); | |
267 | } | |
268 | ||
269 | ||
7af964d1 A |
270 | typedef struct PendingInitialize { |
271 | Class subclass; | |
272 | struct PendingInitialize *next; | |
1807f628 A |
273 | |
274 | PendingInitialize(Class cls) : subclass(cls), next(nullptr) { } | |
7af964d1 A |
275 | } PendingInitialize; |
276 | ||
1807f628 A |
277 | typedef objc::DenseMap<Class, PendingInitialize *> PendingInitializeMap; |
278 | static PendingInitializeMap *pendingInitializeMap; | |
7af964d1 A |
279 | |
280 | /*********************************************************************** | |
281 | * _finishInitializing | |
282 | * cls has completed its +initialize method, and so has its superclass. | |
283 | * Mark cls as initialized as well, then mark any of cls's subclasses | |
284 | * that have already finished their own +initialize methods. | |
285 | **********************************************************************/ | |
286 | static void _finishInitializing(Class cls, Class supercls) | |
287 | { | |
288 | PendingInitialize *pending; | |
289 | ||
31875a97 | 290 | classInitLock.assertLocked(); |
1807f628 | 291 | ASSERT(!supercls || supercls->isInitialized()); |
7af964d1 A |
292 | |
293 | if (PrintInitializing) { | |
4a109af3 | 294 | _objc_inform("INITIALIZE: thread %p: %s is fully +initialized", |
1807f628 | 295 | objc_thread_self(), cls->nameForLogging()); |
7af964d1 A |
296 | } |
297 | ||
7af964d1 | 298 | // mark this class as fully +initialized |
7257e56c | 299 | cls->setInitialized(); |
31875a97 | 300 | classInitLock.notifyAll(); |
7af964d1 A |
301 | _setThisThreadIsNotInitializingClass(cls); |
302 | ||
303 | // mark any subclasses that were merely waiting for this class | |
304 | if (!pendingInitializeMap) return; | |
7af964d1 | 305 | |
1807f628 A |
306 | auto it = pendingInitializeMap->find(cls); |
307 | if (it == pendingInitializeMap->end()) return; | |
308 | ||
309 | pending = it->second; | |
310 | pendingInitializeMap->erase(it); | |
311 | ||
7af964d1 | 312 | // Destroy the pending table if it's now empty, to save memory. |
1807f628 A |
313 | if (pendingInitializeMap->size() == 0) { |
314 | delete pendingInitializeMap; | |
7257e56c | 315 | pendingInitializeMap = nil; |
7af964d1 A |
316 | } |
317 | ||
318 | while (pending) { | |
319 | PendingInitialize *next = pending->next; | |
320 | if (pending->subclass) _finishInitializing(pending->subclass, cls); | |
1807f628 | 321 | delete pending; |
7af964d1 A |
322 | pending = next; |
323 | } | |
324 | } | |
325 | ||
326 | ||
327 | /*********************************************************************** | |
328 | * _finishInitializingAfter | |
329 | * cls has completed its +initialize method, but its superclass has not. | |
330 | * Wait until supercls finishes before marking cls as initialized. | |
331 | **********************************************************************/ | |
332 | static void _finishInitializingAfter(Class cls, Class supercls) | |
333 | { | |
7af964d1 | 334 | |
31875a97 | 335 | classInitLock.assertLocked(); |
7af964d1 A |
336 | |
337 | if (PrintInitializing) { | |
4a109af3 A |
338 | _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully " |
339 | "+initialized after superclass +[%s initialize] completes", | |
1807f628 | 340 | objc_thread_self(), cls->nameForLogging(), |
4a109af3 | 341 | supercls->nameForLogging()); |
7af964d1 A |
342 | } |
343 | ||
344 | if (!pendingInitializeMap) { | |
1807f628 | 345 | pendingInitializeMap = new PendingInitializeMap{10}; |
7af964d1 A |
346 | // fixme pre-size this table for CF/NSObject +initialize |
347 | } | |
348 | ||
1807f628 A |
349 | PendingInitialize *pending = new PendingInitialize{cls}; |
350 | auto result = pendingInitializeMap->try_emplace(supercls, pending); | |
351 | if (!result.second) { | |
352 | pending->next = result.first->second; | |
353 | result.first->second = pending; | |
354 | } | |
7af964d1 A |
355 | } |
356 | ||
357 | ||
c1e772c4 A |
358 | // Provide helpful messages in stack traces. |
359 | OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) | |
360 | void waitForInitializeToComplete(Class cls) | |
361 | asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize"); | |
362 | OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) | |
363 | void callInitialize(Class cls) | |
364 | asm("_CALLING_SOME_+initialize_METHOD"); | |
365 | ||
366 | ||
367 | void waitForInitializeToComplete(Class cls) | |
368 | { | |
4a109af3 A |
369 | if (PrintInitializing) { |
370 | _objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] " | |
1807f628 | 371 | "completes", objc_thread_self(), cls->nameForLogging()); |
4a109af3 A |
372 | } |
373 | ||
c1e772c4 A |
374 | monitor_locker_t lock(classInitLock); |
375 | while (!cls->isInitialized()) { | |
376 | classInitLock.wait(); | |
377 | } | |
378 | asm(""); | |
379 | } | |
380 | ||
381 | ||
382 | void callInitialize(Class cls) | |
383 | { | |
1807f628 | 384 | ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize)); |
c1e772c4 A |
385 | asm(""); |
386 | } | |
387 | ||
388 | ||
4a109af3 A |
389 | /*********************************************************************** |
390 | * classHasTrivialInitialize | |
391 | * Returns true if the class has no +initialize implementation or | |
392 | * has a +initialize implementation that looks empty. | |
393 | * Any root class +initialize implemetation is assumed to be trivial. | |
394 | **********************************************************************/ | |
395 | static bool classHasTrivialInitialize(Class cls) | |
396 | { | |
397 | if (cls->isRootClass() || cls->isRootMetaclass()) return true; | |
398 | ||
34d5b5e8 | 399 | Class rootCls = cls->ISA()->ISA()->getSuperclass(); |
4a109af3 | 400 | |
34d5b5e8 A |
401 | IMP rootImp = lookUpImpOrNilTryCache(rootCls, @selector(initialize), rootCls->ISA()); |
402 | IMP imp = lookUpImpOrNilTryCache(cls, @selector(initialize), cls->ISA()); | |
4a109af3 A |
403 | return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp); |
404 | } | |
405 | ||
406 | ||
407 | /*********************************************************************** | |
408 | * lockAndFinishInitializing | |
409 | * Mark a class as finished initializing and notify waiters, or queue for later. | |
410 | * If the superclass is also done initializing, then update | |
411 | * the info bits and notify waiting threads. | |
412 | * If not, update them later. (This can happen if this +initialize | |
413 | * was itself triggered from inside a superclass +initialize.) | |
414 | **********************************************************************/ | |
415 | static void lockAndFinishInitializing(Class cls, Class supercls) | |
416 | { | |
417 | monitor_locker_t lock(classInitLock); | |
418 | if (!supercls || supercls->isInitialized()) { | |
419 | _finishInitializing(cls, supercls); | |
420 | } else { | |
421 | _finishInitializingAfter(cls, supercls); | |
422 | } | |
423 | } | |
424 | ||
425 | ||
426 | /*********************************************************************** | |
427 | * performForkChildInitialize | |
428 | * +initialize after fork() is problematic. It's possible for the | |
429 | * fork child process to call some +initialize that would deadlock waiting | |
430 | * for another +initialize in the parent process. | |
431 | * We wouldn't know how much progress it made therein, so we can't | |
432 | * act as if +initialize completed nor can we restart +initialize | |
433 | * from scratch. | |
434 | * | |
435 | * Instead we proceed introspectively. If the class has some | |
436 | * +initialize implementation, we halt. If the class has no | |
437 | * +initialize implementation of its own, we continue. Root | |
438 | * class +initialize is assumed to be empty if it exists. | |
439 | * | |
440 | * We apply this rule even if the child's +initialize does not appear | |
441 | * to be blocked by anything. This prevents races wherein the +initialize | |
442 | * deadlock only rarely hits. Instead we disallow it even when we "won" | |
443 | * the race. | |
444 | * | |
445 | * Exception: processes that are single-threaded when fork() is called | |
446 | * have no restrictions on +initialize in the child. Examples: sshd and httpd. | |
447 | * | |
448 | * Classes that wish to implement +initialize and be callable after | |
449 | * fork() must use an atfork() handler to provoke +initialize in fork prepare. | |
450 | **********************************************************************/ | |
451 | ||
452 | // Called before halting when some +initialize | |
453 | // method can't be called after fork(). | |
454 | BREAKPOINT_FUNCTION( | |
455 | void objc_initializeAfterForkError(Class cls) | |
456 | ); | |
457 | ||
458 | void performForkChildInitialize(Class cls, Class supercls) | |
459 | { | |
460 | if (classHasTrivialInitialize(cls)) { | |
461 | if (PrintInitializing) { | |
462 | _objc_inform("INITIALIZE: thread %p: skipping trivial +[%s " | |
463 | "initialize] in fork() child process", | |
1807f628 | 464 | objc_thread_self(), cls->nameForLogging()); |
4a109af3 A |
465 | } |
466 | lockAndFinishInitializing(cls, supercls); | |
467 | } | |
468 | else { | |
469 | if (PrintInitializing) { | |
470 | _objc_inform("INITIALIZE: thread %p: refusing to call +[%s " | |
471 | "initialize] in fork() child process because " | |
472 | "it may have been in progress when fork() was called", | |
1807f628 | 473 | objc_thread_self(), cls->nameForLogging()); |
4a109af3 A |
474 | } |
475 | _objc_inform_now_and_on_crash | |
476 | ("+[%s initialize] may have been in progress in another thread " | |
477 | "when fork() was called.", | |
478 | cls->nameForLogging()); | |
479 | objc_initializeAfterForkError(cls); | |
480 | _objc_fatal | |
481 | ("+[%s initialize] may have been in progress in another thread " | |
482 | "when fork() was called. We cannot safely call it or " | |
483 | "ignore it in the fork() child process. Crashing instead. " | |
484 | "Set a breakpoint on objc_initializeAfterForkError to debug.", | |
485 | cls->nameForLogging()); | |
486 | } | |
487 | } | |
488 | ||
489 | ||
b3962a83 A |
490 | /*********************************************************************** |
491 | * class_initialize. Send the '+initialize' message on demand to any | |
492 | * uninitialized class. Force initialization of superclasses first. | |
b3962a83 | 493 | **********************************************************************/ |
13ba007e | 494 | void initializeNonMetaClass(Class cls) |
b3962a83 | 495 | { |
1807f628 | 496 | ASSERT(!cls->isMetaClass()); |
cd5f04f5 | 497 | |
b3962a83 | 498 | Class supercls; |
c1e772c4 | 499 | bool reallyInitialize = NO; |
b3962a83 | 500 | |
b3962a83 A |
501 | // Make sure super is done initializing BEFORE beginning to initialize cls. |
502 | // See note about deadlock above. | |
34d5b5e8 | 503 | supercls = cls->getSuperclass(); |
7257e56c | 504 | if (supercls && !supercls->isInitialized()) { |
13ba007e | 505 | initializeNonMetaClass(supercls); |
b3962a83 A |
506 | } |
507 | ||
508 | // Try to atomically set CLS_INITIALIZING. | |
1807f628 | 509 | SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs; |
31875a97 A |
510 | { |
511 | monitor_locker_t lock(classInitLock); | |
512 | if (!cls->isInitialized() && !cls->isInitializing()) { | |
513 | cls->setInitializing(); | |
514 | reallyInitialize = YES; | |
1807f628 A |
515 | |
516 | // Grab a copy of the will-initialize funcs with the lock held. | |
517 | localWillInitializeFuncs.initFrom(willInitializeFuncs); | |
31875a97 | 518 | } |
b3962a83 | 519 | } |
b3962a83 A |
520 | |
521 | if (reallyInitialize) { | |
522 | // We successfully set the CLS_INITIALIZING bit. Initialize the class. | |
523 | ||
524 | // Record that we're initializing this class so we can message it. | |
525 | _setThisThreadIsInitializingClass(cls); | |
4a109af3 A |
526 | |
527 | if (MultithreadedForkChild) { | |
528 | // LOL JK we don't really call +initialize methods after fork(). | |
529 | performForkChildInitialize(cls, supercls); | |
530 | return; | |
531 | } | |
b3962a83 | 532 | |
1807f628 A |
533 | for (auto callback : localWillInitializeFuncs) |
534 | callback.f(callback.context, cls); | |
535 | ||
b3962a83 A |
536 | // Send the +initialize message. |
537 | // Note that +initialize is sent to the superclass (again) if | |
538 | // this class doesn't implement +initialize. 2157218 | |
539 | if (PrintInitializing) { | |
4a109af3 | 540 | _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]", |
1807f628 | 541 | objc_thread_self(), cls->nameForLogging()); |
b3962a83 | 542 | } |
7af964d1 | 543 | |
c1e772c4 A |
544 | // Exceptions: A +initialize call that throws an exception |
545 | // is deemed to be a complete and successful +initialize. | |
4a109af3 A |
546 | // |
547 | // Only __OBJC2__ adds these handlers. !__OBJC2__ has a | |
548 | // bootstrapping problem of this versus CF's call to | |
549 | // objc_exception_set_functions(). | |
550 | #if __OBJC2__ | |
551 | @try | |
552 | #endif | |
553 | { | |
c1e772c4 | 554 | callInitialize(cls); |
7af964d1 | 555 | |
c1e772c4 | 556 | if (PrintInitializing) { |
4a109af3 | 557 | _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", |
1807f628 | 558 | objc_thread_self(), cls->nameForLogging()); |
c1e772c4 A |
559 | } |
560 | } | |
4a109af3 | 561 | #if __OBJC2__ |
c1e772c4 A |
562 | @catch (...) { |
563 | if (PrintInitializing) { | |
4a109af3 A |
564 | _objc_inform("INITIALIZE: thread %p: +[%s initialize] " |
565 | "threw an exception", | |
1807f628 | 566 | objc_thread_self(), cls->nameForLogging()); |
c1e772c4 A |
567 | } |
568 | @throw; | |
569 | } | |
4a109af3 A |
570 | @finally |
571 | #endif | |
572 | { | |
573 | // Done initializing. | |
574 | lockAndFinishInitializing(cls, supercls); | |
7af964d1 | 575 | } |
b3962a83 A |
576 | return; |
577 | } | |
578 | ||
7257e56c | 579 | else if (cls->isInitializing()) { |
b3962a83 A |
580 | // We couldn't set INITIALIZING because INITIALIZING was already set. |
581 | // If this thread set it earlier, continue normally. | |
582 | // If some other thread set it, block until initialize is done. | |
583 | // It's ok if INITIALIZING changes to INITIALIZED while we're here, | |
584 | // because we safely check for INITIALIZED inside the lock | |
585 | // before blocking. | |
586 | if (_thisThreadIsInitializingClass(cls)) { | |
587 | return; | |
4a109af3 | 588 | } else if (!MultithreadedForkChild) { |
c1e772c4 | 589 | waitForInitializeToComplete(cls); |
b3962a83 | 590 | return; |
4a109af3 A |
591 | } else { |
592 | // We're on the child side of fork(), facing a class that | |
593 | // was initializing by some other thread when fork() was called. | |
594 | _setThisThreadIsInitializingClass(cls); | |
595 | performForkChildInitialize(cls, supercls); | |
b3962a83 A |
596 | } |
597 | } | |
598 | ||
7257e56c | 599 | else if (cls->isInitialized()) { |
b3962a83 A |
600 | // Set CLS_INITIALIZING failed because someone else already |
601 | // initialized the class. Continue normally. | |
602 | // NOTE this check must come AFTER the ISINITIALIZING case. | |
603 | // Otherwise: Another thread is initializing this class. ISINITIALIZED | |
604 | // is false. Skip this clause. Then the other thread finishes | |
605 | // initialization and sets INITIALIZING=no and INITIALIZED=yes. | |
606 | // Skip the ISINITIALIZING clause. Die horribly. | |
607 | return; | |
608 | } | |
609 | ||
610 | else { | |
611 | // We shouldn't be here. | |
612 | _objc_fatal("thread-safe class init in objc runtime is buggy!"); | |
613 | } | |
614 | } | |
1807f628 A |
615 | |
616 | void _objc_addWillInitializeClassFunc(_objc_func_willInitializeClass _Nonnull func, void * _Nullable context) { | |
617 | #if __OBJC2__ | |
618 | unsigned count; | |
619 | Class *realizedClasses; | |
620 | ||
621 | // Fetch all currently initialized classes. Do this with classInitLock held | |
622 | // so we don't race with setting those flags. | |
623 | { | |
624 | monitor_locker_t initLock(classInitLock); | |
625 | realizedClasses = objc_copyRealizedClassList(&count); | |
626 | for (unsigned i = 0; i < count; i++) { | |
627 | // Remove uninitialized classes from the array. | |
628 | if (!realizedClasses[i]->isInitializing() && !realizedClasses[i]->isInitialized()) | |
629 | realizedClasses[i] = Nil; | |
630 | } | |
631 | ||
632 | willInitializeFuncs.append({func, context}); | |
633 | } | |
634 | ||
635 | // Invoke the callback for all realized classes that weren't cleared out. | |
636 | for (unsigned i = 0; i < count; i++) { | |
637 | if (Class cls = realizedClasses[i]) { | |
638 | func(context, cls); | |
639 | } | |
640 | } | |
641 | ||
642 | free(realizedClasses); | |
643 | #endif | |
644 | } |