* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
- *
- * Portions Copyright (c) 1999 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 1.1 (the "License"). You may not use this file
- * except in compliance with the License. Please obtain a copy of the
- * License at http://www.apple.com/publicsource and read it before using
- * this file.
- *
+ *
+ * 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
+ * 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 OR NON- INFRINGEMENT. Please see the
- * License for the specific language governing rights and limitations
- * under the License.
- *
+ * 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@
*/
/***********************************************************************
*
**********************************************************************/
+/***********************************************************************
+ * 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 <mach-o/ldsyms.h>
#include <mach-o/dyld.h>
-#include <mach/vm_statistics.h>
+#include <mach-o/dyld_gdb.h>
+#include <mach/mach.h>
+#include <mach/mach_error.h>
// project headers first, otherwise we get the installed ones
#import "objc-class.h"
#import "objc-private.h"
#import <objc/Object.h>
#import <objc/Protocol.h>
+#import "objc-rtp.h"
+#import "objc-auto.h"
#include <sys/time.h>
#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/* NXHashTable SPI */
+OBJC_EXPORT unsigned _NXHashCapacity(NXHashTable *table);
+OBJC_EXPORT void _NXHashRehashToCapacity(NXHashTable *table, unsigned newCapacity);
+
OBJC_EXPORT Class _objc_getNonexistentClass(void);
typedef struct _objc_unresolved_category
{
struct _objc_unresolved_category * next;
- struct objc_category * cat;
+ struct objc_category * cat; // may be NULL
long version;
- int bindme;
} _objc_unresolved_category;
-typedef struct _PendingClass
+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;
- struct objc_class * classToSetUp;
- const char * nameof_superclass;
- int version;
- struct _PendingClass * next;
-} PendingClass;
+ 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 to call when message sent to nil object.
-void (*_objc_msgNil)(id, SEL) = NULL;
-
// Function called after class has been fixed up (MACH only)
void (*callbackFunction)(Class, const char *) = 0;
-// Prototype for function passed to
-typedef void (*NilObjectMsgCallback) (id nilObject, SEL selector);
-
// Lock for class hashtable
OBJC_DECLARE_LOCK (classLock);
-// Condition for logging load progress
-static int LaunchingDebug = -1;
+// 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
-pthread_key_t _objc_pthread_key;
+__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 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, int bindme);
-static void _objc_add_categories_from_image (header_info * hi);
+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 PendingClass * newPending (void);
static NXMapTable * pendingClassRefsMapTable (void);
-static void _objc_add_classes_from_image (NXHashTable * clsHash, header_info * hi);
-static void _objc_fixup_string_objects_for_image(header_info * hi);
+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 map_selrefs (SEL * sels, unsigned int cnt);
-static void map_method_descs (struct objc_method_description_list * methods);
static void _objc_fixup_protocol_objects_for_image (header_info * hi);
-static void _objc_bindModuleContainingCategory(Category cat);
static void _objc_fixup_selector_refs (const header_info * hi);
-static void _objc_call_loads_for_image (header_info * header);
-static void _objc_checkForPendingClassReferences (struct objc_class * cls);
-static void _objc_map_image(headerType *mh, unsigned long vmaddr_slide);
-static void _objc_unmap_image(headerType *mh, unsigned long vmaddr_slide);
+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 vm_range_t get_shared_range(vm_address_t start, vm_address_t end);
+static void offer_shared_range(vm_address_t start, vm_address_t end);
+static void install_shared_range(vm_range_t remote, vm_address_t local);
+static void clear_shared_range_file_cache(void);
+
/***********************************************************************
* 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 = 0;
+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 = 0;
+static NXHashTable * class_hash NOBSS = 0;
static NXHashTablePrototype classHashPrototype =
{
(unsigned (*) (const void *, const void *)) classHash,
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;
-
-static int Postpone_install_relationships = 0;
-
-static NXMapTable * pendingClassRefsMap = 0;
+// 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.
struct objc_class * cls)
{
// Standard string comparison
- return ((name->name[0] == cls->name[0]) &&
- (strcmp (name->name, cls->name) == 0));
+ // 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.
if (class_hash)
return;
- // Provide a generous initial capacity to cut down on rehashes
- // at launch time. A smallish Foundation+AppKit program will have
+ // 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,
- 1024,
+ 16,
nil,
- _objc_create_zone ());
+ _objc_internal_zone ());
_objc_debug_class_hash = class_hash;
}
}
cnt = 0;
state = NXInitHashState(class_hash);
- while (cnt < num && NXNextHashState(class_hash, &state, (void **)&class)) {
+ while (cnt < bufferLen &&
+ NXNextHashState(class_hash, &state, (void **)&class))
+ {
buffer[cnt++] = class;
}
OBJC_UNLOCK(&classLock);
objc_classHandler = userSuppliedHandler;
}
+
/***********************************************************************
-* objc_getClass. Return the id of the named class. If the class does
-* not exist, call the objc_classHandler routine with the class name.
-* If the objc_classHandler returns a non-zero value, try once more to
-* find the class. Default objc_classHandler always returns zero.
-* objc_setClassHandler is how someone can install a non-default routine.
-* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
+* 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)
**********************************************************************/
-id objc_getClass (const char * aClassName)
+static id look_up_class(const char *aClassName, BOOL includeUnconnected, BOOL includeClassHandler)
{
- struct objc_class cls;
- id ret;
+ BOOL includeClassLoader = YES; // class loader cannot be skipped
+ id result = nil;
+ struct objc_class query;
- // Synchronize access to hash table
- OBJC_LOCK (&classLock);
+ query.name = aClassName;
- // Check the hash table
- cls.name = aClassName;
- ret = (id) NXHashGet (class_hash, &cls);
- OBJC_UNLOCK (&classLock);
+ retry:
- // If not found, go call objc_classHandler and try again
- if (!ret && (*objc_classHandler)(aClassName))
- {
+ if (!result && class_hash) {
+ // Check ordinary classes
OBJC_LOCK (&classLock);
- ret = (id) NXHashGet (class_hash, &cls);
+ result = (id)NXHashGet(class_hash, &query);
OBJC_UNLOCK (&classLock);
}
- return ret;
+ 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)
{
- struct objc_class cls;
- id ret;
-
- // Synchronize access to hash table
- OBJC_LOCK (&classLock);
-
- // Check the hash table
- cls.name = aClassName;
- ret = (id) NXHashGet (class_hash, &cls);
-
- // Desynchronize
- OBJC_UNLOCK (&classLock);
- return ret;
+ // NO unconnected, NO class handler
+ return look_up_class(aClassName, NO, NO);
}
/***********************************************************************
* objc_addClass. Add the specified class to the table of known classes,
* after doing a little verification and fixup.
**********************************************************************/
-void objc_addClass (Class cls)
+void objc_addClass (struct objc_class *cls)
{
// Synchronize access to hash table
OBJC_LOCK (&classLock);
// 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 (((struct objc_class *)cls)->cache == NULL)
- {
- ((struct objc_class *)cls)->cache = (Cache) &emptyCache;
- ((struct objc_class *)cls)->info = CLS_CLASS;
+ if (cls->cache == NULL) {
+ cls->cache = (Cache) &emptyCache;
+ cls->info = CLS_CLASS;
}
- if (((struct objc_class *)cls)->isa->cache == NULL)
- {
- ((struct objc_class *)cls)->isa->cache = (Cache) &emptyCache;
- ((struct objc_class *)cls)->isa->info = CLS_META;
+ 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);
/***********************************************************************
* _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)
{
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 **) malloc_zone_calloc (_objc_create_zone (), 1, mallocSize);
+ 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
- ((struct objc_class *)cls)->methodLists = ptr;
- ((struct objc_class *)cls)->info |= CLS_METHOD_ARRAY;
-
- // Do the same thing to the meta-class
- if (((((struct objc_class *)cls)->info & CLS_CLASS) != 0) && cls->isa)
- _objcTweakMethodListPointerForClass (cls->isa);
+ 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_method_list * mlist,
- struct objc_method_list *** list)
+void _objc_insertMethods(struct objc_class *cls,
+ struct objc_method_list *mlist)
{
- struct objc_method_list ** ptr;
- volatile struct objc_method_list ** tempList;
- int endIndex;
- int oldSize;
- int newSize;
+ 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, double it
+ // If array is full, add to it
if (*ptr == END_OF_METHODS_LIST)
{
// Calculate old and new dimensions
oldSize = (endIndex + 1) * sizeof(void *);
newSize = oldSize + sizeof(struct objc_method_list *); // only increase by 1
- // Replace existing array with copy twice its size
- tempList = (struct objc_method_list **) malloc_zone_realloc ((void *) _objc_create_zone (),
- (void *) *list,
- (size_t) newSize);
- *list = tempList;
+ // 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);
/***********************************************************************
* _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_method_list * mlist,
- struct objc_method_list *** list)
+void _objc_removeMethods(struct objc_class *cls,
+ struct objc_method_list *mlist)
{
- struct objc_method_list ** ptr;
+ 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;
/***********************************************************************
* _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 (category->instance_methods, &cls->methodLists);
+ _objc_insertMethods (cls, category->instance_methods);
// Augment class methods
if (category->class_methods)
- _objc_insertMethods (category->class_methods, &cls->isa->methodLists);
+ _objc_insertMethods (cls->isa, category->class_methods);
// Augment protocols
if ((version >= 5) && category->protocols)
}
/***********************************************************************
-* _objc_add_category_flush_caches. Install the specified category's methods into
-* the class it augments, and flush the class' method cache.
-*
+* _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;
+}
+
+
/***********************************************************************
-* _objc_resolve_categories_for_class. Install all categories intended
-* for the specified class, in reverse order from the order in which we
-* found the categories in the image.
-* This is done as lazily as we can.
+* resolve_categories_for_class.
+* Install all existing categories intended for the specified class.
+* cls must be a true class and not a metaclass.
**********************************************************************/
-void _objc_resolve_categories_for_class (struct objc_class * cls)
+static void resolve_categories_for_class(struct objc_class *cls)
{
- _objc_unresolved_category * cat;
+ _objc_unresolved_category * pending;
_objc_unresolved_category * next;
// Nothing to do if there are no categories at all
- if (!category_hash)
- return;
+ if (!category_hash) return;
// Locate and remove first element in category list
// associated with this class
- cat = NXMapRemove (category_hash, cls->name);
+ pending = NXMapKeyFreeingRemove (category_hash, cls->name);
// Traverse the list of categories, if any, registered for this class
- while (cat)
- {
- if (cat->bindme) {
- _objc_bindModuleContainingCategory(cat->cat);
+
+ // 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);
}
- // Install the category
- // use the non-flush-cache version since we are only
- // called from the class intialization code
- _objc_add_category (cls, cat->cat, cat->version);
// Delink and reclaim this registration
- next = cat->next;
- free (cat);
- cat = next;
+ 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. Add the specified category to the registry
-* of categories to be installed later (once we know for sure which
- * classes we have). If there are multiple categories on a given class,
-* they will be processed in reverse order from the order in which they
-* were found in the image.
+* _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,
- int bindme)
+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, just add the category
- // We could check to see if its initted, and if not, defer this
- // work until _objc_resolve_categories_for_class for all cases
- // The only trick then is whether we need to bind it. This
- // might be doable if we store an obscured pointer so that we
- // avoid touching the memory... [BG 5/2001 still in think mode]
- if (theClass = objc_lookUpClass (cat->class_name))
- {
- if (bindme) {
- _objc_bindModuleContainingCategory(cat);
- }
- _objc_add_category_flush_caches (theClass, cat, version);
+ // 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_create_zone ());
+ _objc_internal_zone ());
- // Locate an existing category, if any, for the class. This is linked
- // after the new entry, so list is LIFO.
+ // 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
- new_cat = malloc_zone_malloc (_objc_create_zone (),
- sizeof(_objc_unresolved_category));
+ // 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;
- new_cat->bindme = bindme; // could use a bit in the next pointer instead of a whole word
- (void) NXMapInsert (category_hash, cat->class_name , new_cat);
+ (void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat);
}
+
/***********************************************************************
-* _objc_add_categories_from_image.
+* _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_add_categories_from_image (header_info * hi)
+static void _objc_read_categories_from_image (header_info * hi)
{
Module mods;
unsigned int midx;
- int isDynamic = (hi->mhdr->filetype == MH_DYLIB) || (hi->mhdr->filetype == MH_BUNDLE);
- // Major loop - process all modules in the header
- mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide);
+ if (_objcHeaderIsReplacement(hi)) {
+ // Ignore any categories in this image
+ return;
+ }
- trace(0xb120, hi->mod_count, 0, 0);
+ // Major loop - process all modules in the header
+ mods = hi->mod_ptr;
- for (midx = 0; midx < hi->mod_count; midx += 1)
- {
+ // 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;
-
-
- trace(0xb123, midx, mods[midx].symtab->cat_def_cnt, 0);
-
+
// Minor loop - register all categories from given module
- for (index = mods[midx].symtab->cls_def_cnt; index < total; index += 1)
- {
- _objc_register_category(mods[midx].symtab->defs[index], mods[midx].version, isDynamic);
+ 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);
}
-
- trace(0xb124, midx, 0, 0);
}
-
- trace(0xb12f, 0, 0, 0);
}
+
/***********************************************************************
-* _headerForClass.
+* _headerForAddress.
+* addr can be a class or a category
**********************************************************************/
-static const header_info * _headerForClass (struct objc_class * cls)
+static const header_info *_headerForAddress(void *addr)
{
- const struct segment_command * objcSeg;
- unsigned int size;
- unsigned long vmaddrPlus;
+ 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
- objcSeg = _getObjcHeaderData ((headerType *) hInfo->mhdr, &size);
- if (!objcSeg)
- continue;
+ if (!hInfo->objcSegmentHeader) continue;
+ seg = hInfo->objcSegmentHeader->vmaddr + hInfo->image_slide;
+ size = hInfo->objcSegmentHeader->filesize;
// Is the class in this header?
- vmaddrPlus = (unsigned long) objcSeg->vmaddr + hInfo->image_slide;
- if ((vmaddrPlus <= (unsigned long) cls) &&
- ((unsigned long) cls < (vmaddrPlus + size)))
+ if ((seg <= (unsigned long) addr) &&
+ ((unsigned long) addr < (seg + size)))
return hInfo;
}
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.
**********************************************************************/
return _getObjcHeaderName ((headerType *) header);
}
+
/***********************************************************************
-* checkForPendingClassReferences. Complete any fixups registered for
-* this class.
+* 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 void _objc_checkForPendingClassReferences (struct objc_class * cls)
+static BOOL class_is_connected(struct objc_class *cls)
{
- PendingClass * pending;
-
- // Nothing to do if there are no pending classes
- if (!pendingClassRefsMap)
- return;
-
- // Get pending list for this class
- pending = NXMapGet (pendingClassRefsMap, cls->name);
- if (!pending)
- return;
-
- // Remove the list from the table
- (void) NXMapRemove (pendingClassRefsMap, cls->name);
-
- // Process all elements in the list
- while (pending)
- {
- PendingClass * next;
-
- // Remember follower for loop
- next = pending->next;
-
- // Fill in a pointer to Class
- // (satisfies caller of objc_pendClassReference)
- if (pending->ref)
- *pending->ref = objc_getClass (cls->name);
-
- // Fill in super, isa, cache, and version for the class
- // and its meta-class
- // (satisfies caller of objc_pendClassInstallation)
- // NOTE: There must be no more than one of these for
- // any given classToSetUp
- if (pending->classToSetUp)
- {
- struct objc_class * fixCls;
-
- // Locate the Class to be fixed up
- fixCls = pending->classToSetUp;
-
- // Set up super class fields with names to be replaced by pointers
- fixCls->super_class = (struct objc_class *) pending->nameof_superclass;
- fixCls->isa->super_class = (struct objc_class *) pending->nameof_superclass;
-
- // Fix up class pointers, version, and cache pointers
- _class_install_relationships (fixCls, pending->version);
- }
+ BOOL result;
+ OBJC_LOCK(&classLock);
+ result = NXHashMember(class_hash, cls);
+ OBJC_UNLOCK(&classLock);
+ return result;
+}
- // Reclaim the element
- free (pending);
- // Move on
- pending = next;
+/***********************************************************************
+* 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;
}
+
/***********************************************************************
-* newPending. Allocate and zero a PendingClass structure.
+* pendingSubclassesMapTable. Return a pointer to the lookup table for
+* pending subclasses.
**********************************************************************/
-static inline PendingClass * newPending (void)
+static inline NXMapTable *pendingSubclassesMapTable(void)
{
- PendingClass * pending;
-
- pending = (PendingClass *) malloc_zone_calloc (_objc_create_zone (), 1, sizeof(PendingClass));
-
- return pending;
+ // Allocate table if needed
+ if (!pendingSubclassesMap) {
+ pendingSubclassesMap =
+ NXCreateMapTableFromZone(NXStrValueMapPrototype,
+ 10, _objc_internal_zone ());
+ }
+
+ // Return table pointer
+ return pendingSubclassesMap;
}
+
/***********************************************************************
-* pendingClassRefsMapTable. Return a pointer to the lookup table for
-* pending classes.
+* pendClassInstallation
+* Finish connecting class cls when its superclass becomes connected.
+* Check for multiple pends of the same class because connect_class does not.
**********************************************************************/
-static inline NXMapTable * pendingClassRefsMapTable (void)
+static void pendClassInstallation(struct objc_class *cls,
+ const char *superName)
{
- // Allocate table if needed
- if (!pendingClassRefsMap)
- pendingClassRefsMap = NXCreateMapTableFromZone (NXStrValueMapPrototype, 10, _objc_create_zone ());
+ NXMapTable *table;
+ PendingSubclass *pending;
+ PendingSubclass *oldList;
+ PendingSubclass *l;
+
+ // Create and/or locate pending class lookup table
+ table = pendingSubclassesMapTable ();
- // Return table pointer
- return pendingClassRefsMap;
+ // 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);
}
+
/***********************************************************************
-* objc_pendClassReference. Register the specified class pointer (ref)
-* to be filled in later with a pointer to the class having the specified
-* name.
+* pendClassReference
+* Fix up a class ref when the class with the given name becomes connected.
**********************************************************************/
-void objc_pendClassReference (const char * className,
- struct objc_class * * ref)
+static void pendClassReference(struct objc_class **ref,
+ const char *className)
{
- NXMapTable * table;
- PendingClass * pending;
-
+ NXMapTable *table;
+ PendingClassRef *pending;
+
// Create and/or locate pending class lookup table
table = pendingClassRefsMapTable ();
-
+
// Create entry containing the class reference
- pending = newPending ();
+ pending = _malloc_internal(sizeof(PendingClassRef));
pending->ref = ref;
-
+
// Link new entry into head of list of entries for this class
- pending->next = NXMapGet (pendingClassRefsMap, className);
-
+ pending->next = NXMapGet (table, className);
+
// (Re)place entry list in the table
- (void) NXMapInsert (table, className, pending);
+ (void) NXMapKeyCopyingInsert (table, className, pending);
+
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: pended reference to class '%s' at %p",
+ className, (void *)ref);
+ }
}
+
/***********************************************************************
-* objc_pendClassInstallation. Register the specified class to have its
-* super class pointers filled in later because the superclass is not
-* yet found.
+* resolve_references_to_class
+* Fix up any pending class refs to this class.
**********************************************************************/
-void objc_pendClassInstallation (struct objc_class *cls, int version)
+static void resolve_references_to_class(struct objc_class *cls)
{
- NXMapTable * table;
- PendingClass * pending;
+ PendingClassRef *pending;
+
+ if (!pendingClassRefsMap) return; // no unresolved refs for any class
- // Create and/or locate pending class lookup table
- table = pendingClassRefsMapTable ();
+ pending = NXMapGet(pendingClassRefsMap, cls->name);
+ if (!pending) return; // no unresolved refs for this class
- // Create entry referring to this class
- pending = newPending ();
- pending->classToSetUp = cls;
- pending->nameof_superclass = (const char *) cls->super_class;
- pending->version = version;
+ NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name);
- // Link new entry into head of list of entries for this class
- pending->next = NXMapGet (pendingClassRefsMap, cls->super_class);
+ if (PrintConnecting) {
+ _objc_inform("CONNECT: resolving references to class '%s'", cls->name);
+ }
- // (Re)place entry list in the table
- (void) NXMapInsert (table, cls->super_class, pending);
+ 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;
+ }
}
+
/***********************************************************************
-* _objc_add_classes_from_image. Install all classes contained in the
-* specified image.
+* resolve_subclasses_of_class
+* Fix up any pending subclasses of this class.
**********************************************************************/
-static void _objc_add_classes_from_image(NXHashTable *clsHash, header_info *hi)
+static void resolve_subclasses_of_class(struct objc_class *cls)
{
- unsigned int index;
- unsigned int midx;
- Module mods;
- int isDynamic = (hi->mhdr->filetype == MH_DYLIB) || (hi->mhdr->filetype == MH_BUNDLE);
+ PendingSubclass *pending;
+
+ if (!pendingSubclassesMap) return; // no unresolved subclasses
- // Major loop - process all modules in the image
- mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide);
- for (midx = 0; midx < hi->mod_count; midx += 1)
- {
+ 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 * oldCls;
struct objc_class * newCls;
+ struct objc_method_list *mlist;
// Locate the class description pointer
newCls = mods[midx].symtab->defs[index];
- // remember to bind the module on initialization
- if (isDynamic)
- newCls->info |= CLS_NEED_BIND ;
-
- // Convert old style method list to the new style
- _objcTweakMethodListPointerForClass (newCls);
+ // 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;
- oldCls = NXHashInsert (clsHash, newCls);
-
- // Non-Nil oldCls is a class that NXHashInsert just
- // bumped from table because it has the same name
- // as newCls
- if (oldCls)
- {
- const header_info * oldHeader;
- const header_info * newHeader;
- const char * oldName;
- const char * newName;
-
- // Log the duplication
- oldHeader = _headerForClass (oldCls);
- newHeader = _headerForClass (newCls);
- oldName = _nameForHeader (oldHeader->mhdr);
- 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);
-
- // Use the chosen class
- // NOTE: Isn't this a NOP?
- newCls = objc_lookUpClass (oldCls->name);
- }
+ // 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;
- // Unless newCls was a duplicate, and we chose the
- // existing one instead, set the version in the meta-class
- if (newCls != oldCls)
- newCls->isa->version = mods[midx].version;
+ // Set metaclass version
+ newCls->isa->version = mods[midx].version;
- // Install new categories intended for this class
- // NOTE: But, if we displaced an existing "isEqual"
- // class, the categories have already been installed
- // on an old class and are gone from the registry!!
+ // methodLists is NULL or a single list, not an array
+ newCls->info |= CLS_NO_METHOD_ARRAY;
+ newCls->isa->info |= CLS_NO_METHOD_ARRAY;
- // we defer this work until the class is initialized.
- //_objc_resolve_categories_for_class (newCls);
+ // 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);
- // Resolve (a) pointers to the named class, and/or
- // (b) the super_class, cache, and version
- // fields of newCls and its meta-class
- // NOTE: But, if we displaced an existing "isEqual"
- // class, this has already been done... with an
- // old-now-"unused" class!!
- _objc_checkForPendingClassReferences (newCls);
+ // 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_fixup_string_objects_for_image. Initialize the isa pointers
-* of all NSConstantString objects.
+* _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_fixup_string_objects_for_image (header_info * hi)
+static void _objc_connect_classes_from_image(header_info *hi)
{
- unsigned int size;
- OBJC_CONSTANT_STRING_PTR section;
- struct objc_class * constantStringClass;
- unsigned int index;
-
- // Locate section holding string objects
- section = _getObjcStringObjects ((headerType *) hi->mhdr, &size);
- if (!section || !size)
- return;
- section = (OBJC_CONSTANT_STRING_PTR) ((unsigned long) section + hi->image_slide);
-
- // Luckily NXConstantString is the same size as NSConstantString
- constantStringClass = objc_getClass ("NSConstantString");
+ unsigned int index;
+ unsigned int midx;
+ Module mods;
+ BOOL replacement = _objcHeaderIsReplacement(hi);
- // Process each string object in the section
- for (index = 0; index < size; index += 1)
+ // Major loop - process all modules in the image
+ mods = hi->mod_ptr;
+ for (midx = 0; midx < hi->mod_count; midx += 1)
{
- struct objc_class * * isaptr;
+ // Skip module containing no classes
+ if (mods[midx].symtab == NULL)
+ continue;
- isaptr = (struct objc_class * *) OBJC_CONSTANT_STRING_DEREF section[index];
- if (*isaptr == 0)
- *isaptr = constantStringClass;
+ // 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
ref = (const char *) cls_refs[index];
// Get pointer to class of this name
- cls = (struct objc_class *)objc_lookUpClass (ref);
-
- // If class isn't there yet, use pending mechanism
- if (!cls)
- {
- // Register this ref to be set later
- objc_pendClassReference (ref, &cls_refs[index]);
-
- // Use place-holder class
+ // 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 ();
}
-
- // Replace name string pointer with class pointer
- else
- cls_refs[index] = cls;
}
}
+
+/***********************************************************************
+* _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 void map_selrefs(SEL *sels, unsigned int cnt)
+static inline BOOL map_selrefs(SEL *src, SEL *dst, size_t size, BOOL copy)
{
- unsigned int index;
+ 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;
+ SEL sel;
// Lookup pointer to uniqued string
- sel = sel_registerNameNoCopy ((const char *) sels[index]);
+ 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 (sels[index] != sel)
- sels[index] = sel;
+ 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)
+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)
{
method = &methods->list[index];
// Lookup pointer to uniqued string
- sel = sel_registerNameNoCopy ((const char *) method->name);
+ 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();
}
/***********************************************************************
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);
{
// Selectorize the instance methods
if (protos[index] OBJC_PROTOCOL_DEREF instance_methods)
- map_method_descs (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);
+ map_method_descs (protos[index] OBJC_PROTOCOL_DEREF class_methods, isBundle);
}
// Invoke Protocol class method to fix up the protocol
*/
}
-/**********************************************************************
-* _objc_bind_symbol. Bind the module containing the symbol. Use 2-level namespace API
-* Only look in images that we know to have ObjC symbols (e.g. 9 for Mail 7/2001)
-* Radar 2701686
-***********************************************************************/
-static void _objc_bind_symbol(const char *name)
+
+/***********************************************************************
+* _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)
{
- static header_info *lastHeader = NULL;
- header_info *hInfo;
- const headerType *imageHeader = lastHeader ? lastHeader->mhdr : NULL;
+ 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;
- // Ideally we would have a way to not even process a symbol in a module
- // we've already visited
+ // 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);
- // First assume there is some locality and search where we last found a symbol
- if ( imageHeader
- && NSIsSymbolNameDefinedInImage(imageHeader, name)
- && NSLookupSymbolInImage(imageHeader, name, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND) != NULL )
- {
- // Found
- return;
+ // 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));
}
- // Symbol wasn't found in the image we last searched
- // Search in all the images known to contain ObjcC
- for ( hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next)
- {
- imageHeader = hInfo->mhdr;
- if ( hInfo != lastHeader
- && NSIsSymbolNameDefinedInImage(imageHeader, name)
- && NSLookupSymbolInImage(imageHeader, name, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND) != NULL )
- {
- // found
- lastHeader = hInfo;
- return;
- }
+ // 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;
}
- // die now, or later ??
- // _objc_fatal("could not find %s", name);
-}
-/***********************************************************************
-* _objc_bindModuleContainingCategory. Bind the module containing the
-* category.
-**********************************************************************/
-static void _objc_bindModuleContainingCategory (Category cat)
-{
- char * class_name;
- char * category_name;
- char * name;
- char tmp_buf[128];
- unsigned int name_len;
+ // 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);
+ }
+ }
- // Bind ".objc_category_name_<classname>_<categoryname>",
- // where <classname> is the class name with the leading
- // '%'s stripped.
- class_name = cat->class_name;
- category_name = cat->category_name;
- name_len = strlen(class_name) + strlen(category_name) + 30;
- if ( name_len > 128 )
- name = malloc(name_len);
- else
- name = tmp_buf;
- while (*class_name == '%')
- class_name += 1;
- strcpy (name, ".objc_category_name_");
- strcat (name, class_name);
- strcat (name, "_");
- strcat (name, category_name);
- if (LaunchingDebug) { _objc_syslog("_objc_bindModuleContainingCategory for %s on %s", category_name, class_name); }
- _objc_bind_symbol(name);
- if ( name != tmp_buf )
- free(name);
+ // 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_bindModuleContainingClass. Bind the module containing the
-* class.
-* This is done lazily, just after initializing the class (if needed)
+* _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
+ }
-void _objc_bindModuleContainingClass (struct objc_class * cls) {
- char * name;
- const char * class_name;
- char tmp_buf[128];
- unsigned int name_len;
-
- // Use the real class behind the poser
- if (CLS_GETINFO (cls, CLS_POSING))
- cls = getOriginalClassForPosingClass (cls);
- class_name = cls->name;
-
- name_len = strlen(class_name) + 20;
- if ( name_len > 128 )
- name = malloc(name_len);
- else
- name = tmp_buf;
+ // Free the memory, unless it was in the static HeaderInfoTable.
+ if (deadHead < HeaderInfoTable ||
+ deadHead >= HeaderInfoTable + HINFO_SIZE)
+ {
+ _free_internal(deadHead);
+ }
- while (*class_name == '%')
- class_name += 1;
- strcpy (name, ".objc_class_name_");
- strcat (name, class_name);
- if (LaunchingDebug) { _objc_syslog("_objc_bindModuleContainingClass for %s", class_name); }
- _objc_bind_symbol(name);
- if ( name != tmp_buf )
- free(name);
+ break;
+ }
+ }
}
/***********************************************************************
-* _objc_bindClassIfNeeded.
-* If the given class is still marked as needs-bind, bind the module
-* containing it.
-* Called during _objc_call_loads_for_image just before sending +load,
-* and during class_initialize just before sending +initialize.
+* 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.
**********************************************************************/
-void _objc_bindClassIfNeeded(struct objc_class *cls)
+static BOOL check_wants_gc(void)
{
- // Clear NEED_BIND *after* binding to prevent race
- // This assumes that simultaneous binding of one module by two threads is ok.
- if (cls->info & CLS_NEED_BIND) {
- _objc_bindModuleContainingClass(cls);
- cls->info &= ~CLS_NEED_BIND;
+ // 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;
+ */
}
-
/***********************************************************************
-* _objc_addHeader.
-*
-**********************************************************************/
+* verify_gc_readiness
+* if we want gc, verify that every header describes files compiled
+* and presumably ready for gc.
+************************************************************************/
-// tested with 2; typical case is 4, but OmniWeb & Mail push it towards 20
-#define HINFO_SIZE 16
-
-static int HeaderInfoCounter = 0;
-static header_info HeaderInfoTable[HINFO_SIZE] = { {0} };
-
-static header_info * _objc_addHeader(const headerType *header, unsigned long vmaddr_slide)
+static void verify_gc_readiness(BOOL wantsGC, header_info *hi)
{
- int mod_count;
- Module mod_ptr = _getObjcModules ((headerType *) header, &mod_count);
- header_info *result;
-
- // if there is no objc data - ignore this entry!
- if (mod_ptr == NULL) {
- return NULL;
- }
+ BOOL busted = NO;
- if (HeaderInfoCounter < HINFO_SIZE) {
- // avoid mallocs for the common case
- result = &HeaderInfoTable[HeaderInfoCounter++];
- }
- else {
- result = malloc_zone_malloc(_objc_create_zone(), sizeof(header_info));
+ // 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");
+ }
}
-
- // Set up the new vector entry
- result->mhdr = header;
- result->mod_ptr = mod_ptr;
- result->mod_count = mod_count;
- result->image_slide = vmaddr_slide;
-
- // chain it on
- // (a simple lock here would go a long way towards thread safety)
- result->next = FirstHeader;
- FirstHeader = result;
- return result;
-}
-
-/**********************************************************************
-* _objc_fatalHeader
-*
-* If we have it we're in trouble
-**************************************************************************/
-static void _objc_fatalHeader(const headerType *header)
-{
- header_info *hInfo;
-
- for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next) {
- if (hInfo->mhdr == header) {
- _objc_fatal("cannot unmap an image containing ObjC data");
+ 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.
-*
+*
+* If the image is a dylib (not a bundle or an executable), and contains
+* at least one full aligned page of selector refs, this function uses
+* the shared range functions to try to recycle already-written memory
+* from other processes.
**********************************************************************/
static void _objc_fixup_selector_refs (const header_info * hi)
{
- unsigned int size;
- Module mods;
- SEL * messages_refs;
+ unsigned int count;
+ Module mods;
+ vm_address_t local_sels;
+ vm_size_t local_size;
- mods = (Module) ((unsigned long) hi->mod_ptr + hi->image_slide);
+ mods = hi->mod_ptr;
// Fix up message refs
- messages_refs = (SEL *) _getObjcMessageRefs ((headerType *) hi->mhdr, &size);
- if (messages_refs)
- {
- messages_refs = (SEL *) ((unsigned long) messages_refs + hi->image_slide);
- map_selrefs (messages_refs, size);
- }
-}
-
+ 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);
+
+ if (aligned_start >= aligned_end ||
+ hi->mhdr->filetype == MH_BUNDLE ||
+ hi->mhdr->filetype == MH_EXECUTE)
+ {
+ // Less than a page of sels, OR bundle or executable - fix in place
-/***********************************************************************
-* _objc_call_loads_for_image.
-**********************************************************************/
-static void _objc_call_loads_for_image (header_info * header)
-{
- struct objc_class * cls;
- struct objc_class * * pClass;
- Category * pCategory;
- IMP load_method;
- unsigned int nModules;
- unsigned int nClasses;
- unsigned int nCategories;
- struct objc_symtab * symtab;
- struct objc_module * module;
+ map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size,
+ hi->mhdr->filetype == MH_BUNDLE);
- // Major loop - process all modules named in header
- module = (struct objc_module *) ((unsigned long) header->mod_ptr + header->image_slide);
- for (nModules = header->mod_count; nModules; nModules -= 1, module += 1)
- {
- symtab = module->symtab;
- if (symtab == NULL)
- continue;
+ if (PrintSharing) {
+ _objc_inform("SHARING: NONE [%p..%p) (%d pages) for %s",
+ local_sels, local_sels+local_size,
+ (aligned_end > aligned_start ?
+ (aligned_end-aligned_start) / vm_page_size : 0),
+ _nameForHeader(hi->mhdr));
+ }
+ }
+ else {
+ // At least one page of sels - try to use sharing
+ vm_range_t remote_range;
+
+ if (PrintSharing) {
+ _objc_inform("SHARING: looking for range [%p..%p) ...",
+ aligned_start, aligned_end);
+ }
- // Minor loop - call the +load from each class in the given module
- for (nClasses = symtab->cls_def_cnt, pClass = (Class *) symtab->defs;
- nClasses;
- nClasses -= 1, pClass += 1)
- {
- struct objc_method_list **mlistp;
- cls = (struct objc_class *)*pClass;
- mlistp = get_base_method_list(cls->isa);
- if (cls->isa->methodLists && mlistp)
- {
- // Look up the method manually (vs messaging the class) to bypass
- // +initialize and cache fill on class that is not even loaded yet
- load_method = class_lookupNamedMethodInMethodList (*mlistp, "load");
- if (load_method) {
- _objc_bindClassIfNeeded(cls);
- (*load_method) ((id) cls, @selector(load));
+ remote_range = get_shared_range(aligned_start, aligned_end);
+
+ if (remote_range.address != 0) {
+ // Sharing succeeded - fix using remote_range
+ BOOL stomped;
+
+ // local_sels..aligned_start (unshared)
+ map_selrefs((SEL *)local_sels, (SEL *)local_sels,
+ aligned_start - local_sels, NO);
+ // aligned_start..aligned_end (shared)
+ stomped =
+ map_selrefs((SEL *)aligned_start, (SEL *)remote_range.address,
+ aligned_end - aligned_start, NO);
+ // aligned_end..local_sels+local_size (unshared)
+ map_selrefs((SEL *)aligned_end, (SEL *)aligned_end,
+ local_sels+local_size - aligned_end, NO);
+
+ install_shared_range(remote_range, aligned_start);
+
+ if (PrintSharing) {
+ _objc_inform("SHARING: %s [%p..%p) (%d pages) for %s",
+ stomped ? "TRIED" : "USING",
+ local_sels, local_sels+local_size,
+ (aligned_end-aligned_start) / vm_page_size,
+ _nameForHeader(hi->mhdr));
}
- }
- }
+ }
+ else {
+ // Sharing failed, including first process -
+ // fix in place and then offer to share
- // Minor loop - call the +load from augmented class of
- // each category in the given module
- for (nCategories = symtab->cat_def_cnt,
- pCategory = (Category *) &symtab->defs[symtab->cls_def_cnt];
- nCategories;
- nCategories -= 1, pCategory += 1)
- {
- struct objc_method_list * methods;
+ map_selrefs((SEL *)local_sels, (SEL *)local_sels, local_size, NO);
- methods = (*pCategory)->class_methods;
- if (methods)
- {
- load_method = class_lookupNamedMethodInMethodList (methods, "load");
- if (load_method) {
- // Strictly speaking we shouldn't need (and don't want) to get the class here
- // The side effect we're looking for is to load it if needed.
- // Since category +loads are rare we could spend some cycles finding out
- // if we have a "bindme" TBD and do it here, saving a class load.
- // But chances are the +load will cause class initialization anyway
- cls = objc_getClass ((*pCategory)->class_name);
- // the class & all categories are now bound in
- (*load_method) ((id) cls, @selector(load));
+ offer_shared_range(aligned_start, aligned_end);
+
+ if (PrintSharing) {
+ _objc_inform("SHARING: OFFER [%p..%p) (%d pages) for %s",
+ local_sels, local_sels+local_size,
+ (aligned_end-aligned_start) / vm_page_size,
+ _nameForHeader(hi->mhdr));
}
}
}
}
}
+
/***********************************************************************
-* runtime configuration
+* objc_setConfiguration
+* Read environment variables that affect the runtime.
+* Also print environment variable help, if requested.
**********************************************************************/
static void objc_setConfiguration() {
- if ( LaunchingDebug == -1 ) {
- // watch image loading and binding
- LaunchingDebug = getenv("LaunchingDebug") != NULL;
+ 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.
**********************************************************************/
// add further cleanup here...
- free(data);
+ _free_internal(data);
}
}
/***********************************************************************
-* _objcInit.
-* Library initializer called by dyld & from crt0
+* _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
+}
-void _objcInit(void) {
+
+/***********************************************************************
+* 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;
- static int _done = 0;
- extern void __CFInitialize(void);
- extern int ptrace(int, int, int, int); // a system call visible to sc_trace
- /* Protect against multiple invocations, as all library
- * initializers should. */
- if (0 != _done) return;
- _done = 1;
+ // 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);
+ }
- ptrace(0xb000, 0, 0, 0);
- trace(0xb000, 0, 0, 0);
+ if (PrintImages) {
+ _objc_inform("IMAGES: processing %u newly-mapped images...\n", infoCount);
+ }
- // make sure CF is initialized before we go further;
- // someday this can be removed, as it'll probably be automatic
- __CFInitialize();
-
- pthread_key_create(&_objc_pthread_key, _objc_pthread_destroyspecific);
- // Create the class lookup table
- _objc_init_class_hash ();
+ // 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;
- trace(0xb001, 0, 0, 0);
+ 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;
+ }
- objc_setConfiguration(); // Get our configuration
-
- trace(0xb003, 0, 0, 0);
+ 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)":"");
+ }
+ }
- // a pre-cheetah comment:
- // XXXXX BEFORE HERE *NO* PAGES ARE STOMPED ON;
+ // 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);
+ }
- // Register our image mapping routine with dyld so it
- // gets invoked when an image is added. This also invokes
- // the callback right now on any images already present.
- // The modules present in the application and the existing
- // mapped images are treated differently than a newly discovered
- // mapped image - we process all modules for classes before
- // trying to install_relationships (glue up their superclasses)
- // or trying to send them any +load methods.
+ // Initialize everything. Parts of this order are important for
+ // correctness or performance.
- // So we tell the map_image dyld callback to not do this part...
+ // Read classes from all images.
+ for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
+ _objc_read_classes_from_image(hInfo);
+ }
- Postpone_install_relationships = 1;
+ // Read categories from all images.
+ for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
+ _objc_read_categories_from_image(hInfo);
+ }
- // register for unmapping first so we can't miss any during load attempts
- _dyld_register_func_for_remove_image (&_objc_unmap_image);
+ // Connect classes from all images.
+ for (hInfo = firstNewHeader; hInfo != NULL; hInfo = hInfo->next) {
+ _objc_connect_classes_from_image(hInfo);
+ }
- // finally, register for images
- _dyld_register_func_for_add_image (&_objc_map_image);
+ // 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);
+ }
- // a pre-cheetah comment:
- // XXXXX BEFORE HERE *ALL* PAGES ARE STOMPED ON
+ // Close any shared range file left open during selector uniquing
+ clear_shared_range_file_cache();
- Postpone_install_relationships = 0;
+ firstTime = NO;
- trace(0xb006, 0, 0, 0);
-
- // Install relations on classes that were found
- for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next)
- {
- int nModules;
- int index;
- struct objc_module * module;
- struct objc_class * cls;
+ // Call pending +load methods.
+ // Note that this may in turn cause map_images() to be called again.
+ call_load_methods();
+}
- module = (struct objc_module *) ((unsigned long) hInfo->mod_ptr + hInfo->image_slide);
- for (nModules = hInfo->mod_count; nModules; nModules -= 1)
- {
- for (index = 0; index < module->symtab->cls_def_cnt; index += 1)
- {
- cls = (struct objc_class *) module->symtab->defs[index];
- _class_install_relationships (cls, module->version);
- }
- module += 1;
+/***********************************************************************
+* 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);
+ }
}
+ }
+}
- trace(0xb007, hInfo, hInfo->mod_count, 0);
+/***********************************************************************
+* _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);
}
+}
- trace(0xb008, 0, 0, 0);
- for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next)
+/***********************************************************************
+* _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)
{
- // Initialize the isa pointers of all NXConstantString objects
- (void)_objc_fixup_string_objects_for_image (hInfo);
+ // 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);
+ }
+ }
+ }
+
- // Convert class refs from name pointers to ids
- (void)_objc_map_class_refs_for_image (hInfo);
+ // 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 ();
+ }
+ }
}
- trace(0xb00a, 0, 0, 0);
+ OBJC_UNLOCK(&classLock);
+}
- // For each image selectorize the method names and +_fixup each of
- // protocols in the image
- for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next)
- _objc_fixup_protocol_objects_for_image (hInfo);
- for (hInfo = FirstHeader; hInfo != NULL; hInfo = hInfo->next)
- _objc_call_loads_for_image (hInfo);
+/***********************************************************************
+* _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;
- ptrace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init
- trace(0xb00f, 0, 0, 0); // end of __initialize_objc ObjC init
+ 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;
+ }
+ }
+ }
+ }
+ }
}
/***********************************************************************
-* _objc_map_image.
+* 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 _objc_map_image(headerType *mh, unsigned long vmaddr_slide)
+static void unload_paranoia(header_info *hi)
{
- static int dumpClasses = -1;
- header_info *hInfo;
+ // 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);
- if ( dumpClasses == -1 ) {
- if ( getenv("OBJC_DUMP_CLASSES") ) dumpClasses = 1;
- else dumpClasses = 0;
+ 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);
+ }
+ }
+ }
}
- trace(0xb100, 0, 0, 0);
+ // 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);
+}
- // Add this header to the chain
- hInfo = _objc_addHeader (mh, vmaddr_slide);
- if (!hInfo) return;
+/***********************************************************************
+* _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;
- if (LaunchingDebug) { _objc_syslog("objc_map_image for %s\n", _nameForHeader(mh)); }
+ // 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)" : "");
+ }
- trace(0xb101, 0, 0, 0);
+ // 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);
- // Register any categories and/or classes and/or selectors this image contains
- _objc_add_categories_from_image (hInfo);
+ // Remove header_info from header list
+ _objc_removeHeader(hi);
+}
- trace(0xb103, 0, 0, 0);
- _objc_add_classes_from_image (class_hash, hInfo);
+/***********************************************************************
+* _objc_setNilReceiver
+**********************************************************************/
+id _objc_setNilReceiver(id newNilReceiver)
+{
+ id oldNilReceiver;
- trace(0xb104, 0, 0, 0);
+ oldNilReceiver = _objc_nilReceiver;
+ _objc_nilReceiver = newNilReceiver;
- _objc_fixup_selector_refs (hInfo);
+ return oldNilReceiver;
+}
- trace(0xb105, 0, 0, 0);
+/***********************************************************************
+* _objc_getNilReceiver
+**********************************************************************/
+id _objc_getNilReceiver(void)
+{
+ return _objc_nilReceiver;
+}
- // Log all known class names, if asked
- if ( dumpClasses )
- {
- printf ("classes...\n");
- objc_dump_class_hash ();
- }
- if (!Postpone_install_relationships)
- {
- int nModules;
- int index;
- struct objc_module * module;
+/***********************************************************************
+* _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;
+}
- // Major loop - process each module
- module = (struct objc_module *) ((unsigned long) hInfo->mod_ptr + hInfo->image_slide);
- trace(0xb106, hInfo->mod_count, 0, 0);
+#if defined(__ppc__)
- for (nModules = hInfo->mod_count; nModules; nModules -= 1)
- {
- // Minor loop - process each class in a given module
- for (index = 0; index < module->symtab->cls_def_cnt; index += 1)
- {
- struct objc_class * cls;
+/**********************************************************************
+* 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;
+ }
+}
- // Locate the class description
- cls = (struct objc_class *) module->symtab->defs[index];
+// defined(__ppc__)
+#endif
- // If there is no superclass or the superclass can be found,
- // install this class, and invoke the expected callback
- if (!((struct objc_class *)cls)->super_class || objc_lookUpClass ((char *) ((struct objc_class *)cls)->super_class))
- {
- _class_install_relationships (cls, module->version);
- if (callbackFunction)
- (*callbackFunction) (cls, 0);
- }
- else
+
+/**********************************************************************
+* 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?
{
- // Super class can not be found yet, arrange for this class to
- // be filled in later
- objc_pendClassInstallation (cls, module->version);
- ((struct objc_class *)cls)->super_class = _objc_getNonexistentClass ();
- ((struct objc_class *)cls)->isa->super_class = _objc_getNonexistentClass ();
+ // 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;
}
-
- // Move on
- module += 1;
+ } else {
+ // Unopened file looks funny - don't open it
+ return -1;
}
+ }
+}
- trace(0xb108, 0, 0, 0);
-
- // Initialize the isa pointers of all NXConstantString objects
- _objc_fixup_string_objects_for_image (hInfo);
- trace(0xb109, 0, 0, 0);
+/**********************************************************************
+ * Shared range support:
+ *
+ * Some libraries contain many pages worth of selector references.
+ * In most processes, these libraries get loaded at the same addresses,
+ * so the selectors are uniqued to the same values. To save memory,
+ * the runtime tries to share these memory pages across processes.
+ *
+ * A file /tmp/objc_sharing_<arch>_<euid> records memory ranges and process
+ * IDs. When a set of selector refs is to be uniqued, this file is checked
+ * for a matching memory range being shared by another process. If
+ * such a range is found:
+ * 1. map the sharing process's memory somewhere into this address space
+ * 2. read from the real selector refs and write into the mapped memory.
+ * 3. vm_copy from the mapped memory to the real selector refs location
+ * 4. deallocate the mapped memory
+ *
+ * The mapped memory is merely used as a guess. Correct execution is
+ * guaranteed no matter what values the mapped memory actually contains.
+ * If the mapped memory really matches the values needed in this process,
+ * the mapped memory will be unchanged. If the mapped memory doesn't match,
+ * or contains random values, it will be fixed up to the correct values.
+ * The memory is shared whenever the guess happens to be correct.
+ *
+ * The file of shared ranges is imprecise. Processes may die leaving
+ * their entries in the file. A PID may be recycled to some process that
+ * does not use Objective-C. The sharing mechanism is robust in the face
+ * of these failures. Bad shared memory is simply fixed up. No shared
+ * memory means the selectors are fixed in place. If an entry in the
+ * file is found to be unusable, the process that finds it will instead
+ * offer to share its own memory, replacing the bad entry in the file.
+ *
+ * Individual entries in the file are written atomically, but the file is
+ * otherwise unsynchronized. At worst, a sharing opportunity may be missed
+ * because two new entries are written simultaneously in the same place.
+ **********************************************************************/
+
+
+struct remote_range_t {
+ vm_range_t range;
+ pid_t pid;
+};
- // Convert class refs from name pointers to ids
- _objc_map_class_refs_for_image (hInfo);
- trace(0xb10a, 0, 0, 0);
+// Cache for the last shared range file used, and its EUID.
+static pthread_mutex_t sharedRangeLock = PTHREAD_MUTEX_INITIALIZER;
+static uid_t sharedRangeEUID = 0;
+static FILE * sharedRangeFile = NULL;
+static BOOL sharedRangeFileInUse = NO;
- // Selectorize the method names and +_fixup each of
- // protocols in the image
- _objc_fixup_protocol_objects_for_image (hInfo);
- trace(0xb10b, 0, 0, 0);
+/**********************************************************************
+* open_shared_range_file
+* Open the shared range file "/tmp/objc_sharing_<arch>_<euid>" in
+* the given mode.
+* The returned file should be closed with close_shared_range_file().
+**********************************************************************/
+static FILE *open_shared_range_file(BOOL create)
+{
+ const char arch[] =
+#if defined(__ppc__) || defined(ppc)
+ "ppc";
+#elif defined(__ppc64__) || defined(ppc64)
+ "ppc64";
+#elif defined(__i386__) || defined(i386)
+ "i386";
+#else
+# error "unknown architecture"
+#endif
+ char filename[18 + sizeof(arch) + 1 + 3*sizeof(uid_t) + 1];
+ uid_t euid;
+ FILE *file = NULL;
+ int fd;
+
+ // Never share when superuser
+ euid = geteuid();
+ if (euid == 0) {
+ if (PrintSharing) {
+ _objc_inform("SHARING: superuser never shares");
+ }
+ return NULL;
+ }
- // Call +load on all classes and categorized classes
- _objc_call_loads_for_image (hInfo);
+ // Return cached file if it matches and it's not still being used
+ pthread_mutex_lock(&sharedRangeLock);
+ if (!sharedRangeFileInUse && euid == sharedRangeEUID) {
+ file = sharedRangeFile;
+ sharedRangeFileInUse = YES;
+ pthread_mutex_unlock(&sharedRangeLock);
+ rewind(file);
+ return file;
+ }
+ pthread_mutex_unlock(&sharedRangeLock);
- trace(0xb10c, 0, 0, 0);
+ // Open /tmp/objc_sharing_<euid>
+ snprintf(filename,sizeof(filename), "/tmp/objc_sharing_%s_%u", arch, euid);
+ fd = secure_open(filename, O_RDWR | (create ? O_CREAT : 0), euid);
+ if (fd >= 0) {
+ file = fdopen(fd, "r+");
}
- trace(0xb10f, 0, 0, 0);
+ if (file) {
+ // Cache this file if there's no already-open file cached
+ pthread_mutex_lock(&sharedRangeLock);
+ if (!sharedRangeFileInUse) {
+ sharedRangeFile = file;
+ sharedRangeEUID = euid;
+ sharedRangeFileInUse = YES;
+ }
+ pthread_mutex_unlock(&sharedRangeLock);
+ }
+ else {
+ // open() or fdopen() failed
+ if (PrintSharing) {
+ _objc_inform("SHARING: bad or missing sharing file '%s': %s",
+ filename, errno ? strerror(errno) :
+ "potential security violation");
+ }
+ }
+
+ return file;
}
-/***********************************************************************
-* _objc_unmap_image.
+
+/**********************************************************************
+* close_shared_range_file
+* Close a file opened with open_shared_range_file.
+* The file may actually be kept open and cached for a future
+* open_shared_range_file call. If so, clear_shared_range_file_cache()
+* can be used to really close the file.
**********************************************************************/
-static void _objc_unmap_image(headerType *mh, unsigned long vmaddr_slide) {
- // we shouldn't have it if it didn't have objc data
- // if we do have it, do a fatal
- _objc_fatalHeader(mh);
+static void close_shared_range_file(FILE *file)
+{
+ // Flush any writes in case the file is kept open.
+ fflush(file);
+
+ pthread_mutex_lock(&sharedRangeLock);
+ if (file == sharedRangeFile && sharedRangeFileInUse) {
+ // This file is the cached shared file.
+ // Leave the file open and cached, but no longer in use.
+ sharedRangeFileInUse = NO;
+ } else {
+ // This is not the cached file.
+ fclose(file);
+ }
+ pthread_mutex_unlock(&sharedRangeLock);
}
-/***********************************************************************
-* objc_setNilObjectMsgHandler.
+
+/**********************************************************************
+* clear_shared_range_file_cache
+* Really close any file left open by close_shared_range_file.
+* This is called by map_images() after loading multiple images, each
+* of which may have used the shared range file.
**********************************************************************/
-void objc_setNilObjectMsgHandler (NilObjectMsgCallback nilObjMsgCallback)
+static void clear_shared_range_file_cache(void)
{
- _objc_msgNil = nilObjMsgCallback;
+ pthread_mutex_lock(&sharedRangeLock);
+ if (sharedRangeFile && !sharedRangeFileInUse) {
+ fclose(sharedRangeFile);
+ sharedRangeFile = NULL;
+ sharedRangeEUID = 0;
+ sharedRangeFileInUse = 0;
+ }
+ pthread_mutex_unlock(&sharedRangeLock);
}
-/***********************************************************************
-* objc_getNilObjectMsgHandler.
+
+/**********************************************************************
+* get_shared_range
+* Try to find a shared range matching addresses [aligned_start..aligned_end).
+* If a range is found, it is mapped into this process and returned.
+* If no range is found, or the found range could not be mapped for
+* some reason, the range {0, 0} is returned.
+* aligned_start and aligned_end must be page-aligned.
**********************************************************************/
-NilObjectMsgCallback objc_getNilObjectMsgHandler (void)
+static vm_range_t get_shared_range(vm_address_t aligned_start,
+ vm_address_t aligned_end)
{
- return _objc_msgNil;
+ struct remote_range_t remote;
+ vm_range_t result;
+ FILE *file;
+
+ result.address = 0;
+ result.size = 0;
+
+ // Open shared range file, but don't create it
+ file = open_shared_range_file(NO);
+ if (!file) return result;
+
+ // Search for the desired memory range
+ while (1 == fread(&remote, sizeof(remote), 1, file)) {
+ if (remote.pid != 0 &&
+ remote.range.address == aligned_start &&
+ remote.range.size == aligned_end - aligned_start)
+ {
+ // Found a match in the file - try to grab the memory
+ mach_port_name_t remote_task;
+ vm_prot_t cur_prot, max_prot;
+ vm_address_t local_addr;
+ kern_return_t kr;
+
+ // Find the task offering the memory
+ kr = task_for_pid(mach_task_self(), remote.pid, &remote_task);
+ if (kr != KERN_SUCCESS) {
+ // task is dead
+ if (PrintSharing) {
+ _objc_inform("SHARING: no task for pid %d: %s",
+ remote.pid, mach_error_string(kr));
+ }
+ break;
+ }
+
+ // Map the memory into our process
+ local_addr = 0;
+ kr = vm_remap(mach_task_self(), &local_addr, remote.range.size,
+ 0 /*alignment*/, 1 /*anywhere*/,
+ remote_task, remote.range.address,
+ 1 /*copy*/, &cur_prot, &max_prot, VM_INHERIT_NONE);
+ mach_port_deallocate(mach_task_self(), remote_task);
+
+ if (kr != KERN_SUCCESS) {
+ // couldn't map memory
+ if (PrintSharing) {
+ _objc_inform("SHARING: vm_remap from pid %d failed: %s",
+ remote.pid, mach_error_string(kr));
+ }
+ break;
+ }
+
+ if (!(cur_prot & VM_PROT_READ) || !(cur_prot & VM_PROT_WRITE)) {
+ // Received memory is not mapped read/write - don't use it
+ // fixme try to change permissions? check max_prot?
+ if (PrintSharing) {
+ _objc_inform("SHARING: memory from pid %d not read/write",
+ remote.pid);
+ }
+ vm_deallocate(mach_task_self(), local_addr, remote.range.size);
+ break;
+ }
+
+ // Success
+ result.address = local_addr;
+ result.size = remote.range.size;
+ }
+ }
+
+ close_shared_range_file(file);
+ return result;
}
+/**********************************************************************
+* offer_shared_range
+* Offer memory range [aligned_start..aligned_end) in this process
+* to other Objective-C-using processes.
+* If some other entry in the shared range list matches this range,
+* is is overwritten with this process's PID. (Thus any stale PIDs are
+* replaced.)
+* If the shared range file could not be updated for any reason, this
+* function fails silently.
+* aligned_start and aligned_end must be page-aligned.
+**********************************************************************/
+static void offer_shared_range(vm_address_t aligned_start,
+ vm_address_t aligned_end)
+{
+ struct remote_range_t remote;
+ struct remote_range_t local;
+ BOOL found = NO;
+ FILE *file;
+ int err = 0;
+
+ local.range.address = aligned_start;
+ local.range.size = aligned_end - aligned_start;
+ local.pid = getpid();
+
+ // Open shared range file, creating if necessary
+ file = open_shared_range_file(YES);
+ if (!file) return;
+
+ // Find an existing entry for this range, if any
+ while (1 == fread(&remote, sizeof(remote), 1, file)) {
+ if (remote.pid != 0 &&
+ remote.range.address == aligned_start &&
+ remote.range.size == aligned_end - aligned_start)
+ {
+ // Found a match - overwrite it
+ err = fseek(file, -sizeof(remote), SEEK_CUR);
+ found = YES;
+ break;
+ }
+ }
+
+ if (!found) {
+ // No existing entry - write at the end of the file
+ err = fseek(file, 0, SEEK_END);
+ }
+
+ if (err == 0) {
+ fwrite(&local, sizeof(local), 1, file);
+ }
+
+ close_shared_range_file(file);
+}
+
+
+/**********************************************************************
+* install_shared_range
+* Install a shared range received from get_shared_range() into
+* its final resting place.
+* If possible, the memory is copied using virtual memory magic rather
+* than actual data writes. dst always gets updated values, even if
+* virtual memory magic is not possible.
+* The shared range is always deallocated.
+* src and dst must be page-aligned.
+**********************************************************************/
+static void install_shared_range(vm_range_t src, vm_address_t dst)
+{
+ kern_return_t kr;
+
+ // Copy from src to dst
+ kr = vm_copy(mach_task_self(), src.address, src.size, dst);
+ if (kr != KERN_SUCCESS) {
+ // VM copy failed. Use non-VM copy.
+ if (PrintSharing) {
+ _objc_inform("SHARING: vm_copy failed: %s", mach_error_string(kr));
+ }
+ memmove((void *)dst, (void *)src.address, src.size);
+ }
+
+ // Unmap the shared range at src
+ vm_deallocate(mach_task_self(), src.address, src.size);
+}