2 * Copyright (c) 2004-2006 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 /***********************************************************************
26 * Support for +load methods.
27 **********************************************************************/
29 #include "objc-loadmethod.h"
30 #include "objc-private.h"
32 struct loadable_class {
33 Class cls; // may be NULL
37 struct loadable_category {
38 Category cat; // may be NULL
43 // List of classes that need +load called (pending superclass +load)
44 // This list always has superclasses first because of the way it is constructed
45 static struct loadable_class *loadable_classes NOBSS = NULL;
46 static int loadable_classes_used NOBSS = 0;
47 static int loadable_classes_allocated NOBSS = 0;
49 // List of categories that need +load called (pending parent class +load)
50 static struct loadable_category *loadable_categories NOBSS = NULL;
51 static int loadable_categories_used NOBSS = 0;
52 static int loadable_categories_allocated NOBSS = 0;
55 /***********************************************************************
56 * add_class_to_loadable_list
57 * Class cls has just become connected. Schedule it for +load if
58 * it implements a +load method.
59 **********************************************************************/
60 __private_extern__ void add_class_to_loadable_list(Class cls)
64 recursive_mutex_assert_locked(&loadMethodLock);
66 method = _class_getLoadMethod(cls);
67 if (!method) return; // Don't bother if cls has no +load method
70 _objc_inform("LOAD: class '%s' scheduled for +load", _class_getName(cls));
73 if (loadable_classes_used == loadable_classes_allocated) {
74 loadable_classes_allocated = loadable_classes_allocated*2 + 16;
76 _realloc_internal(loadable_classes,
77 loadable_classes_allocated *
78 sizeof(struct loadable_class));
81 loadable_classes[loadable_classes_used].cls = cls;
82 loadable_classes[loadable_classes_used].method = method;
83 loadable_classes_used++;
87 /***********************************************************************
88 * add_category_to_loadable_list
89 * Category cat's parent class exists and the category has been attached
90 * to its class. Schedule this category for +load after its parent class
91 * becomes connected and has its own +load method called.
92 **********************************************************************/
93 __private_extern__ void add_category_to_loadable_list(Category cat)
97 recursive_mutex_assert_locked(&loadMethodLock);
99 method = _category_getLoadMethod(cat);
101 // Don't bother if cat has no +load method
105 _objc_inform("LOAD: category '%s(%s)' scheduled for +load",
106 _category_getClassName(cat), _category_getName(cat));
109 if (loadable_categories_used == loadable_categories_allocated) {
110 loadable_categories_allocated = loadable_categories_allocated*2 + 16;
111 loadable_categories =
112 _realloc_internal(loadable_categories,
113 loadable_categories_allocated *
114 sizeof(struct loadable_category));
117 loadable_categories[loadable_categories_used].cat = cat;
118 loadable_categories[loadable_categories_used].method = method;
119 loadable_categories_used++;
123 /***********************************************************************
124 * remove_class_from_loadable_list
125 * Class cls may have been loadable before, but it is now no longer
126 * loadable (because its image is being unmapped).
127 **********************************************************************/
128 __private_extern__ void remove_class_from_loadable_list(Class cls)
130 recursive_mutex_assert_locked(&loadMethodLock);
132 if (loadable_classes) {
134 for (i = 0; i < loadable_classes_used; i++) {
135 if (loadable_classes[i].cls == cls) {
136 loadable_classes[i].cls = NULL;
138 _objc_inform("LOAD: class '%s' unscheduled for +load", _class_getName(cls));
147 /***********************************************************************
148 * remove_category_from_loadable_list
149 * Category cat may have been loadable before, but it is now no longer
150 * loadable (because its image is being unmapped).
151 **********************************************************************/
152 __private_extern__ void remove_category_from_loadable_list(Category cat)
154 recursive_mutex_assert_locked(&loadMethodLock);
156 if (loadable_categories) {
158 for (i = 0; i < loadable_categories_used; i++) {
159 if (loadable_categories[i].cat == cat) {
160 loadable_categories[i].cat = NULL;
162 _objc_inform("LOAD: category '%s(%s)' unscheduled for +load",
163 _category_getClassName(cat),
164 _category_getName(cat));
173 /***********************************************************************
175 * Call all pending class +load methods.
176 * If new classes become loadable, +load is NOT called for them.
178 * Called only by call_load_methods().
179 **********************************************************************/
180 static void call_class_loads(void)
184 // Detach current loadable list.
185 struct loadable_class *classes = loadable_classes;
186 int used = loadable_classes_used;
187 loadable_classes = NULL;
188 loadable_classes_allocated = 0;
189 loadable_classes_used = 0;
191 // Call all +loads for the detached list.
192 for (i = 0; i < used; i++) {
193 Class cls = classes[i].cls;
194 IMP load_method = classes[i].method;
198 _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
200 (*load_method) ((id) cls, SEL_load);
203 // Destroy the detached list.
204 if (classes) _free_internal(classes);
208 /***********************************************************************
209 * call_category_loads
210 * Call some pending category +load methods.
211 * The parent class of the +load-implementing categories has all of
212 * its categories attached, in case some are lazily waiting for +initalize.
213 * Don't call +load unless the parent class is connected.
214 * If new categories become loadable, +load is NOT called, and they
215 * are added to the end of the loadable list, and we return TRUE.
216 * Return FALSE if no new categories became loadable.
218 * Called only by call_load_methods().
219 **********************************************************************/
220 static BOOL call_category_loads(void)
223 BOOL new_categories_added = NO;
225 // Detach current loadable list.
226 struct loadable_category *cats = loadable_categories;
227 int used = loadable_categories_used;
228 int allocated = loadable_categories_allocated;
229 loadable_categories = NULL;
230 loadable_categories_allocated = 0;
231 loadable_categories_used = 0;
233 // Call all +loads for the detached list.
234 for (i = 0; i < used; i++) {
235 Category cat = cats[i].cat;
236 IMP load_method = cats[i].method;
240 cls = _category_getClass(cat);
241 if (cls && _class_isLoadable(cls)) {
243 _objc_inform("LOAD: +[%s(%s) load]\n",
245 _category_getName(cat));
247 (*load_method) ((id) cls, SEL_load);
252 // Compact detached list (order-preserving)
254 for (i = 0; i < used; i++) {
256 cats[i-shift] = cats[i];
263 // Copy any new +load candidates from the new list to the detached list.
264 new_categories_added = (loadable_categories_used > 0);
265 for (i = 0; i < loadable_categories_used; i++) {
266 if (used == allocated) {
267 allocated = allocated*2 + 16;
268 cats = _realloc_internal(cats, allocated *
269 sizeof(struct loadable_category));
271 cats[used++] = loadable_categories[i];
274 // Destroy the new list.
275 if (loadable_categories) _free_internal(loadable_categories);
277 // Reattach the (now augmented) detached list.
278 // But if there's nothing left to load, destroy the list.
280 loadable_categories = cats;
281 loadable_categories_used = used;
282 loadable_categories_allocated = allocated;
284 if (cats) _free_internal(cats);
285 loadable_categories = NULL;
286 loadable_categories_used = 0;
287 loadable_categories_allocated = 0;
291 if (loadable_categories_used != 0) {
292 _objc_inform("LOAD: %d categories still waiting for +load\n",
293 loadable_categories_used);
297 return new_categories_added;
301 /***********************************************************************
303 * Call all pending class and category +load methods.
304 * Class +load methods are called superclass-first.
305 * Category +load methods are not called until after the parent class's +load.
307 * This method must be RE-ENTRANT, because a +load could trigger
308 * more image mapping. In addition, the superclass-first ordering
309 * must be preserved in the face of re-entrant calls. Therefore,
310 * only the OUTERMOST call of this function will do anything, and
311 * that call will handle all loadable classes, even those generated
312 * while it was running.
314 * The sequence below preserves +load ordering in the face of
315 * image loading during a +load, and make sure that no
316 * +load method is forgotten because it was added during
319 * 1. Repeatedly call class +loads until there aren't any more
320 * 2. Call category +loads ONCE.
321 * 3. Run more +loads if:
322 * (a) there are more classes to load, OR
323 * (b) there are some potential category +loads that have
324 * still never been attempted.
325 * Category +loads are only run once to ensure "parent class first"
326 * ordering, even if a category +load triggers a new loadable class
327 * and a new loadable category attached to that class.
329 * Locking: loadMethodLock must be held by the caller
330 * All other locks must not be held.
331 **********************************************************************/
332 __private_extern__ void call_load_methods(void)
334 static BOOL loading = NO;
335 BOOL more_categories;
337 recursive_mutex_assert_locked(&loadMethodLock);
339 // Re-entrant calls do nothing; the outermost call will finish the job.
344 // 1. Repeatedly call class +loads until there aren't any more
345 while (loadable_classes_used > 0) {
349 // 2. Call category +loads ONCE
350 more_categories = call_category_loads();
352 // 3. Run more +loads if there are classes OR more untried categories
353 } while (loadable_classes_used > 0 || more_categories);