]> git.saurik.com Git - apple/objc4.git/blobdiff - runtime/objc-loadmethod.mm
objc4-532.tar.gz
[apple/objc4.git] / runtime / objc-loadmethod.mm
diff --git a/runtime/objc-loadmethod.mm b/runtime/objc-loadmethod.mm
new file mode 100644 (file)
index 0000000..f95e220
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * 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;
+}
+
+