objc-initialize.mm 23 KB

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