]> git.saurik.com Git - apple/objc4.git/blame - runtime/objc-initialize.mm
objc4-818.2.tar.gz
[apple/objc4.git] / runtime / objc-initialize.mm
CommitLineData
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 103monitor_t classInitLock;
b3962a83
A
104
105
1807f628
A
106struct _objc_willInitializeClassCallback {
107 _objc_func_willInitializeClass f;
108 void *context;
109};
110static 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**********************************************************************/
121typedef 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 133static _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
174void _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 189bool _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**********************************************************************/
212static 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**********************************************************************/
250static 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
270typedef 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
277typedef objc::DenseMap<Class, PendingInitialize *> PendingInitializeMap;
278static 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**********************************************************************/
286static 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**********************************************************************/
332static 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.
359OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
360void waitForInitializeToComplete(Class cls)
361 asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize");
362OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
363void callInitialize(Class cls)
364 asm("_CALLING_SOME_+initialize_METHOD");
365
366
367void 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
382void 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**********************************************************************/
395static 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**********************************************************************/
415static 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().
454BREAKPOINT_FUNCTION(
455 void objc_initializeAfterForkError(Class cls)
456);
457
458void 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 494void 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
616void _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}