/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * 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-runtime.m * Copyright 1988-1996, NeXT Software, Inc. * Author: s. naroff * **********************************************************************/ /*********************************************************************** * Class loading and connecting (GrP 2004-2-11) * * When images are loaded (during program startup or otherwise), the * runtime needs to load classes and categories from the images, connect * classes to superclasses and categories to parent classes, and call * +load methods. * * The Objective-C runtime can cope with classes arriving in any order. * That is, a class may be discovered by the runtime before some * superclass is known. To handle out-of-order class loads, the * runtime uses a "pending class" system. * * (Historical note) * Panther and earlier: many classes arrived out-of-order because of * the poorly-ordered callback from dyld. However, the runtime's * pending mechanism only handled "missing superclass" and not * "present superclass but missing higher class". See Radar #3225652. * Tiger: The runtime's pending mechanism was augmented to handle * arbitrary missing classes. In addition, dyld was rewritten and * now sends the callbacks in strictly bottom-up link order. * The pending mechanism may now be needed only for rare and * hard to construct programs. * (End historical note) * * A class when first seen in an image is considered "unconnected". * It is stored in `unconnected_class_hash`. If all of the class's * superclasses exist and are already "connected", then the new class * can be connected to its superclasses and moved to `class_hash` for * normal use. Otherwise, the class waits in `unconnected_class_hash` * until the superclasses finish connecting. * * A "connected" class is * (1) in `class_hash`, * (2) connected to its superclasses, * (3) has no unconnected superclasses, * (4) is otherwise initialized and ready for use, and * (5) is eligible for +load if +load has not already been called. * * An "unconnected" class is * (1) in `unconnected_class_hash`, * (2) not connected to its superclasses, * (3) has an immediate superclass which is either missing or unconnected, * (4) is not ready for use, and * (5) is not yet eligible for +load. * * Image mapping is NOT CURRENTLY THREAD-SAFE with respect to just about * * * anything. Image mapping IS RE-ENTRANT in several places: superclass * lookup may cause ZeroLink to load another image, and +load calls may * cause dyld to load another image. * * Image mapping sequence: * * Read all classes in all new images. * Add them all to unconnected_class_hash. * Note any +load implementations before categories are attached. * Fix up any pended classrefs referring to them. * Attach any pending categories. * Read all categories in all new images. * Attach categories whose parent class exists (connected or not), * and pend the rest. * Mark them all eligible for +load (if implemented), even if the * parent class is missing. * Try to connect all classes in all new images. * If the superclass is missing, pend the class * If the superclass is unconnected, try to recursively connect it * If the superclass is connected: * connect the class * mark the class eligible for +load, if implemented * connect any pended subclasses of the class * Resolve selector refs and class refs in all new images. * Class refs whose classes still do not exist are pended. * Fix up protocol objects in all new images. * Call +load for classes and categories. * May include classes or categories that are not in these images, * but are newly eligible because of these image. * Class +loads will be called superclass-first because of the * superclass-first nature of the connecting process. * Category +load needs to be deferred until the parent class is * connected and has had its +load called. * * Performance: all classes are read before any categories are read. * Fewer categories need be pended for lack of a parent class. * * Performance: all categories are attempted to be attached before * any classes are connected. Fewer class caches need be flushed. * (Unconnected classes and their respective subclasses are guaranteed * to be un-messageable, so their caches will be empty.) * * Performance: all classes are read before any classes are connected. * Fewer classes need be pended for lack of a superclass. * * Correctness: all selector and class refs are fixed before any * protocol fixups or +load methods. libobjc itself contains selector * and class refs which are used in protocol fixup and +load. * * Correctness: +load methods are scheduled in bottom-up link order. * This constraint is in addition to superclass order. Some +load * implementations expect to use another class in a linked-to library, * even if the two classes don't share a direct superclass relationship. * * Correctness: all classes are scanned for +load before any categories * are attached. Otherwise, if a category implements +load and its class * has no class methods, the class's +load scan would find the category's * +load method, which would then be called twice. * **********************************************************************/ /*********************************************************************** * Imports. **********************************************************************/ #include #include #include #include #include // project headers first, otherwise we get the installed ones #import "objc-class.h" #import #import #import "maptable.h" #import "objc-private.h" #import #import #import "objc-rtp.h" #import "objc-auto.h" #include #include #include #include #include /* NXHashTable SPI */ OBJC_EXPORT unsigned _NXHashCapacity(NXHashTable *table); OBJC_EXPORT void _NXHashRehashToCapacity(NXHashTable *table, unsigned newCapacity); OBJC_EXPORT Class _objc_getNonexistentClass(void); OBJC_EXPORT Class getOriginalClassForPosingClass(Class); /*********************************************************************** * Constants and macros internal to this module. **********************************************************************/ /* Turn on support for literal string objects. */ #define LITERAL_STRING_OBJECTS /*********************************************************************** * Types internal to this module. **********************************************************************/ typedef struct _objc_unresolved_category { struct _objc_unresolved_category * next; struct objc_category * cat; // may be NULL long version; } _objc_unresolved_category; typedef struct _PendingSubclass { struct objc_class *subclass; // subclass to finish connecting; may be NULL struct _PendingSubclass *next; } PendingSubclass; typedef struct _PendingClassRef { struct objc_class **ref; // class reference to fix up; may be NULL struct _PendingClassRef *next; } PendingClassRef; struct loadable_class { struct objc_class *cls; // may be NULL IMP method; }; struct loadable_category { struct objc_category *cat; // may be NULL IMP method; }; /*********************************************************************** * Exports. **********************************************************************/ // Function called after class has been fixed up (MACH only) void (*callbackFunction)(Class, const char *) = 0; // Lock for class hashtable OBJC_DECLARE_LOCK (classLock); // Settings from environment variables __private_extern__ int PrintImages = -1; // env OBJC_PRINT_IMAGES __private_extern__ int PrintLoading = -1; // env OBJC_PRINT_LOAD_METHODS __private_extern__ int PrintConnecting = -1; // env OBJC_PRINT_CONNECTION __private_extern__ int PrintRTP = -1; // env OBJC_PRINT_RTP __private_extern__ int PrintGC = -1; // env OBJC_PRINT_GC __private_extern__ int PrintSharing = -1; // env OBJC_PRINT_SHARING __private_extern__ int PrintCxxCtors = -1; // env OBJC_PRINT_CXX_CTORS __private_extern__ int UseInternalZone = -1; // env OBJC_USE_INTERNAL_ZONE __private_extern__ int AllowInterposing = -1;// env OBJC_ALLOW_INTERPOSING __private_extern__ int DebugUnload = -1; // env OBJC_DEBUG_UNLOAD __private_extern__ int DebugFragileSuperclasses = -1; // env OBJC_DEBUG_FRAGILE_SUPERCLASSES __private_extern__ int ForceGC = -1; // env OBJC_FORCE_GC __private_extern__ int ForceNoGC = -1; // env OBJC_FORCE_NO_GC __private_extern__ int CheckFinalizers = -1; // env OBJC_CHECK_FINALIZERS // objc's key for pthread_getspecific __private_extern__ pthread_key_t _objc_pthread_key = 0; // 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 NOBSS = NULL; static int loadable_classes_used NOBSS = 0; static int loadable_classes_allocated NOBSS = 0; // List of categories that need +load called (pending parent class +load) static struct loadable_category *loadable_categories NOBSS = NULL; static int loadable_categories_used NOBSS = 0; static int loadable_categories_allocated NOBSS = 0; // Selectors for which @selector() doesn't work __private_extern__ SEL cxx_construct_sel = NULL; __private_extern__ SEL cxx_destruct_sel = NULL; __private_extern__ const char *cxx_construct_name = ".cxx_construct"; __private_extern__ const char *cxx_destruct_name = ".cxx_destruct"; /*********************************************************************** * Function prototypes internal to this module. **********************************************************************/ static unsigned classHash (void * info, struct objc_class * data); static int classIsEqual (void * info, struct objc_class * name, struct objc_class * cls); static int _objc_defaultClassHandler (const char * clsName); static void _objcTweakMethodListPointerForClass (struct objc_class * cls); static void _objc_add_category_flush_caches(struct objc_class * cls, struct objc_category * category, int version); static void _objc_add_category(struct objc_class * cls, struct objc_category * category, int version); static void _objc_register_category (struct objc_category * cat, long version); static void _objc_read_categories_from_image (header_info * hi); static const header_info * _headerForClass (struct objc_class * cls); static NXMapTable * pendingClassRefsMapTable (void); static NXMapTable * pendingSubclassesMapTable (void); static void _objc_read_classes_from_image (header_info * hi); static void _objc_map_class_refs_for_image (header_info * hi); static void _objc_fixup_protocol_objects_for_image (header_info * hi); static void _objc_fixup_selector_refs (const header_info * hi); static void _objc_unmap_image(const headerType *mh); static BOOL connect_class(struct objc_class *cls); static void add_category_to_loadable_list(struct objc_category *cat); /*********************************************************************** * Static data internal to this module. **********************************************************************/ // we keep a linked list of header_info's describing each image as told to us by dyld static header_info *FirstHeader NOBSS = 0; // NULL means empty list static header_info *LastHeader NOBSS = 0; // NULL means invalid; recompute it // Hash table of classes static NXHashTable * class_hash NOBSS = 0; static NXHashTablePrototype classHashPrototype = { (unsigned (*) (const void *, const void *)) classHash, (int (*)(const void *, const void *, const void *)) classIsEqual, NXNoEffectFree, 0 }; // Hash table of unconnected classes static NXHashTable *unconnected_class_hash NOBSS = NULL; // Exported copy of class_hash variable (hook for debugging tools) NXHashTable *_objc_debug_class_hash = NULL; // Function pointer objc_getClass calls through when class is not found static int (*objc_classHandler) (const char *) = _objc_defaultClassHandler; // Function pointer called by objc_getClass and objc_lookupClass when // class is not found. _objc_classLoader is called before objc_classHandler. static BOOL (*_objc_classLoader)(const char *) = NULL; // Category and class registries // Keys are COPIES of strings, to prevent stale pointers with unloaded bundles // Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove static NXMapTable * category_hash = NULL; // Keys are COPIES of strings, to prevent stale pointers with unloaded bundles // Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove static NXMapTable * pendingClassRefsMap = NULL; static NXMapTable * pendingSubclassesMap = NULL; /*********************************************************************** * objc_dump_class_hash. Log names of all known classes. **********************************************************************/ void objc_dump_class_hash (void) { NXHashTable * table; unsigned count; struct objc_class * data; NXHashState state; table = class_hash; count = 0; state = NXInitHashState (table); while (NXNextHashState (table, &state, (void **) &data)) printf ("class %d: %s\n", ++count, data->name); } /*********************************************************************** * classHash. **********************************************************************/ static unsigned classHash (void * info, struct objc_class * data) { // Nil classes hash to zero if (!data) return 0; // Call through to real hash function return _objc_strhash ((unsigned char *) ((struct objc_class *) data)->name); } /*********************************************************************** * classIsEqual. Returns whether the class names match. If we ever * check more than the name, routines like objc_lookUpClass have to * change as well. **********************************************************************/ static int classIsEqual (void * info, struct objc_class * name, struct objc_class * cls) { // Standard string comparison // Our local inlined version is significantly shorter on PPC and avoids the // mflr/mtlr and dyld_stub overhead when calling strcmp. return _objc_strcmp(name->name, cls->name) == 0; } /*********************************************************************** * NXMapKeyCopyingInsert * Like NXMapInsert, but strdups the key if necessary. * Used to prevent stale pointers when bundles are unloaded. **********************************************************************/ static void *NXMapKeyCopyingInsert(NXMapTable *table, const void *key, const void *value) { void *realKey; void *realValue = NULL; if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { // key DOES exist in table - use table's key for insertion } else { // key DOES NOT exist in table - copy the new key before insertion realKey = _strdup_internal(key); } return NXMapInsert(table, realKey, value); } /*********************************************************************** * NXMapKeyFreeingRemove * Like NXMapRemove, but frees the existing key if necessary. * Used to prevent stale pointers when bundles are unloaded. **********************************************************************/ static void *NXMapKeyFreeingRemove(NXMapTable *table, const void *key) { void *realKey; void *realValue = NULL; if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { // key DOES exist in table - remove pair and free key realValue = NXMapRemove(table, realKey); _free_internal(realKey); // the key from the table, not necessarily the one given return realValue; } else { // key DOES NOT exist in table - nothing to do return NULL; } } /*********************************************************************** * _objc_init_class_hash. Return the class lookup table, create it if * necessary. **********************************************************************/ void _objc_init_class_hash (void) { // Do nothing if class hash table already exists if (class_hash) return; // class_hash starts small, with only enough capacity for libobjc itself. // If a second library is found by map_images(), class_hash is immediately // resized to capacity 1024 to cut down on rehashes. // Old numbers: A smallish Foundation+AppKit program will have // about 520 classes. Larger apps (like IB or WOB) have more like // 800 classes. Some customers have massive quantities of classes. // Foundation-only programs aren't likely to notice the ~6K loss. class_hash = NXCreateHashTableFromZone (classHashPrototype, 16, nil, _objc_internal_zone ()); _objc_debug_class_hash = class_hash; } /*********************************************************************** * objc_getClassList. Return the known classes. **********************************************************************/ int objc_getClassList(Class *buffer, int bufferLen) { NXHashState state; struct objc_class * class; int cnt, num; OBJC_LOCK(&classLock); num = NXCountHashTable(class_hash); if (NULL == buffer) { OBJC_UNLOCK(&classLock); return num; } cnt = 0; state = NXInitHashState(class_hash); while (cnt < bufferLen && NXNextHashState(class_hash, &state, (void **)&class)) { buffer[cnt++] = class; } OBJC_UNLOCK(&classLock); return num; } /*********************************************************************** * objc_getClasses. Return class lookup table. * * NOTE: This function is very dangerous, since you cannot safely use * the hashtable without locking it, and the lock is private! **********************************************************************/ void * objc_getClasses (void) { // Return the class lookup hash table return class_hash; } /*********************************************************************** * _objc_defaultClassHandler. Default objc_classHandler. Does nothing. **********************************************************************/ static int _objc_defaultClassHandler (const char * clsName) { // Return zero so objc_getClass doesn't bother re-searching return 0; } /*********************************************************************** * objc_setClassHandler. Set objc_classHandler to the specified value. * * NOTE: This should probably deal with userSuppliedHandler being NULL, * because the objc_classHandler caller does not check... it would bus * error. It would make sense to handle NULL by restoring the default * handler. Is anyone hacking with this, though? **********************************************************************/ void objc_setClassHandler (int (*userSuppliedHandler) (const char *)) { objc_classHandler = userSuppliedHandler; } /*********************************************************************** * look_up_class * Map a class name to a class using various methods. * This is the common implementation of objc_lookUpClass and objc_getClass, * and is also used internally to get additional search options. * Sequence: * 1. class_hash * 2. unconnected_class_hash (optional) * 3. classLoader callback * 4. classHandler callback (optional) **********************************************************************/ static id look_up_class(const char *aClassName, BOOL includeUnconnected, BOOL includeClassHandler) { BOOL includeClassLoader = YES; // class loader cannot be skipped id result = nil; struct objc_class query; query.name = aClassName; retry: if (!result && class_hash) { // Check ordinary classes OBJC_LOCK (&classLock); result = (id)NXHashGet(class_hash, &query); OBJC_UNLOCK (&classLock); } if (!result && includeUnconnected && unconnected_class_hash) { // Check not-yet-connected classes OBJC_LOCK(&classLock); result = (id)NXHashGet(unconnected_class_hash, &query); OBJC_UNLOCK(&classLock); } if (!result && includeClassLoader && _objc_classLoader) { // Try class loader callback if ((*_objc_classLoader)(aClassName)) { // Re-try lookup without class loader includeClassLoader = NO; goto retry; } } if (!result && includeClassHandler && objc_classHandler) { // Try class handler callback if ((*objc_classHandler)(aClassName)) { // Re-try lookup without class handler or class loader includeClassLoader = NO; includeClassHandler = NO; goto retry; } } return result; } /*********************************************************************** * objc_getClass. Return the id of the named class. If the class does * not exist, call _objc_classLoader and then objc_classHandler, either of * which may create a new class. * Warning: doesn't work if aClassName is the name of a posed-for class's isa! **********************************************************************/ id objc_getClass (const char * aClassName) { // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); } /*********************************************************************** * objc_getRequiredClass. * Same as objc_getClass, but kills the process if the class is not found. * This is used by ZeroLink, where failing to find a class would be a * compile-time link error without ZeroLink. **********************************************************************/ id objc_getRequiredClass(const char *aClassName) { id cls = objc_getClass(aClassName); if (!cls) _objc_fatal("link error: class '%s' not found.", aClassName); return cls; } /*********************************************************************** * objc_lookUpClass. Return the id of the named class. * If the class does not exist, call _objc_classLoader, which may create * a new class. * * Formerly objc_getClassWithoutWarning () **********************************************************************/ id objc_lookUpClass (const char * aClassName) { // NO unconnected, NO class handler return look_up_class(aClassName, NO, NO); } /*********************************************************************** * objc_getMetaClass. Return the id of the meta class the named class. * Warning: doesn't work if aClassName is the name of a posed-for class's isa! **********************************************************************/ id objc_getMetaClass (const char * aClassName) { struct objc_class * cls; cls = objc_getClass (aClassName); if (!cls) { _objc_inform ("class `%s' not linked into application", aClassName); return Nil; } return cls->isa; } /*********************************************************************** * objc_addClass. Add the specified class to the table of known classes, * after doing a little verification and fixup. **********************************************************************/ void objc_addClass (struct objc_class *cls) { // Synchronize access to hash table OBJC_LOCK (&classLock); // Make sure both the class and the metaclass have caches! // Clear all bits of the info fields except CLS_CLASS and CLS_META. // Normally these bits are already clear but if someone tries to cons // up their own class on the fly they might need to be cleared. if (cls->cache == NULL) { cls->cache = (Cache) &emptyCache; cls->info = CLS_CLASS; } if (cls->isa->cache == NULL) { cls->isa->cache = (Cache) &emptyCache; cls->isa->info = CLS_META; } // methodLists should be: // 1. NULL (Tiger and later only) // 2. A -1 terminated method list array // In either case, CLS_NO_METHOD_ARRAY remains clear. // If the user manipulates the method list directly, // they must use the magic private format. // Add the class to the table (void) NXHashInsert (class_hash, cls); // Desynchronize OBJC_UNLOCK (&classLock); } /*********************************************************************** * _objcTweakMethodListPointerForClass. * Change the class's method list pointer to a method list array. * Does nothing if the method list pointer is already a method list array. * If the class is currently in use, methodListLock must be held by the caller. **********************************************************************/ static void _objcTweakMethodListPointerForClass (struct objc_class * cls) { struct objc_method_list * originalList; const int initialEntries = 4; int mallocSize; struct objc_method_list ** ptr; // Do nothing if methodLists is already an array. if (cls->methodLists && !(cls->info & CLS_NO_METHOD_ARRAY)) return; // Remember existing list originalList = (struct objc_method_list *) cls->methodLists; // Allocate and zero a method list array mallocSize = sizeof(struct objc_method_list *) * initialEntries; ptr = (struct objc_method_list **) _calloc_internal(1, mallocSize); // Insert the existing list into the array ptr[initialEntries - 1] = END_OF_METHODS_LIST; ptr[0] = originalList; // Replace existing list with array cls->methodLists = ptr; _class_clearInfo(cls, CLS_NO_METHOD_ARRAY); } /*********************************************************************** * _objc_insertMethods. * Adds methods to a class. * Does not flush any method caches. * Does not take any locks. * If the class is already in use, use class_addMethods() instead. **********************************************************************/ void _objc_insertMethods(struct objc_class *cls, struct objc_method_list *mlist) { struct objc_method_list ***list; struct objc_method_list **ptr; int endIndex; int oldSize; int newSize; if (!cls->methodLists) { // cls has no methods - simply use this method list cls->methodLists = (struct objc_method_list **)mlist; _class_setInfo(cls, CLS_NO_METHOD_ARRAY); return; } // Create method list array if necessary _objcTweakMethodListPointerForClass(cls); list = &cls->methodLists; // Locate unused entry for insertion point ptr = *list; while ((*ptr != 0) && (*ptr != END_OF_METHODS_LIST)) ptr += 1; // If array is full, add to it if (*ptr == END_OF_METHODS_LIST) { // Calculate old and new dimensions endIndex = ptr - *list; oldSize = (endIndex + 1) * sizeof(void *); newSize = oldSize + sizeof(struct objc_method_list *); // only increase by 1 // Grow the method list array by one. // This block may be from user code; don't use _realloc_internal *list = (struct objc_method_list **)realloc(*list, newSize); // Zero out addition part of new array bzero (&((*list)[endIndex]), newSize - oldSize); // Place new end marker (*list)[(newSize/sizeof(void *)) - 1] = END_OF_METHODS_LIST; // Insertion point corresponds to old array end ptr = &((*list)[endIndex]); } // Right shift existing entries by one bcopy (*list, (*list) + 1, ((void *) ptr) - ((void *) *list)); // Insert at method list at beginning of array **list = mlist; } /*********************************************************************** * _objc_removeMethods. * Remove methods from a class. * Does not take any locks. * Does not flush any method caches. * If the class is currently in use, use class_removeMethods() instead. **********************************************************************/ void _objc_removeMethods(struct objc_class *cls, struct objc_method_list *mlist) { struct objc_method_list ***list; struct objc_method_list **ptr; if (cls->methodLists == NULL) { // cls has no methods return; } if (cls->methodLists == (struct objc_method_list **)mlist) { // mlist is the class's only method list - erase it cls->methodLists = NULL; return; } if (cls->info & CLS_NO_METHOD_ARRAY) { // cls has only one method list, and this isn't it - do nothing return; } // cls has a method list array - search it list = &cls->methodLists; // Locate list in the array ptr = *list; while (*ptr != mlist) { // fix for radar # 2538790 if ( *ptr == END_OF_METHODS_LIST ) return; ptr += 1; } // Remove this entry *ptr = 0; // Left shift the following entries while (*(++ptr) != END_OF_METHODS_LIST) *(ptr-1) = *ptr; *(ptr-1) = 0; } /*********************************************************************** * _objc_add_category. Install the specified category's methods and * protocols into the class it augments. * The class is assumed not to be in use yet: no locks are taken and * no method caches are flushed. **********************************************************************/ static inline void _objc_add_category(struct objc_class *cls, struct objc_category *category, int version) { if (PrintConnecting) { _objc_inform("CONNECT: attaching category '%s (%s)'", cls->name, category->category_name); } // Augment instance methods if (category->instance_methods) _objc_insertMethods (cls, category->instance_methods); // Augment class methods if (category->class_methods) _objc_insertMethods (cls->isa, category->class_methods); // Augment protocols if ((version >= 5) && category->protocols) { if (cls->isa->version >= 5) { category->protocols->next = cls->protocols; cls->protocols = category->protocols; cls->isa->protocols = category->protocols; } else { _objc_inform ("unable to add protocols from category %s...\n", category->category_name); _objc_inform ("class `%s' must be recompiled\n", category->class_name); } } } /*********************************************************************** * _objc_add_category_flush_caches. Install the specified category's * methods into the class it augments, and flush the class' method cache. **********************************************************************/ static void _objc_add_category_flush_caches(struct objc_class *cls, struct objc_category *category, int version) { // Install the category's methods into its intended class OBJC_LOCK(&methodListLock); _objc_add_category (cls, category, version); OBJC_UNLOCK(&methodListLock); // Flush caches so category's methods can get called _objc_flush_caches (cls); } /*********************************************************************** * reverse_cat * Reverse the given linked list of pending categories. * The pending category list is built backwards, and needs to be * reversed before actually attaching the categories to a class. * Returns the head of the new linked list. **********************************************************************/ static _objc_unresolved_category *reverse_cat(_objc_unresolved_category *cat) { if (!cat) return NULL; _objc_unresolved_category *prev = NULL; _objc_unresolved_category *cur = cat; _objc_unresolved_category *ahead = cat->next; while (cur) { ahead = cur->next; cur->next = prev; prev = cur; cur = ahead; } return prev; } /*********************************************************************** * resolve_categories_for_class. * Install all existing categories intended for the specified class. * cls must be a true class and not a metaclass. **********************************************************************/ static void resolve_categories_for_class(struct objc_class *cls) { _objc_unresolved_category * pending; _objc_unresolved_category * next; // Nothing to do if there are no categories at all if (!category_hash) return; // Locate and remove first element in category list // associated with this class pending = NXMapKeyFreeingRemove (category_hash, cls->name); // Traverse the list of categories, if any, registered for this class // The pending list is built backwards. Reverse it and walk forwards. pending = reverse_cat(pending); while (pending) { if (pending->cat) { // Install the category // use the non-flush-cache version since we are only // called from the class intialization code _objc_add_category(cls, pending->cat, pending->version); } // Delink and reclaim this registration next = pending->next; _free_internal(pending); pending = next; } } /*********************************************************************** * _objc_resolve_categories_for_class. * Public version of resolve_categories_for_class. This was * exported pre-10.4 for Omni et al. to workaround a problem * with too-lazy category attachment. * cls should be a class, but this function can also cope with metaclasses. **********************************************************************/ void _objc_resolve_categories_for_class(struct objc_class *cls) { // If cls is a metaclass, get the class. // resolve_categories_for_class() requires a real class to work correctly. if (ISMETA(cls)) { if (strncmp(cls->name, "_%", 2) == 0) { // Posee's meta's name is smashed and isn't in the class_hash, // so objc_getClass doesn't work. char *baseName = strchr(cls->name, '%'); // get posee's real name cls = objc_getClass(baseName); } else { cls = objc_getClass(cls->name); } } resolve_categories_for_class(cls); } /*********************************************************************** * _objc_register_category. * Process a category read from an image. * If the category's class exists, attach the category immediately. * If the category's class does not exist yet, pend the category for * later attachment. Pending categories are attached in the order * they were discovered. **********************************************************************/ static void _objc_register_category(struct objc_category *cat, long version) { _objc_unresolved_category * new_cat; _objc_unresolved_category * old; struct objc_class *theClass; // If the category's class exists, attach the category. if ((theClass = objc_lookUpClass(cat->class_name))) { _objc_add_category_flush_caches(theClass, cat, version); return; } // If the category's class exists but is unconnected, // then attach the category to the class but don't bother // flushing any method caches (because they must be empty). // YES unconnected, NO class_handler if ((theClass = look_up_class(cat->class_name, YES, NO))) { _objc_add_category(theClass, cat, version); return; } // Category's class does not exist yet. // Save the category for later attachment. if (PrintConnecting) { _objc_inform("CONNECT: pending category '%s (%s)'", cat->class_name, cat->category_name); } // Create category lookup table if needed if (!category_hash) category_hash = NXCreateMapTableFromZone (NXStrValueMapPrototype, 128, _objc_internal_zone ()); // Locate an existing list of categories, if any, for the class. old = NXMapGet (category_hash, cat->class_name); // Register the category to be fixed up later. // The category list is built backwards, and is reversed again // by resolve_categories_for_class(). new_cat = _malloc_internal(sizeof(_objc_unresolved_category)); new_cat->next = old; new_cat->cat = cat; new_cat->version = version; (void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat); } /*********************************************************************** * _objc_read_categories_from_image. * Read all categories from the given image. * Install them on their parent classes, or register them for later * installation. * Register them for later +load, if implemented. **********************************************************************/ static void _objc_read_categories_from_image (header_info * hi) { Module mods; unsigned int midx; if (_objcHeaderIsReplacement(hi)) { // Ignore any categories in this image return; } // Major loop - process all modules in the header mods = hi->mod_ptr; // NOTE: The module and category lists are traversed backwards // to preserve the pre-10.4 processing order. Changing the order // would have a small chance of introducing binary compatibility bugs. midx = hi->mod_count; while (midx-- > 0) { unsigned int index; unsigned int total; // Nothing to do for a module without a symbol table if (mods[midx].symtab == NULL) continue; // Total entries in symbol table (class entries followed // by category entries) total = mods[midx].symtab->cls_def_cnt + mods[midx].symtab->cat_def_cnt; // Minor loop - register all categories from given module index = total; while (index-- > mods[midx].symtab->cls_def_cnt) { struct objc_category *cat = mods[midx].symtab->defs[index]; _objc_register_category(cat, mods[midx].version); add_category_to_loadable_list(cat); } } } /*********************************************************************** * _headerForAddress. * addr can be a class or a category **********************************************************************/ static const header_info *_headerForAddress(void *addr) { unsigned long size; unsigned long seg; header_info * hInfo; // Check all headers in the vector for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) { // Locate header data, if any if (!hInfo->objcSegmentHeader) continue; seg = hInfo->objcSegmentHeader->vmaddr + hInfo->image_slide; size = hInfo->objcSegmentHeader->filesize; // Is the class in this header? if ((seg <= (unsigned long) addr) && ((unsigned long) addr < (seg + size))) return hInfo; } // Not found return 0; } /*********************************************************************** * _headerForClass * Return the image header containing this class, or NULL. * Returns NULL on runtime-constructed classes, and the NSCF classes. **********************************************************************/ static const header_info *_headerForClass(struct objc_class *cls) { return _headerForAddress(cls); } /*********************************************************************** * _nameForHeader. **********************************************************************/ const char * _nameForHeader (const headerType * header) { return _getObjcHeaderName ((headerType *) header); } /*********************************************************************** * class_is_connected. * Returns TRUE if class cls is connected. * A connected class has either a connected superclass or a NULL superclass, * and is present in class_hash. **********************************************************************/ static BOOL class_is_connected(struct objc_class *cls) { BOOL result; OBJC_LOCK(&classLock); result = NXHashMember(class_hash, cls); OBJC_UNLOCK(&classLock); return result; } /*********************************************************************** * pendingClassRefsMapTable. Return a pointer to the lookup table for * pending class refs. **********************************************************************/ static inline NXMapTable *pendingClassRefsMapTable(void) { // Allocate table if needed if (!pendingClassRefsMap) { pendingClassRefsMap = NXCreateMapTableFromZone(NXStrValueMapPrototype, 10, _objc_internal_zone ()); } // Return table pointer return pendingClassRefsMap; } /*********************************************************************** * pendingSubclassesMapTable. Return a pointer to the lookup table for * pending subclasses. **********************************************************************/ static inline NXMapTable *pendingSubclassesMapTable(void) { // Allocate table if needed if (!pendingSubclassesMap) { pendingSubclassesMap = NXCreateMapTableFromZone(NXStrValueMapPrototype, 10, _objc_internal_zone ()); } // Return table pointer return pendingSubclassesMap; } /*********************************************************************** * pendClassInstallation * Finish connecting class cls when its superclass becomes connected. * Check for multiple pends of the same class because connect_class does not. **********************************************************************/ static void pendClassInstallation(struct objc_class *cls, const char *superName) { NXMapTable *table; PendingSubclass *pending; PendingSubclass *oldList; PendingSubclass *l; // Create and/or locate pending class lookup table table = pendingSubclassesMapTable (); // Make sure this class isn't already in the pending list. oldList = NXMapGet (table, superName); for (l = oldList; l != NULL; l = l->next) { if (l->subclass == cls) return; // already here, nothing to do } // Create entry referring to this class pending = _malloc_internal(sizeof(PendingSubclass)); pending->subclass = cls; // Link new entry into head of list of entries for this class pending->next = oldList; // (Re)place entry list in the table (void) NXMapKeyCopyingInsert (table, superName, pending); } /*********************************************************************** * pendClassReference * Fix up a class ref when the class with the given name becomes connected. **********************************************************************/ static void pendClassReference(struct objc_class **ref, const char *className) { NXMapTable *table; PendingClassRef *pending; // Create and/or locate pending class lookup table table = pendingClassRefsMapTable (); // Create entry containing the class reference pending = _malloc_internal(sizeof(PendingClassRef)); pending->ref = ref; // Link new entry into head of list of entries for this class pending->next = NXMapGet (table, className); // (Re)place entry list in the table (void) NXMapKeyCopyingInsert (table, className, pending); if (PrintConnecting) { _objc_inform("CONNECT: pended reference to class '%s' at %p", className, (void *)ref); } } /*********************************************************************** * resolve_references_to_class * Fix up any pending class refs to this class. **********************************************************************/ static void resolve_references_to_class(struct objc_class *cls) { PendingClassRef *pending; if (!pendingClassRefsMap) return; // no unresolved refs for any class pending = NXMapGet(pendingClassRefsMap, cls->name); if (!pending) return; // no unresolved refs for this class NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name); if (PrintConnecting) { _objc_inform("CONNECT: resolving references to class '%s'", cls->name); } 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; } } /*********************************************************************** * _objc_read_classes_from_image. * Read classes from the given image, perform assorted minor fixups, * scan for +load implementation. * Does not connect classes to superclasses. * Does attach pended categories to the classes. * Adds all classes to unconnected_class_hash. class_hash is unchanged. **********************************************************************/ static void _objc_read_classes_from_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; int isBundle = (hi->mhdr->filetype == MH_BUNDLE); if (_objcHeaderIsReplacement(hi)) { // Ignore any classes in this image return; } // class_hash starts small, enough only for libobjc itself. // If other Objective-C libraries are found, immediately resize // class_hash, assuming that Foundation and AppKit are about // to add lots of classes. OBJC_LOCK(&classLock); if (hi->mhdr != &_mh_dylib_header && _NXHashCapacity(class_hash) < 1024) { _NXHashRehashToCapacity(class_hash, 1024); } OBJC_UNLOCK(&classLock); // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == NULL) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { struct objc_class * newCls; struct objc_method_list *mlist; // Locate the class description pointer newCls = mods[midx].symtab->defs[index]; // Classes loaded from Mach-O bundles can be unloaded later. // Nothing uses this class yet, so _class_setInfo is not needed. if (isBundle) newCls->info |= CLS_FROM_BUNDLE; if (isBundle) newCls->isa->info |= CLS_FROM_BUNDLE; // Use common static empty cache instead of NULL if (newCls->cache == NULL) newCls->cache = (Cache) &emptyCache; if (newCls->isa->cache == NULL) newCls->isa->cache = (Cache) &emptyCache; // Set metaclass version newCls->isa->version = mods[midx].version; // methodLists is NULL or a single list, not an array newCls->info |= CLS_NO_METHOD_ARRAY; newCls->isa->info |= CLS_NO_METHOD_ARRAY; // Check for +load implementation before categories are attached if ((mlist = get_base_method_list(newCls->isa))) { if (lookupNamedMethodInMethodList (mlist, "load")) { newCls->isa->info |= CLS_HAS_LOAD_METHOD; } } // Install into unconnected_class_hash OBJC_LOCK(&classLock); if (!unconnected_class_hash) { unconnected_class_hash = NXCreateHashTableFromZone(classHashPrototype, 128, NULL, _objc_internal_zone()); } NXHashInsert(unconnected_class_hash, newCls); OBJC_UNLOCK(&classLock); // Fix up pended class refs to this class, if any resolve_references_to_class(newCls); // Attach pended categories for this class, if any resolve_categories_for_class(newCls); } } } /*********************************************************************** * _objc_connect_classes_from_image. * Connect the classes in the given image to their superclasses, * or register them for later connection if any superclasses are missing. **********************************************************************/ static void _objc_connect_classes_from_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; BOOL replacement = _objcHeaderIsReplacement(hi); // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == NULL) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { struct objc_class *cls = mods[midx].symtab->defs[index]; if (! replacement) { BOOL connected = connect_class(cls); if (connected && callbackFunction) { (*callbackFunction)(cls, 0); } } else { // Replacement image - fix up super_class only (#3704817) const char *super_name = (const char *) cls->super_class; if (super_name) cls->super_class = objc_getClass(super_name); } } } } /*********************************************************************** * _objc_map_class_refs_for_image. Convert the class ref entries from * a class name string pointer to a class pointer. If the class does * not yet exist, the reference is added to a list of pending references * to be fixed up at a later date. **********************************************************************/ static void _objc_map_class_refs_for_image (header_info * hi) { struct objc_class * * cls_refs; unsigned int size; unsigned int index; // Locate class refs in image cls_refs = _getObjcClassRefs ((headerType *) hi->mhdr, &size); if (!cls_refs) return; cls_refs = (struct objc_class * *) ((unsigned long) cls_refs + hi->image_slide); // Process each class ref for (index = 0; index < size; index += 1) { const char * ref; struct objc_class * cls; // Get ref to convert from name string to class pointer ref = (const char *) cls_refs[index]; // Get pointer to class of this name // YES unconnected, YES class loader cls = look_up_class(ref, YES, YES); if (cls) { // Referenced class exists. Fix up the reference. cls_refs[index] = cls; } else { // Referenced class does not exist yet. Insert a placeholder // class and fix up the reference later. pendClassReference (&cls_refs[index], ref); cls_refs[index] = _objc_getNonexistentClass (); } } } /*********************************************************************** * _objc_remove_pending_class_refs_in_image * Delete any pending class ref fixups for class refs in the given image, * because the image is about to be unloaded. **********************************************************************/ static void _objc_remove_pending_class_refs_in_image(header_info *hi) { struct objc_class **cls_refs, **cls_refs_end; unsigned int size; if (!pendingClassRefsMap) return; // Locate class refs in this image cls_refs = _getObjcClassRefs ((headerType *) hi->mhdr, &size); if (!cls_refs) return; cls_refs = (struct objc_class **) ((uintptr_t)cls_refs + hi->image_slide); cls_refs_end = (struct objc_class **)(size + (uintptr_t)cls_refs); // Search the pending class ref table for class refs in this range. // The class refs may have already been stomped with nonexistentClass, // so there's no way to recover the original class name. const char *key; PendingClassRef *pending; NXMapState state = NXInitMapState(pendingClassRefsMap); while(NXNextMapState(pendingClassRefsMap, &state, (const void **)&key, (const void **)&pending)) { for ( ; pending != NULL; pending = pending->next) { if (pending->ref >= cls_refs && pending->ref < cls_refs_end) { pending->ref = NULL; } } } } /*********************************************************************** * map_selrefs. Register each selector in the specified array. If a * given selector is already registered, update this array to point to * the registered selector string. * If copy is TRUE, all selector data is always copied. This is used * for registering selectors from unloadable bundles, so the selector * can still be used after the bundle's data segment is unmapped. * Returns YES if dst was written to, NO if it was unchanged. **********************************************************************/ static inline BOOL map_selrefs(SEL *src, SEL *dst, size_t size, BOOL copy) { BOOL result = NO; unsigned int cnt = size / sizeof(SEL); unsigned int index; sel_lock(); // Process each selector for (index = 0; index < cnt; index += 1) { SEL sel; // Lookup pointer to uniqued string sel = sel_registerNameNoLock((const char *) src[index], copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) if (dst[index] != sel) { dst[index] = sel; result = YES; } } sel_unlock(); return result; } /*********************************************************************** * map_method_descs. For each method in the specified method list, * replace the name pointer with a uniqued selector. * If copy is TRUE, all selector data is always copied. This is used * for registering selectors from unloadable bundles, so the selector * can still be used after the bundle's data segment is unmapped. **********************************************************************/ static void map_method_descs (struct objc_method_description_list * methods, BOOL copy) { unsigned int index; sel_lock(); // Process each method for (index = 0; index < methods->count; index += 1) { struct objc_method_description * method; SEL sel; // Get method entry to fix up method = &methods->list[index]; // Lookup pointer to uniqued string sel = sel_registerNameNoLock((const char *) method->name, copy); // Replace this selector with uniqued one (avoid // modifying the VM page if this would be a NOP) if (method->name != sel) method->name = sel; } sel_unlock(); } /*********************************************************************** * _fixup. **********************************************************************/ @interface Protocol(RuntimePrivate) + _fixup: (OBJC_PROTOCOL_PTR)protos numElements: (int) nentries; @end /*********************************************************************** * _objc_fixup_protocol_objects_for_image. For each protocol in the * specified image, selectorize the method names and call +_fixup. **********************************************************************/ static void _objc_fixup_protocol_objects_for_image (header_info * hi) { unsigned int size; OBJC_PROTOCOL_PTR protos; unsigned int index; int isBundle = hi->mhdr->filetype == MH_BUNDLE; // Locate protocols in the image protos = (OBJC_PROTOCOL_PTR) _getObjcProtocols ((headerType *) hi->mhdr, &size); if (!protos) return; // Apply the slide bias protos = (OBJC_PROTOCOL_PTR) ((unsigned long) protos + hi->image_slide); // Process each protocol for (index = 0; index < size; index += 1) { // Selectorize the instance methods if (protos[index] OBJC_PROTOCOL_DEREF instance_methods) map_method_descs (protos[index] OBJC_PROTOCOL_DEREF instance_methods, isBundle); // Selectorize the class methods if (protos[index] OBJC_PROTOCOL_DEREF class_methods) map_method_descs (protos[index] OBJC_PROTOCOL_DEREF class_methods, isBundle); } // Invoke Protocol class method to fix up the protocol [Protocol _fixup:(OBJC_PROTOCOL_PTR)protos numElements:size]; } /*********************************************************************** * _objc_headerStart. Return what headers we know about. **********************************************************************/ header_info * _objc_headerStart () { // Take advatage of our previous work return FirstHeader; } void _objc_bindModuleContainingList() { /* We define this for backwards binary compat with things which should not * have been using it (cough OmniWeb), but now it does nothing for them. */ } /*********************************************************************** * _objc_addHeader. **********************************************************************/ // tested with 2; typical case is 4, but OmniWeb & Mail push it towards 20 #define HINFO_SIZE 16 static int HeaderInfoCounter NOBSS = 0; static header_info HeaderInfoTable[HINFO_SIZE] NOBSS = { {0} }; static header_info * _objc_addHeader(const struct mach_header *header) { int mod_count = 0; uintptr_t mod_unslid; uint32_t info_size = 0; uintptr_t image_info_unslid; const struct segment_command *objc_segment; ptrdiff_t slide; header_info *result; // Locate the __OBJC segment objc_segment = getsegbynamefromheader(header, SEG_OBJC); if (!objc_segment) return NULL; // Locate some sections in the __OBJC segment mod_unslid = (uintptr_t)_getObjcModules(header, &mod_count); if (!mod_unslid) return NULL; image_info_unslid = (uintptr_t)_getObjcImageInfo(header, &info_size); // Calculate vm slide. slide = _getImageSlide(header); // Find or allocate a header_info entry. if (HeaderInfoCounter < HINFO_SIZE) { result = &HeaderInfoTable[HeaderInfoCounter++]; } else { result = _malloc_internal(sizeof(header_info)); } // Set up the new header_info entry. result->mhdr = header; result->mod_ptr = (Module)(mod_unslid + slide); result->mod_count = mod_count; result->image_slide = slide; result->objcSegmentHeader = objc_segment; if (image_info_unslid) { result->info = (objc_image_info *)(image_info_unslid + slide); } else { result->info = NULL; } // Make sure every copy of objc_image_info in this image is the same. // This means same version and same bitwise contents. if (result->info) { objc_image_info *start = result->info; objc_image_info *end = (objc_image_info *)(info_size + (uint8_t *)start); objc_image_info *info = start; while (info < end) { // version is byte size, except for version 0 size_t struct_size = info->version; if (struct_size == 0) struct_size = 2 * sizeof(uint32_t); if (info->version != start->version || 0 != memcmp(info, start, struct_size)) { _objc_fatal("'%s' has inconsistently-compiled Objective-C " "code. Please recompile all code in it.", _nameForHeader(header)); } info = (objc_image_info *)(struct_size + (uint8_t *)info); } } // Add the header to the header list. // The header is appended to the list, to preserve the bottom-up order. result->next = NULL; if (!FirstHeader) { // list is empty FirstHeader = LastHeader = result; } else { if (!LastHeader) { // list is not empty, but LastHeader is invalid - recompute it LastHeader = FirstHeader; while (LastHeader->next) LastHeader = LastHeader->next; } // LastHeader is now valid LastHeader->next = result; LastHeader = result; } return result; } /*********************************************************************** * _objc_RemoveHeader * Remove the given header from the header list. * FirstHeader is updated. * LastHeader is set to NULL. Any code that uses LastHeader must * detect this NULL and recompute LastHeader by traversing the list. **********************************************************************/ static void _objc_removeHeader(header_info *hi) { header_info **hiP; for (hiP = &FirstHeader; *hiP != NULL; hiP = &(**hiP).next) { if (*hiP == hi) { header_info *deadHead = *hiP; // Remove from the linked list (updating FirstHeader if necessary). *hiP = (**hiP).next; // Update LastHeader if necessary. if (LastHeader == deadHead) { LastHeader = NULL; // will be recomputed next time it's used } // Free the memory, unless it was in the static HeaderInfoTable. if (deadHead < HeaderInfoTable || deadHead >= HeaderInfoTable + HINFO_SIZE) { _free_internal(deadHead); } break; } } } /*********************************************************************** * check_gc * Check whether the executable supports or requires GC, and make sure * all already-loaded libraries support the executable's GC mode. * Returns TRUE if the executable wants GC on. **********************************************************************/ static BOOL check_wants_gc(void) { // GC is off in Tiger. return NO; /* const header_info *hi; BOOL appWantsGC; // Environment variables can override the following. if (ForceGC) { _objc_inform("GC: forcing GC ON because OBJC_FORCE_GC is set"); appWantsGC = YES; } else if (ForceNoGC) { _objc_inform("GC: forcing GC OFF because OBJC_FORCE_NO_GC is set"); appWantsGC = NO; } else { // Find the executable and check its GC bits. // If the executable cannot be found, default to NO. // (The executable will not be found if the executable contains // no Objective-C code.) appWantsGC = NO; for (hi = FirstHeader; hi != NULL; hi = hi->next) { if (hi->mhdr->filetype == MH_EXECUTE) { appWantsGC = _objcHeaderSupportsGC(hi) ? YES : NO; if (PrintGC) { _objc_inform("GC: executable '%s' %s GC", _nameForHeader(hi->mhdr), appWantsGC ? "supports" : "does not support"); } } } } return appWantsGC; */ } /*********************************************************************** * verify_gc_readiness * if we want gc, verify that every header describes files compiled * and presumably ready for gc. ************************************************************************/ static void verify_gc_readiness(BOOL wantsGC, header_info *hi) { BOOL busted = NO; // Find the libraries and check their GC bits against the app's request for (; hi != NULL; hi = hi->next) { if (hi->mhdr->filetype == MH_EXECUTE) { continue; } else if (hi->mhdr == &_mh_dylib_header) { // libobjc itself works with anything even though it is not // compiled with -fobjc-gc (fixme should it be?) } else if (wantsGC && ! _objcHeaderSupportsGC(hi)) { // App wants GC but library does not support it - bad _objc_inform("'%s' was not compiled with -fobjc-gc, but " "the application requires GC", _nameForHeader(hi->mhdr)); busted = YES; } if (PrintGC) { _objc_inform("GC: library '%s' %s GC", _nameForHeader(hi->mhdr), _objcHeaderSupportsGC(hi) ? "supports" : "does not support"); } } if (busted) { // GC state is not consistent. // Kill the process unless one of the forcing flags is set. if (!ForceGC && !ForceNoGC) { _objc_fatal("*** GC capability of application and some libraries did not match"); } } } /*********************************************************************** * _objc_fixup_selector_refs. Register all of the selectors in each * image, and fix them all up. **********************************************************************/ static void _objc_fixup_selector_refs (const header_info * hi) { unsigned int count; Module mods; vm_address_t local_sels; vm_size_t local_size; mods = hi->mod_ptr; // Fix up message refs local_sels = (vm_address_t) _getObjcMessageRefs ((headerType *) hi->mhdr, &count); local_size = count * sizeof(SEL); if (local_sels) { vm_address_t aligned_start, aligned_end; local_sels = local_sels + hi->image_slide; aligned_start = round_page(local_sels); aligned_end = trunc_page(local_sels + local_size); map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size, hi->mhdr->filetype == MH_BUNDLE); } } /*********************************************************************** * objc_setConfiguration * Read environment variables that affect the runtime. * Also print environment variable help, if requested. **********************************************************************/ static void objc_setConfiguration() { int PrintHelp = (getenv("OBJC_HELP") != NULL); int PrintOptions = (getenv("OBJC_PRINT_OPTIONS") != NULL); if (PrintHelp) { _objc_inform("OBJC_HELP: describe Objective-C runtime environment variables"); if (PrintOptions) { _objc_inform("OBJC_HELP is set"); } _objc_inform("OBJC_PRINT_OPTIONS: list which options are set"); } if (PrintOptions) { _objc_inform("OBJC_PRINT_OPTIONS is set"); } #define OPTION(var, env, help) \ if ( var == -1 ) { \ var = getenv(#env) != NULL; \ if (PrintHelp) _objc_inform(#env ": " help); \ if (PrintOptions && var) _objc_inform(#env " is set"); \ } OPTION(PrintImages, OBJC_PRINT_IMAGES, "log image and library names as the runtime loads them"); OPTION(PrintConnecting, OBJC_PRINT_CONNECTION, "log progress of class and category connections"); OPTION(PrintLoading, OBJC_PRINT_LOAD_METHODS, "log class and category +load methods as they are called"); OPTION(PrintRTP, OBJC_PRINT_RTP, "log initialization of the Objective-C runtime pages"); OPTION(PrintGC, OBJC_PRINT_GC, "log some GC operations"); OPTION(PrintSharing, OBJC_PRINT_SHARING, "log cross-process memory sharing"); OPTION(PrintCxxCtors, OBJC_PRINT_CXX_CTORS, "log calls to C++ ctors and dtors for instance variables"); OPTION(DebugUnload, OBJC_DEBUG_UNLOAD, "warn about poorly-behaving bundles when unloaded"); OPTION(DebugFragileSuperclasses, OBJC_DEBUG_FRAGILE_SUPERCLASSES, "warn about subclasses that may have been broken by subsequent changes to superclasses"); OPTION(UseInternalZone, OBJC_USE_INTERNAL_ZONE, "allocate runtime data in a dedicated malloc zone"); OPTION(AllowInterposing, OBJC_ALLOW_INTERPOSING, "allow function interposing of objc_msgSend()"); OPTION(ForceGC, OBJC_FORCE_GC, "force GC ON, even if the executable wants it off"); OPTION(ForceNoGC, OBJC_FORCE_NO_GC, "force GC OFF, even if the executable wants it on"); OPTION(CheckFinalizers, OBJC_CHECK_FINALIZERS, "warn about classes that implement -dealloc but not -finalize"); #undef OPTION } /*********************************************************************** * objc_setMultithreaded. **********************************************************************/ void objc_setMultithreaded (BOOL flag) { // Nothing here. Thread synchronization in the runtime is always active. } /*********************************************************************** * _objc_pthread_destroyspecific * Destructor for objc's per-thread data. * arg shouldn't be NULL, but we check anyway. **********************************************************************/ extern void _destroyInitializingClassList(struct _objc_initializing_classes *list); void _objc_pthread_destroyspecific(void *arg) { _objc_pthread_data *data = (_objc_pthread_data *)arg; if (data != NULL) { _destroyInitializingClassList(data->initializingClasses); // add further cleanup here... _free_internal(data); } } /*********************************************************************** * _objcInit * Former library initializer. This function is now merely a placeholder * for external callers. All runtime initialization has now been moved * to map_images(). **********************************************************************/ void _objcInit(void) { // do nothing } /*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * All class registration and fixups are performed (or deferred pending * discovery of missing superclasses etc), and +load methods are called. * * info[] is in bottom-up order i.e. libobjc will be earlier in the * array than any library that links to libobjc. **********************************************************************/ static void map_images(const struct dyld_image_info infoList[], uint32_t infoCount) { static BOOL firstTime = YES; static BOOL wantsGC NOBSS = NO; uint32_t i; header_info *firstNewHeader = NULL; header_info *hInfo; // Perform first-time initialization if necessary. // This function is called before ordinary library initializers. if (firstTime) { pthread_key_create(&_objc_pthread_key, _objc_pthread_destroyspecific); objc_setConfiguration(); // read environment variables _objc_init_class_hash (); // create class_hash // grab selectors for which @selector() doesn't work cxx_construct_sel = sel_registerName(cxx_construct_name); cxx_destruct_sel = sel_registerName(cxx_destruct_name); } if (PrintImages) { _objc_inform("IMAGES: processing %u newly-mapped images...\n", infoCount); } // Find all images with an __OBJC segment. // firstNewHeader is set the the first one, and the header_info // linked list following firstNewHeader is the rest. for (i = 0; i < infoCount; i++) { const struct mach_header *mhdr = infoList[i].imageLoadAddress; hInfo = _objc_addHeader(mhdr); if (!hInfo) { // no objc data in this entry if (PrintImages) { _objc_inform("IMAGES: image '%s' contains no __OBJC segment\n", infoList[i].imageFilePath); } continue; } if (!firstNewHeader) firstNewHeader = hInfo; if (PrintImages) { _objc_inform("IMAGES: loading image for %s%s%s%s\n", _nameForHeader(mhdr), mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", _objcHeaderIsReplacement(hInfo) ? " (replacement)":"", _objcHeaderSupportsGC(hInfo) ? " (supports GC)":""); } } // Perform one-time runtime initialization that must be deferred until // the executable itself is found. This needs to be done before // further initialization. // (The executable may not be present in this infoList if the // executable does not contain Objective-C code but Objective-C // is dynamically loaded later. In that case, check_wants_gc() // will do the right thing.) if (firstTime) { wantsGC = check_wants_gc(); verify_gc_readiness(wantsGC, FirstHeader); // TIGER DEVELOPMENT ONLY // REQUIRE A SPECIAL NON-SHIPPING FILE TO ENABLE GC if (wantsGC) { // make sure that the special file is there before proceeding with GC struct stat ignored; wantsGC = stat("/autozone", &ignored) != -1; if (!wantsGC && PrintGC) _objc_inform("GC: disabled, lacking /autozone file"); } gc_init(wantsGC); // needs executable for GC decision rtp_init(); // needs GC decision first } else { verify_gc_readiness(wantsGC, firstNewHeader); } // Initialize everything. Parts of this order are important for // correctness or performance. // Read classes from all images. for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { _objc_read_classes_from_image(hInfo); } // Read categories from all images. for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { _objc_read_categories_from_image(hInfo); } // Connect classes from all images. for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { _objc_connect_classes_from_image(hInfo); } // Fix up class refs, selector refs, and protocol objects from all images. for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) { _objc_map_class_refs_for_image(hInfo); _objc_fixup_selector_refs(hInfo); _objc_fixup_protocol_objects_for_image(hInfo); } firstTime = NO; // Call pending +load methods. // Note that this may in turn cause map_images() to be called again. call_load_methods(); } /*********************************************************************** * unmap_images * Process the given images which are about to be unmapped by dyld. * Currently we assume only MH_BUNDLE images are unmappable, and * print warnings about anything else. **********************************************************************/ static void unmap_images(const struct dyld_image_info infoList[], uint32_t infoCount) { uint32_t i; if (PrintImages) { _objc_inform("IMAGES: processing %u newly-unmapped images...\n", infoCount); } for (i = 0; i < infoCount; i++) { const struct mach_header *mhdr = infoList[i].imageLoadAddress; if (mhdr->filetype == MH_BUNDLE) { _objc_unmap_image(mhdr); } else { // currently only MH_BUNDLEs can be unmapped safely if (PrintImages) { _objc_inform("IMAGES: unmapped image '%s' was not a Mach-O bundle; ignoring\n", infoList[i].imageFilePath); } } } } /*********************************************************************** * _objc_notify_images * Callback from dyld informing objc of images to be added or removed. * This function is never called directly. Instead, a section * __OBJC,__image_notify contains a function pointer to this, and dyld * discovers it from there. **********************************************************************/ __private_extern__ void _objc_notify_images(enum dyld_image_mode mode, uint32_t infoCount, const struct dyld_image_info infoList[]) { if (mode == dyld_image_adding) { map_images(infoList, infoCount); } else if (mode == dyld_image_removing) { unmap_images(infoList, infoCount); } } /*********************************************************************** * _objc_remove_classes_in_image * Remove all classes in the given image from the runtime, because * the image is about to be unloaded. * Things to clean up: * class_hash * unconnected_class_hash * pending subclasses list (only if class is still unconnected) * loadable class list * class's method caches * class refs in all other images **********************************************************************/ static void _objc_remove_classes_in_image(header_info *hi) { unsigned int index; unsigned int midx; Module mods; OBJC_LOCK(&classLock); // Major loop - process all modules in the image mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx += 1) { // Skip module containing no classes if (mods[midx].symtab == NULL) continue; // Minor loop - process all the classes in given module for (index = 0; index < mods[midx].symtab->cls_def_cnt; index += 1) { struct objc_class * cls; // Locate the class description pointer cls = mods[midx].symtab->defs[index]; // Remove from loadable class list, if present remove_class_from_loadable_list(cls); // Remove from unconnected_class_hash and pending subclasses if (unconnected_class_hash && NXHashMember(unconnected_class_hash, cls)) { NXHashRemove(unconnected_class_hash, cls); if (pendingSubclassesMap) { // Find this class in its superclass's pending list char *supercls_name = (char *)cls->super_class; PendingSubclass *pending = NXMapGet(pendingSubclassesMap, supercls_name); for ( ; pending != NULL; pending = pending->next) { if (pending->subclass == cls) { pending->subclass = Nil; break; } } } } // Remove from class_hash NXHashRemove(class_hash, cls); // Free method list array (from objcTweakMethodListPointerForClass) // These blocks might be from user code; don't use free_internal if (cls->methodLists && !(cls->info & CLS_NO_METHOD_ARRAY)) { free(cls->methodLists); } if (cls->isa->methodLists && !(cls->isa->info & CLS_NO_METHOD_ARRAY)) { free(cls->isa->methodLists); } // Free method caches, if any if (cls->cache && cls->cache != &emptyCache) { _free_internal(cls->cache); } if (cls->isa->cache && cls->isa->cache != &emptyCache) { _free_internal(cls->isa->cache); } } } // Search all other images for class refs that point back to this range. // Un-fix and re-pend any such class refs. // Get the location of the dying image's __OBJC segment uintptr_t seg = hi->objcSegmentHeader->vmaddr + hi->image_slide; size_t seg_size = hi->objcSegmentHeader->filesize; header_info *other_hi; for (other_hi = FirstHeader; other_hi != NULL; other_hi = other_hi->next) { struct objc_class **other_refs; unsigned int size; if (other_hi == hi) continue; // skip the image being unloaded // Locate class refs in the other image other_refs = _getObjcClassRefs((headerType *)other_hi->mhdr, &size); if (!other_refs) continue; other_refs = (struct objc_class **)((uintptr_t)other_refs + other_hi->image_slide); // Process each class ref for (index = 0; index < size; index++) { if ((uintptr_t)(other_refs[index]) >= seg && (uintptr_t)(other_refs[index]) < seg+seg_size) { pendClassReference(&other_refs[index],other_refs[index]->name); other_refs[index] = _objc_getNonexistentClass (); } } } OBJC_UNLOCK(&classLock); } /*********************************************************************** * _objc_remove_categories_in_image * Remove all categories in the given image from the runtime, because * the image is about to be unloaded. * Things to clean up: * unresolved category list * loadable category list **********************************************************************/ static void _objc_remove_categories_in_image(header_info *hi) { Module mods; unsigned int midx; // Major loop - process all modules in the header mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx++) { unsigned int index; unsigned int total; Symtab symtab = mods[midx].symtab; // Nothing to do for a module without a symbol table if (symtab == NULL) continue; // Total entries in symbol table (class entries followed // by category entries) total = symtab->cls_def_cnt + symtab->cat_def_cnt; // Minor loop - check all categories from given module for (index = symtab->cls_def_cnt; index < total; index++) { struct objc_category *cat = symtab->defs[index]; // Clean up loadable category list remove_category_from_loadable_list(cat); // Clean up category_hash if (category_hash) { _objc_unresolved_category *cat_entry = NXMapGet(category_hash, cat->class_name); for ( ; cat_entry != NULL; cat_entry = cat_entry->next) { if (cat_entry->cat == cat) { cat_entry->cat = NULL; break; } } } } } } /*********************************************************************** * unload_paranoia * Various paranoid debugging checks that look for poorly-behaving * unloadable bundles. * Called by _objc_unmap_image when OBJC_UNLOAD_DEBUG is set. **********************************************************************/ static void unload_paranoia(header_info *hi) { // Get the location of the dying image's __OBJC segment uintptr_t seg = hi->objcSegmentHeader->vmaddr + hi->image_slide; size_t seg_size = hi->objcSegmentHeader->filesize; _objc_inform("UNLOAD DEBUG: unloading image '%s' [%p..%p]", _nameForHeader(hi->mhdr), seg, seg+seg_size); OBJC_LOCK(&classLock); // Make sure the image contains no categories on surviving classes. { Module mods; unsigned int midx; // Major loop - process all modules in the header mods = hi->mod_ptr; for (midx = 0; midx < hi->mod_count; midx++) { unsigned int index; unsigned int total; Symtab symtab = mods[midx].symtab; // Nothing to do for a module without a symbol table if (symtab == NULL) continue; // Total entries in symbol table (class entries followed // by category entries) total = symtab->cls_def_cnt + symtab->cat_def_cnt; // Minor loop - check all categories from given module for (index = symtab->cls_def_cnt; index < total; index++) { struct objc_category *cat = symtab->defs[index]; struct objc_class query; query.name = cat->class_name; if (NXHashMember(class_hash, &query)) { _objc_inform("UNLOAD DEBUG: dying image contains category '%s(%s)' on surviving class '%s'!", cat->class_name, cat->category_name, cat->class_name); } } } } // Make sure no surviving class is in the dying image. // Make sure no surviving class has a superclass in the dying image. // fixme check method implementations too { struct objc_class *cls; NXHashState state; state = NXInitHashState(class_hash); while (NXNextHashState(class_hash, &state, (void **)&cls)) { if ((vm_address_t)cls >= seg && (vm_address_t)cls < seg+seg_size) { _objc_inform("UNLOAD DEBUG: dying image contains surviving class '%s'!", cls->name); } if ((vm_address_t)cls->super_class >= seg && (vm_address_t)cls->super_class < seg+seg_size) { _objc_inform("UNLOAD DEBUG: dying image contains superclass '%s' of surviving class '%s'!", cls->super_class->name, cls->name); } } } OBJC_UNLOCK(&classLock); } /*********************************************************************** * _objc_unmap_image. * Destroy any Objective-C data for the given image, which is about to * be unloaded by dyld. * Note: not thread-safe, but image loading isn't either. **********************************************************************/ static void _objc_unmap_image(const headerType *mh) { header_info *hi; // Find the runtime's header_info struct for the image for (hi = FirstHeader; hi != NULL; hi = hi->next) { if (hi->mhdr == mh) break; } if (hi == NULL) return; // no objc data for this image if (PrintImages) { _objc_inform("IMAGES: unloading image for %s%s%s%s\n", _nameForHeader(mh), mh->filetype == MH_BUNDLE ? " (bundle)" : "", _objcHeaderIsReplacement(hi) ? " (replacement)" : "", _objcHeaderSupportsGC(hi) ? " (supports GC)" : ""); } // Cleanup: // Remove image's classes from the class list and free auxiliary data. // Remove image's unresolved or loadable categories and free auxiliary data // Remove image's unresolved class refs. _objc_remove_classes_in_image(hi); _objc_remove_categories_in_image(hi); _objc_remove_pending_class_refs_in_image(hi); // Perform various debugging checks if requested. if (DebugUnload) unload_paranoia(hi); // Remove header_info from header list _objc_removeHeader(hi); } /*********************************************************************** * _objc_setNilReceiver **********************************************************************/ id _objc_setNilReceiver(id newNilReceiver) { id oldNilReceiver; oldNilReceiver = _objc_nilReceiver; _objc_nilReceiver = newNilReceiver; return oldNilReceiver; } /*********************************************************************** * _objc_getNilReceiver **********************************************************************/ id _objc_getNilReceiver(void) { return _objc_nilReceiver; } /*********************************************************************** * _objc_setClassLoader * Similar to objc_setClassHandler, but objc_classLoader is used for * both objc_getClass() and objc_lookupClass(), and objc_classLoader * pre-empts objc_classHandler. **********************************************************************/ void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) { _objc_classLoader = newClassLoader; } #if defined(__ppc__) /********************************************************************** * objc_write_branch * Writes at entry a PPC branch instruction sequence that branches to target. * The sequence written will be 1 or 4 instructions long. * Returns the number of instructions written. **********************************************************************/ __private_extern__ size_t objc_write_branch(void *entry, void *target) { unsigned *address = (unsigned *)entry; // location to store the 32 bit PPC instructions intptr_t destination = (intptr_t)target; // destination as an absolute address intptr_t displacement = (intptr_t)destination - (intptr_t)address; // destination as a branch relative offset // Test to see if either the displacement or destination is within the +/- 2^25 range needed // for a simple PPC branch instruction. Shifting the high bit of the displacement (or destination) // left 6 bits and then 6 bits arithmetically to the right does a sign extend of the 26th bit. If // that result is equivalent to the original value, then the displacement (or destination) will fit // into a simple branch. Otherwise a four instruction branch sequence is required. if (((displacement << 6) >> 6) == displacement) { // use a relative branch with the displacement address[0] = 0x48000000 | (displacement & 0x03fffffc); // b *+displacement // issued 1 instruction return 1; } else if (((destination << 6) >> 6) == destination) { // use an absolute branch with the destination address[0] = 0x48000000 | (destination & 0x03fffffc) | 2; // ba destination (2 is the absolute flag) // issued 1 instruction return 1; } else { // The four instruction branch sequence requires that the destination be loaded // into a register, moved to the CTR register then branch using the contents // of the CTR register. unsigned lo = destination & 0xffff; unsigned hi = (destination >> 16) & 0xffff; address[0] = 0x3d800000 | hi; // lis r12,hi ; load the hi half of destination address[1] = 0x618c0000 | lo; // ori r12,r12,lo ; merge in the lo half of destination address[2] = 0x7d8903a6; // mtctr ; move destination to the CTR register address[3] = 0x4e800420; // bctr ; branch to destination // issued 4 instructions return 4; } } // defined(__ppc__) #endif /********************************************************************** * secure_open * Securely open a file from a world-writable directory (like /tmp) * If the file does not exist, it will be atomically created with mode 0600 * If the file exists, it must be, and remain after opening: * 1. a regular file (in particular, not a symlink) * 2. owned by euid * 3. permissions 0600 * 4. link count == 1 * Returns a file descriptor or -1. Errno may or may not be set on error. **********************************************************************/ __private_extern__ int secure_open(const char *filename, int flags, uid_t euid) { struct stat fs, ls; int fd = -1; BOOL truncate = NO; BOOL create = NO; if (flags & O_TRUNC) { // Don't truncate the file until after it is open and verified. truncate = YES; flags &= ~O_TRUNC; } if (flags & O_CREAT) { // Don't create except when we're ready for it create = YES; flags &= ~O_CREAT; flags &= ~O_EXCL; } if (lstat(filename, &ls) < 0) { if (errno == ENOENT && create) { // No such file - create it fd = open(filename, flags | O_CREAT | O_EXCL, 0600); if (fd >= 0) { // File was created successfully. // New file does not need to be truncated. return fd; } else { // File creation failed. return -1; } } else { // lstat failed, or user doesn't want to create the file return -1; } } else { // lstat succeeded - verify attributes and open if (S_ISREG(ls.st_mode) && // regular file? ls.st_nlink == 1 && // link count == 1? ls.st_uid == euid && // owned by euid? (ls.st_mode & ALLPERMS) == (S_IRUSR | S_IWUSR)) // mode 0600? { // Attributes look ok - open it and check attributes again fd = open(filename, flags, 0000); if (fd >= 0) { // File is open - double-check attributes if (0 == fstat(fd, &fs) && fs.st_nlink == ls.st_nlink && // link count == 1? fs.st_uid == ls.st_uid && // owned by euid? fs.st_mode == ls.st_mode && // regular file, 0600? fs.st_ino == ls.st_ino && // same inode as before? fs.st_dev == ls.st_dev) // same device as before? { // File is open and OK if (truncate) ftruncate(fd, 0); return fd; } else { // Opened file looks funny - close it close(fd); return -1; } } else { // File didn't open return -1; } } else { // Unopened file looks funny - don't open it return -1; } } }