+ while (pending) {
+ PendingClassRef *next = pending->next;
+ if (pending->ref) *pending->ref = cls;
+ _free_internal(pending);
+ pending = next;
+ }
+
+ if (NXCountMapTable(pendingClassRefsMap) == 0) {
+ NXFreeMapTable(pendingClassRefsMap);
+ pendingClassRefsMap = NULL;
+ }
+}
+
+
+/***********************************************************************
+* resolve_subclasses_of_class
+* Fix up any pending subclasses of this class.
+**********************************************************************/
+static void resolve_subclasses_of_class(struct objc_class *cls)
+{
+ PendingSubclass *pending;
+
+ if (!pendingSubclassesMap) return; // no unresolved subclasses
+
+ pending = NXMapGet(pendingSubclassesMap, cls->name);
+ if (!pending) return; // no unresolved subclasses for this class
+
+ NXMapKeyFreeingRemove(pendingSubclassesMap, cls->name);
+
+ // Destroy the pending table if it's now empty, to save memory.
+ if (NXCountMapTable(pendingSubclassesMap) == 0) {
+ NXFreeMapTable(pendingSubclassesMap);
+ pendingSubclassesMap = NULL;
+ }
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: resolving subclasses of class '%s'", cls->name);
+ }
+
+ while (pending) {
+ PendingSubclass *next = pending->next;
+ if (pending->subclass) connect_class(pending->subclass);
+ _free_internal(pending);
+ pending = next;
+ }
+}
+
+
+/***********************************************************************
+* get_base_method_list
+* Returns the method list containing the class's own methods,
+* ignoring any method lists added by categories or class_addMethods.
+* Called only by add_class_to_loadable_list.
+* Does not hold methodListLock because add_class_to_loadable_list
+* does not manipulate in-use classes.
+**********************************************************************/
+static struct objc_method_list *get_base_method_list(struct objc_class *cls)
+{
+ struct objc_method_list **ptr;
+
+ if (!cls->methodLists) return NULL;
+ if (cls->info & CLS_NO_METHOD_ARRAY) return (struct objc_method_list *)cls->methodLists;
+ ptr = cls->methodLists;
+ if (!*ptr || *ptr == END_OF_METHODS_LIST) return NULL;
+ while ( *ptr != 0 && *ptr != END_OF_METHODS_LIST ) { ptr++; }
+ --ptr;
+ return *ptr;
+}
+
+
+/***********************************************************************
+* add_class_to_loadable_list
+* Class cls has just become connected. Schedule it for +load if
+* it implements a +load method.
+**********************************************************************/
+static void add_class_to_loadable_list(struct objc_class *cls)
+{
+ IMP method = NULL;
+ struct objc_method_list *mlist;
+
+ if (cls->isa->info & CLS_HAS_LOAD_METHOD) {
+ mlist = get_base_method_list(cls->isa);
+ if (mlist) {
+ method = lookupNamedMethodInMethodList (mlist, "load");
+ }
+ }
+ // Don't bother if cls has no +load method
+ if (!method) return;
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: class '%s' scheduled for +load", cls->name);
+ }
+
+ if (loadable_classes_used == loadable_classes_allocated) {
+ loadable_classes_allocated = loadable_classes_allocated*2 + 16;
+ loadable_classes =
+ _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.
+**********************************************************************/
+static void add_category_to_loadable_list(struct objc_category *cat)
+{
+ IMP method = NULL;
+ struct objc_method_list *mlist;
+
+ mlist = cat->class_methods;
+ if (mlist) {
+ method = lookupNamedMethodInMethodList (mlist, "load");
+ }
+ // Don't bother if cat has no +load method
+ if (!method) return;
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: category '%s(%s)' scheduled for +load",
+ cat->class_name, cat->category_name);
+ }
+
+ if (loadable_categories_used == loadable_categories_allocated) {
+ loadable_categories_allocated = loadable_categories_allocated*2 + 16;
+ loadable_categories =
+ _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).
+**********************************************************************/
+static void remove_class_from_loadable_list(struct objc_class *cls)
+{
+ 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", cls->name);
+ }
+ 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).
+**********************************************************************/
+static void remove_category_from_loadable_list(struct objc_category *cat)
+{
+ 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",
+ cat->class_name, cat->category_name);
+ }
+ 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++) {
+ struct objc_class *cls = classes[i].cls;
+ IMP load_method = classes[i].method;
+ if (!cls) continue;
+
+ if (PrintLoading) {
+ _objc_inform("LOAD: +[%s load]\n", cls->name);
+ }
+ (*load_method) ((id) cls, @selector(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++) {
+ struct objc_category *cat = cats[i].cat;
+ IMP load_method = cats[i].method;
+ struct objc_class *cls;
+ if (!cat) continue;
+
+ cls = objc_getClass(cat->class_name);
+ if (cls && class_is_connected(cls)) {
+ if (PrintLoading) {
+ _objc_inform("LOAD: +[%s(%s) load]\n",
+ cls->name, cat->category_name);
+ }
+ (*load_method) ((id) cls, @selector(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 = _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.
+*
+* fixme this is not thread-safe, but neither is the rest of image mapping.
+**********************************************************************/
+static void call_load_methods(void)
+{
+ static pthread_t load_method_thread NOBSS = NULL;
+ BOOL more_categories;
+
+ if (load_method_thread) {
+ // +loads are already being called. Do nothing, but complain
+ // if it looks like multithreaded use of this thread-unsafe code.
+
+ if (! pthread_equal(load_method_thread, pthread_self())) {
+ _objc_inform("WARNING: multi-threaded library loading detected "
+ "(implementation is not thread-safe)");
+ }
+ return;
+ }
+
+ // Nobody else is calling +loads, so we should do it ourselves.
+ load_method_thread = pthread_self();
+
+ 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);
+
+ load_method_thread = NULL;
+}
+
+
+/***********************************************************************
+* really_connect_class
+* Connect cls to superclass supercls unconditionally.
+* Also adjust the class hash tables and handle +load and pended subclasses.
+*
+* This should be called from connect_class() ONLY.
+**********************************************************************/
+static void really_connect_class(struct objc_class *cls,
+ struct objc_class *supercls)
+{
+ struct objc_class *oldCls;
+ struct objc_class *meta = cls->isa;
+
+ // Wire the classes together.
+ if (supercls) {
+ cls->super_class = supercls;
+ meta->super_class = supercls->isa;
+ meta->isa = supercls->isa->isa;
+ } else {
+ cls->super_class = NULL; // superclass of root class is NULL
+ meta->super_class = cls; // superclass of root metaclass is root class
+ meta->isa = meta; // metaclass of root metaclass is root metaclass
+ }
+
+ OBJC_LOCK(&classLock);
+
+ // Update hash tables.
+ NXHashRemove(unconnected_class_hash, cls);
+ oldCls = NXHashInsert(class_hash, cls);
+
+ // Delete unconnected_class_hash if it is now empty.
+ if (NXCountHashTable(unconnected_class_hash) == 0) {
+ NXFreeHashTable(unconnected_class_hash);
+ unconnected_class_hash = NULL;
+ }
+
+ OBJC_UNLOCK(&classLock);
+
+ // Warn if the new class has the same name as a previously-installed class.
+ // The new class is kept and the old class is discarded.
+ if (oldCls) {
+ const header_info *oldHeader = _headerForClass(oldCls);
+ const header_info *newHeader = _headerForClass(cls);
+ const char *oldName = _nameForHeader(oldHeader->mhdr);
+ const char *newName = _nameForHeader(newHeader->mhdr);
+
+ _objc_inform ("Both %s and %s have implementations of class %s.",
+ oldName, newName, oldCls->name);
+ _objc_inform ("Using implementation from %s.", newName);
+ }
+
+ // Prepare for +load and connect newly-connectable subclasses
+ add_class_to_loadable_list(cls);
+ resolve_subclasses_of_class(cls);
+
+ // GC debugging: make sure all classes with -dealloc also have -finalize
+ if (CheckFinalizers) {
+ extern IMP findIMPInClass(Class cls, SEL sel);
+ if (findIMPInClass(cls, sel_getUid("dealloc")) &&
+ ! findIMPInClass(cls, sel_getUid("finalize")))
+ {
+ _objc_inform("GC: class '%s' implements -dealloc but not -finalize", cls->name);
+ }
+ }
+
+ // Debugging: if this class has ivars, make sure this class's ivars don't
+ // overlap with its super's. This catches some broken fragile base classes.
+ // Do not use super->instance_size vs. self->ivar[0] to check this.
+ // Ivars may be packed across instance_size boundaries.
+ if (DebugFragileSuperclasses && cls->ivars && cls->ivars->ivar_count) {
+ struct objc_class *ivar_cls = supercls;
+
+ // Find closest superclass that has some ivars, if one exists.
+ while (ivar_cls &&
+ (!ivar_cls->ivars || ivar_cls->ivars->ivar_count == 0))
+ {
+ ivar_cls = ivar_cls->super_class;
+ }
+
+ if (ivar_cls) {
+ // Compare superclass's last ivar to this class's first ivar
+ struct objc_ivar *super_ivar =
+ &ivar_cls->ivars->ivar_list[ivar_cls->ivars->ivar_count - 1];
+ struct objc_ivar *self_ivar =
+ &cls->ivars->ivar_list[0];
+
+ // fixme could be smarter about super's ivar size
+ if (self_ivar->ivar_offset <= super_ivar->ivar_offset) {
+ _objc_inform("WARNING: ivars of superclass '%s' and "
+ "subclass '%s' overlap; superclass may have "
+ "changed since subclass was compiled",
+ ivar_cls->name, cls->name);
+ }
+ }
+ }
+}
+
+
+/***********************************************************************
+* connect_class
+* Connect class cls to its superclasses, if possible.
+* If cls becomes connected, move it from unconnected_class_hash
+* to connected_class_hash.
+* Returns TRUE if cls is connected.
+* Returns FALSE if cls could not be connected for some reason
+* (missing superclass or still-unconnected superclass)
+**********************************************************************/
+static BOOL connect_class(struct objc_class *cls)
+{
+ if (class_is_connected(cls)) {
+ // This class is already connected to its superclass.
+ // Do nothing.
+ return TRUE;
+ }
+ else if (cls->super_class == NULL) {
+ // This class is a root class.
+ // Connect it to itself.
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: class '%s' now connected (root class)",
+ cls->name);
+ }
+
+ really_connect_class(cls, NULL);
+ return TRUE;
+ }
+ else {
+ // This class is not a root class and is not yet connected.
+ // Connect it if its superclass and root class are already connected.
+ // Otherwise, add this class to the to-be-connected list,
+ // pending the completion of its superclass and root class.
+
+ // At this point, cls->super_class and cls->isa->isa are still STRINGS
+ char *supercls_name = (char *)cls->super_class;
+ struct objc_class *supercls;
+
+ // YES unconnected, YES class handler
+ if (NULL == (supercls = look_up_class(supercls_name, YES, YES))) {
+ // Superclass does not exist yet.
+ // pendClassInstallation will handle duplicate pends of this class
+ pendClassInstallation(cls, supercls_name);
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: class '%s' NOT connected (missing super)", cls->name);
+ }
+ return FALSE;
+ }
+
+ if (! connect_class(supercls)) {
+ // Superclass exists but is not yet connected.
+ // pendClassInstallation will handle duplicate pends of this class
+ pendClassInstallation(cls, supercls_name);
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: class '%s' NOT connected (unconnected super)", cls->name);
+ }
+ return FALSE;
+ }
+
+ // Superclass exists and is connected.
+ // Connect this class to the superclass.
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: class '%s' now connected", cls->name);
+ }
+
+ really_connect_class(cls, supercls);
+ return TRUE;
+ }