--- /dev/null
+/*
+ * Copyright (c) 2004-2006 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/***********************************************************************
+* objc-loadmethod.m
+* Support for +load methods.
+**********************************************************************/
+
+#include "objc-loadmethod.h"
+#include "objc-private.h"
+
+typedef void(*load_method_t)(id, SEL);
+
+struct loadable_class {
+ Class cls; // may be NULL
+ IMP method;
+};
+
+struct loadable_category {
+ Category cat; // may be NULL
+ IMP method;
+};
+
+
+// List of classes that need +load called (pending superclass +load)
+// This list always has superclasses first because of the way it is constructed
+static struct loadable_class *loadable_classes = NULL;
+static int loadable_classes_used = 0;
+static int loadable_classes_allocated = 0;
+
+// List of categories that need +load called (pending parent class +load)
+static struct loadable_category *loadable_categories = NULL;
+static int loadable_categories_used = 0;
+static int loadable_categories_allocated = 0;
+
+
+/***********************************************************************
+* add_class_to_loadable_list
+* Class cls has just become connected. Schedule it for +load if
+* it implements a +load method.
+**********************************************************************/
+void add_class_to_loadable_list(Class cls)
+{
+ IMP method;
+
+ recursive_mutex_assert_locked(&loadMethodLock);
+
+ method = _class_getLoadMethod(cls);
+ if (!method) return; // Don't bother if cls has no +load method
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: class '%s' scheduled for +load", _class_getName(cls));
+ }
+
+ if (loadable_classes_used == loadable_classes_allocated) {
+ loadable_classes_allocated = loadable_classes_allocated*2 + 16;
+ loadable_classes = (struct loadable_class *)
+ _realloc_internal(loadable_classes,
+ loadable_classes_allocated *
+ sizeof(struct loadable_class));
+ }
+
+ loadable_classes[loadable_classes_used].cls = cls;
+ loadable_classes[loadable_classes_used].method = method;
+ loadable_classes_used++;
+}
+
+
+/***********************************************************************
+* add_category_to_loadable_list
+* Category cat's parent class exists and the category has been attached
+* to its class. Schedule this category for +load after its parent class
+* becomes connected and has its own +load method called.
+**********************************************************************/
+void add_category_to_loadable_list(Category cat)
+{
+ IMP method;
+
+ recursive_mutex_assert_locked(&loadMethodLock);
+
+ method = _category_getLoadMethod(cat);
+
+ // Don't bother if cat has no +load method
+ if (!method) return;
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: category '%s(%s)' scheduled for +load",
+ _category_getClassName(cat), _category_getName(cat));
+ }
+
+ if (loadable_categories_used == loadable_categories_allocated) {
+ loadable_categories_allocated = loadable_categories_allocated*2 + 16;
+ loadable_categories = (struct loadable_category *)
+ _realloc_internal(loadable_categories,
+ loadable_categories_allocated *
+ sizeof(struct loadable_category));
+ }
+
+ loadable_categories[loadable_categories_used].cat = cat;
+ loadable_categories[loadable_categories_used].method = method;
+ loadable_categories_used++;
+}
+
+
+/***********************************************************************
+* remove_class_from_loadable_list
+* Class cls may have been loadable before, but it is now no longer
+* loadable (because its image is being unmapped).
+**********************************************************************/
+void remove_class_from_loadable_list(Class cls)
+{
+ recursive_mutex_assert_locked(&loadMethodLock);
+
+ if (loadable_classes) {
+ int i;
+ for (i = 0; i < loadable_classes_used; i++) {
+ if (loadable_classes[i].cls == cls) {
+ loadable_classes[i].cls = NULL;
+ if (PrintLoading) {
+ _objc_inform("LOAD: class '%s' unscheduled for +load", _class_getName(cls));
+ }
+ return;
+ }
+ }
+ }
+}
+
+
+/***********************************************************************
+* remove_category_from_loadable_list
+* Category cat may have been loadable before, but it is now no longer
+* loadable (because its image is being unmapped).
+**********************************************************************/
+void remove_category_from_loadable_list(Category cat)
+{
+ recursive_mutex_assert_locked(&loadMethodLock);
+
+ if (loadable_categories) {
+ int i;
+ for (i = 0; i < loadable_categories_used; i++) {
+ if (loadable_categories[i].cat == cat) {
+ loadable_categories[i].cat = NULL;
+ if (PrintLoading) {
+ _objc_inform("LOAD: category '%s(%s)' unscheduled for +load",
+ _category_getClassName(cat),
+ _category_getName(cat));
+ }
+ return;
+ }
+ }
+ }
+}
+
+
+/***********************************************************************
+* call_class_loads
+* Call all pending class +load methods.
+* If new classes become loadable, +load is NOT called for them.
+*
+* Called only by call_load_methods().
+**********************************************************************/
+static void call_class_loads(void)
+{
+ int i;
+
+ // Detach current loadable list.
+ struct loadable_class *classes = loadable_classes;
+ int used = loadable_classes_used;
+ loadable_classes = NULL;
+ loadable_classes_allocated = 0;
+ loadable_classes_used = 0;
+
+ // Call all +loads for the detached list.
+ for (i = 0; i < used; i++) {
+ Class cls = classes[i].cls;
+ load_method_t load_method = (load_method_t)classes[i].method;
+ if (!cls) continue;
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
+ }
+ (*load_method)(cls, SEL_load);
+ }
+
+ // Destroy the detached list.
+ if (classes) _free_internal(classes);
+}
+
+
+/***********************************************************************
+* call_category_loads
+* Call some pending category +load methods.
+* The parent class of the +load-implementing categories has all of
+* its categories attached, in case some are lazily waiting for +initalize.
+* Don't call +load unless the parent class is connected.
+* If new categories become loadable, +load is NOT called, and they
+* are added to the end of the loadable list, and we return TRUE.
+* Return FALSE if no new categories became loadable.
+*
+* Called only by call_load_methods().
+**********************************************************************/
+static BOOL call_category_loads(void)
+{
+ int i, shift;
+ BOOL new_categories_added = NO;
+
+ // Detach current loadable list.
+ struct loadable_category *cats = loadable_categories;
+ int used = loadable_categories_used;
+ int allocated = loadable_categories_allocated;
+ loadable_categories = NULL;
+ loadable_categories_allocated = 0;
+ loadable_categories_used = 0;
+
+ // Call all +loads for the detached list.
+ for (i = 0; i < used; i++) {
+ Category cat = cats[i].cat;
+ load_method_t load_method = (load_method_t)cats[i].method;
+ Class cls;
+ if (!cat) continue;
+
+ cls = _category_getClass(cat);
+ if (cls && _class_isLoadable(cls)) {
+ if (PrintLoading) {
+ _objc_inform("LOAD: +[%s(%s) load]\n",
+ _class_getName(cls),
+ _category_getName(cat));
+ }
+ (*load_method)(cls, SEL_load);
+ cats[i].cat = NULL;
+ }
+ }
+
+ // Compact detached list (order-preserving)
+ shift = 0;
+ for (i = 0; i < used; i++) {
+ if (cats[i].cat) {
+ cats[i-shift] = cats[i];
+ } else {
+ shift++;
+ }
+ }
+ used -= shift;
+
+ // Copy any new +load candidates from the new list to the detached list.
+ new_categories_added = (loadable_categories_used > 0);
+ for (i = 0; i < loadable_categories_used; i++) {
+ if (used == allocated) {
+ allocated = allocated*2 + 16;
+ cats = (struct loadable_category *)
+ _realloc_internal(cats, allocated *
+ sizeof(struct loadable_category));
+ }
+ cats[used++] = loadable_categories[i];
+ }
+
+ // Destroy the new list.
+ if (loadable_categories) _free_internal(loadable_categories);
+
+ // Reattach the (now augmented) detached list.
+ // But if there's nothing left to load, destroy the list.
+ if (used) {
+ loadable_categories = cats;
+ loadable_categories_used = used;
+ loadable_categories_allocated = allocated;
+ } else {
+ if (cats) _free_internal(cats);
+ loadable_categories = NULL;
+ loadable_categories_used = 0;
+ loadable_categories_allocated = 0;
+ }
+
+ if (PrintLoading) {
+ if (loadable_categories_used != 0) {
+ _objc_inform("LOAD: %d categories still waiting for +load\n",
+ loadable_categories_used);
+ }
+ }
+
+ return new_categories_added;
+}
+
+
+/***********************************************************************
+* call_load_methods
+* Call all pending class and category +load methods.
+* Class +load methods are called superclass-first.
+* Category +load methods are not called until after the parent class's +load.
+*
+* This method must be RE-ENTRANT, because a +load could trigger
+* more image mapping. In addition, the superclass-first ordering
+* must be preserved in the face of re-entrant calls. Therefore,
+* only the OUTERMOST call of this function will do anything, and
+* that call will handle all loadable classes, even those generated
+* while it was running.
+*
+* The sequence below preserves +load ordering in the face of
+* image loading during a +load, and make sure that no
+* +load method is forgotten because it was added during
+* a +load call.
+* Sequence:
+* 1. Repeatedly call class +loads until there aren't any more
+* 2. Call category +loads ONCE.
+* 3. Run more +loads if:
+* (a) there are more classes to load, OR
+* (b) there are some potential category +loads that have
+* still never been attempted.
+* Category +loads are only run once to ensure "parent class first"
+* ordering, even if a category +load triggers a new loadable class
+* and a new loadable category attached to that class.
+*
+* Locking: loadMethodLock must be held by the caller
+* All other locks must not be held.
+**********************************************************************/
+void call_load_methods(void)
+{
+ static BOOL loading = NO;
+ BOOL more_categories;
+
+ recursive_mutex_assert_locked(&loadMethodLock);
+
+ // Re-entrant calls do nothing; the outermost call will finish the job.
+ if (loading) return;
+ loading = YES;
+
+ void *pool = objc_autoreleasePoolPush();
+
+ do {
+ // 1. Repeatedly call class +loads until there aren't any more
+ while (loadable_classes_used > 0) {
+ call_class_loads();
+ }
+
+ // 2. Call category +loads ONCE
+ more_categories = call_category_loads();
+
+ // 3. Run more +loads if there are classes OR more untried categories
+ } while (loadable_classes_used > 0 || more_categories);
+
+ objc_autoreleasePoolPop(pool);
+
+ loading = NO;
+}
+
+