]>
Commit | Line | Data |
---|---|---|
b3962a83 A |
1 | /* |
2 | * Copyright (c) 2004-2006 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-loadmethod.m | |
26 | * Support for +load methods. | |
27 | **********************************************************************/ | |
28 | ||
29 | #import "objc-loadmethod.h" | |
30 | #import "objc-private.h" | |
31 | ||
32 | struct loadable_class { | |
33 | Class cls; // may be NULL | |
34 | IMP method; | |
35 | }; | |
36 | ||
37 | struct loadable_category { | |
38 | Category cat; // may be NULL | |
39 | IMP method; | |
40 | }; | |
41 | ||
42 | ||
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; | |
48 | ||
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; | |
53 | ||
54 | ||
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) | |
61 | { | |
62 | IMP method = _class_getLoadMethod(cls); | |
63 | if (!method) return; // Don't bother if cls has no +load method | |
64 | ||
65 | if (PrintLoading) { | |
66 | _objc_inform("LOAD: class '%s' scheduled for +load", _class_getName(cls)); | |
67 | } | |
68 | ||
69 | if (loadable_classes_used == loadable_classes_allocated) { | |
70 | loadable_classes_allocated = loadable_classes_allocated*2 + 16; | |
71 | loadable_classes = | |
72 | _realloc_internal(loadable_classes, | |
73 | loadable_classes_allocated * | |
74 | sizeof(struct loadable_class)); | |
75 | } | |
76 | ||
77 | loadable_classes[loadable_classes_used].cls = cls; | |
78 | loadable_classes[loadable_classes_used].method = method; | |
79 | loadable_classes_used++; | |
80 | } | |
81 | ||
82 | ||
83 | /*********************************************************************** | |
84 | * add_category_to_loadable_list | |
85 | * Category cat's parent class exists and the category has been attached | |
86 | * to its class. Schedule this category for +load after its parent class | |
87 | * becomes connected and has its own +load method called. | |
88 | **********************************************************************/ | |
89 | __private_extern__ void add_category_to_loadable_list(Category cat) | |
90 | { | |
91 | IMP method = _category_getLoadMethod(cat); | |
92 | ||
93 | // Don't bother if cat has no +load method | |
94 | if (!method) return; | |
95 | ||
96 | if (PrintLoading) { | |
97 | _objc_inform("LOAD: category '%s(%s)' scheduled for +load", | |
98 | _category_getClassName(cat), _category_getName(cat)); | |
99 | } | |
100 | ||
101 | if (loadable_categories_used == loadable_categories_allocated) { | |
102 | loadable_categories_allocated = loadable_categories_allocated*2 + 16; | |
103 | loadable_categories = | |
104 | _realloc_internal(loadable_categories, | |
105 | loadable_categories_allocated * | |
106 | sizeof(struct loadable_category)); | |
107 | } | |
108 | ||
109 | loadable_categories[loadable_categories_used].cat = cat; | |
110 | loadable_categories[loadable_categories_used].method = method; | |
111 | loadable_categories_used++; | |
112 | } | |
113 | ||
114 | ||
115 | /*********************************************************************** | |
116 | * remove_class_from_loadable_list | |
117 | * Class cls may have been loadable before, but it is now no longer | |
118 | * loadable (because its image is being unmapped). | |
119 | **********************************************************************/ | |
120 | __private_extern__ void remove_class_from_loadable_list(Class cls) | |
121 | { | |
122 | if (loadable_classes) { | |
123 | int i; | |
124 | for (i = 0; i < loadable_classes_used; i++) { | |
125 | if (loadable_classes[i].cls == cls) { | |
126 | loadable_classes[i].cls = NULL; | |
127 | if (PrintLoading) { | |
128 | _objc_inform("LOAD: class '%s' unscheduled for +load", _class_getName(cls)); | |
129 | } | |
130 | return; | |
131 | } | |
132 | } | |
133 | } | |
134 | } | |
135 | ||
136 | ||
137 | /*********************************************************************** | |
138 | * remove_category_from_loadable_list | |
139 | * Category cat may have been loadable before, but it is now no longer | |
140 | * loadable (because its image is being unmapped). | |
141 | **********************************************************************/ | |
142 | __private_extern__ void remove_category_from_loadable_list(Category cat) | |
143 | { | |
144 | if (loadable_categories) { | |
145 | int i; | |
146 | for (i = 0; i < loadable_categories_used; i++) { | |
147 | if (loadable_categories[i].cat == cat) { | |
148 | loadable_categories[i].cat = NULL; | |
149 | if (PrintLoading) { | |
150 | _objc_inform("LOAD: category '%s(%s)' unscheduled for +load", | |
151 | _category_getClassName(cat), | |
152 | _category_getName(cat)); | |
153 | } | |
154 | return; | |
155 | } | |
156 | } | |
157 | } | |
158 | } | |
159 | ||
160 | ||
161 | /*********************************************************************** | |
162 | * call_class_loads | |
163 | * Call all pending class +load methods. | |
164 | * If new classes become loadable, +load is NOT called for them. | |
165 | * | |
166 | * Called only by call_load_methods(). | |
167 | **********************************************************************/ | |
168 | static void call_class_loads(void) | |
169 | { | |
170 | int i; | |
171 | ||
172 | // Detach current loadable list. | |
173 | struct loadable_class *classes = loadable_classes; | |
174 | int used = loadable_classes_used; | |
175 | loadable_classes = NULL; | |
176 | loadable_classes_allocated = 0; | |
177 | loadable_classes_used = 0; | |
178 | ||
179 | // Call all +loads for the detached list. | |
180 | for (i = 0; i < used; i++) { | |
181 | Class cls = classes[i].cls; | |
182 | IMP load_method = classes[i].method; | |
183 | if (!cls) continue; | |
184 | ||
185 | if (PrintLoading) { | |
186 | _objc_inform("LOAD: +[%s load]\n", _class_getName(cls)); | |
187 | } | |
188 | (*load_method) ((id) cls, @selector(load)); | |
189 | } | |
190 | ||
191 | // Destroy the detached list. | |
192 | if (classes) _free_internal(classes); | |
193 | } | |
194 | ||
195 | ||
196 | /*********************************************************************** | |
197 | * call_category_loads | |
198 | * Call some pending category +load methods. | |
199 | * The parent class of the +load-implementing categories has all of | |
200 | * its categories attached, in case some are lazily waiting for +initalize. | |
201 | * Don't call +load unless the parent class is connected. | |
202 | * If new categories become loadable, +load is NOT called, and they | |
203 | * are added to the end of the loadable list, and we return TRUE. | |
204 | * Return FALSE if no new categories became loadable. | |
205 | * | |
206 | * Called only by call_load_methods(). | |
207 | **********************************************************************/ | |
208 | static BOOL call_category_loads(void) | |
209 | { | |
210 | int i, shift; | |
211 | BOOL new_categories_added = NO; | |
212 | ||
213 | // Detach current loadable list. | |
214 | struct loadable_category *cats = loadable_categories; | |
215 | int used = loadable_categories_used; | |
216 | int allocated = loadable_categories_allocated; | |
217 | loadable_categories = NULL; | |
218 | loadable_categories_allocated = 0; | |
219 | loadable_categories_used = 0; | |
220 | ||
221 | // Call all +loads for the detached list. | |
222 | for (i = 0; i < used; i++) { | |
223 | Category cat = cats[i].cat; | |
224 | IMP load_method = cats[i].method; | |
225 | Class cls; | |
226 | if (!cat) continue; | |
227 | ||
228 | cls = _category_getClass(cat); | |
229 | if (cls && _class_isLoadable(cls)) { | |
230 | if (PrintLoading) { | |
231 | _objc_inform("LOAD: +[%s(%s) load]\n", | |
232 | _class_getName(cls), | |
233 | _category_getName(cat)); | |
234 | } | |
235 | (*load_method) ((id) cls, @selector(load)); | |
236 | cats[i].cat = NULL; | |
237 | } | |
238 | } | |
239 | ||
240 | // Compact detached list (order-preserving) | |
241 | shift = 0; | |
242 | for (i = 0; i < used; i++) { | |
243 | if (cats[i].cat) { | |
244 | cats[i-shift] = cats[i]; | |
245 | } else { | |
246 | shift++; | |
247 | } | |
248 | } | |
249 | used -= shift; | |
250 | ||
251 | // Copy any new +load candidates from the new list to the detached list. | |
252 | new_categories_added = (loadable_categories_used > 0); | |
253 | for (i = 0; i < loadable_categories_used; i++) { | |
254 | if (used == allocated) { | |
255 | allocated = allocated*2 + 16; | |
256 | cats = _realloc_internal(cats, allocated * | |
257 | sizeof(struct loadable_category)); | |
258 | } | |
259 | cats[used++] = loadable_categories[i]; | |
260 | } | |
261 | ||
262 | // Destroy the new list. | |
263 | if (loadable_categories) _free_internal(loadable_categories); | |
264 | ||
265 | // Reattach the (now augmented) detached list. | |
266 | // But if there's nothing left to load, destroy the list. | |
267 | if (used) { | |
268 | loadable_categories = cats; | |
269 | loadable_categories_used = used; | |
270 | loadable_categories_allocated = allocated; | |
271 | } else { | |
272 | if (cats) _free_internal(cats); | |
273 | loadable_categories = NULL; | |
274 | loadable_categories_used = 0; | |
275 | loadable_categories_allocated = 0; | |
276 | } | |
277 | ||
278 | if (PrintLoading) { | |
279 | if (loadable_categories_used != 0) { | |
280 | _objc_inform("LOAD: %d categories still waiting for +load\n", | |
281 | loadable_categories_used); | |
282 | } | |
283 | } | |
284 | ||
285 | return new_categories_added; | |
286 | } | |
287 | ||
288 | ||
289 | /*********************************************************************** | |
290 | * call_load_methods | |
291 | * Call all pending class and category +load methods. | |
292 | * Class +load methods are called superclass-first. | |
293 | * Category +load methods are not called until after the parent class's +load. | |
294 | * | |
295 | * This method must be RE-ENTRANT, because a +load could trigger | |
296 | * more image mapping. In addition, the superclass-first ordering | |
297 | * must be preserved in the face of re-entrant calls. Therefore, | |
298 | * only the OUTERMOST call of this function will do anything, and | |
299 | * that call will handle all loadable classes, even those generated | |
300 | * while it was running. | |
301 | * | |
302 | * The sequence below preserves +load ordering in the face of | |
303 | * image loading during a +load, and make sure that no | |
304 | * +load method is forgotten because it was added during | |
305 | * a +load call. | |
306 | * Sequence: | |
307 | * 1. Repeatedly call class +loads until there aren't any more | |
308 | * 2. Call category +loads ONCE. | |
309 | * 3. Run more +loads if: | |
310 | * (a) there are more classes to load, OR | |
311 | * (b) there are some potential category +loads that have | |
312 | * still never been attempted. | |
313 | * Category +loads are only run once to ensure "parent class first" | |
314 | * ordering, even if a category +load triggers a new loadable class | |
315 | * and a new loadable category attached to that class. | |
316 | * | |
317 | * fixme this is not thread-safe, but neither is the rest of image mapping. | |
318 | **********************************************************************/ | |
319 | __private_extern__ void call_load_methods(void) | |
320 | { | |
321 | static pthread_t load_method_thread NOBSS = NULL; | |
322 | BOOL more_categories; | |
323 | ||
324 | if (load_method_thread) { | |
325 | // +loads are already being called. Do nothing, but complain | |
326 | // if it looks like multithreaded use of this thread-unsafe code. | |
327 | ||
328 | if (! pthread_equal(load_method_thread, pthread_self())) { | |
329 | _objc_inform("WARNING: multi-threaded library loading detected " | |
330 | "(implementation is not thread-safe)"); | |
331 | } | |
332 | return; | |
333 | } | |
334 | ||
335 | // Nobody else is calling +loads, so we should do it ourselves. | |
336 | load_method_thread = pthread_self(); | |
337 | ||
338 | do { | |
339 | // 1. Repeatedly call class +loads until there aren't any more | |
340 | while (loadable_classes_used > 0) { | |
341 | call_class_loads(); | |
342 | } | |
343 | ||
344 | // 2. Call category +loads ONCE | |
345 | more_categories = call_category_loads(); | |
346 | ||
347 | // 3. Run more +loads if there are classes OR more untried categories | |
348 | } while (loadable_classes_used > 0 || more_categories); | |
349 | ||
350 | load_method_thread = NULL; | |
351 | } | |
352 | ||
353 |