diff --git a/associate.m b/associate.m index cb5e03f..dff8aa4 100644 --- a/associate.m +++ b/associate.m @@ -181,12 +181,12 @@ static Class allocateHiddenClass(Class superclass) // Set the superclass pointer to the name. The runtime will fix this when // the class links are resolved. newClass->name = superclass->name; - newClass->info = objc_class_flag_resolved | objc_class_flag_initialized | + newClass->info = objc_class_flag_resolved | objc_class_flag_class | objc_class_flag_user_created | objc_class_flag_new_abi | objc_class_flag_hidden_class | objc_class_flag_assoc_class; newClass->super_class = superclass; - newClass->dtable = objc_copy_dtable_for_class(superclass->dtable, newClass); + newClass->dtable = uninstalled_dtable; newClass->instance_size = superclass->instance_size; if (objc_test_class_flag(superclass, objc_class_flag_meta)) { @@ -235,13 +235,25 @@ static void deallocHiddenClass(id obj, SEL _cmd) free(hiddenClass); } -void objc_setAssociatedObject(id object, - void *key, - id value, - objc_AssociationPolicy policy) +static struct reference_list* referenceListForObject(id object, BOOL create) { + if (class_isMetaClass(object->isa)) + { + Class cls = (Class)object; + if ((NULL == cls->extra_data) && create) + { + int *lock = lock_for_pointer(cls); + lock_spinlock(lock); + if (NULL == cls->extra_data) + { + cls->extra_data = calloc(1, sizeof(struct reference_list)); + } + unlock_spinlock(lock); + } + return cls->extra_data; + } Class hiddenClass = findHiddenClass(object); - if (NULL == hiddenClass) + if ((NULL == hiddenClass) && create) { int *lock = lock_for_pointer(object); lock_spinlock(lock); @@ -252,15 +264,22 @@ void objc_setAssociatedObject(id object, } unlock_spinlock(lock); } - struct reference_list *list = object_getIndexedIvars(hiddenClass); + return hiddenClass ? object_getIndexedIvars(hiddenClass) : NULL; +} + +void objc_setAssociatedObject(id object, + void *key, + id value, + objc_AssociationPolicy policy) +{ + struct reference_list *list = referenceListForObject(object, YES); setReference(list, key, value, policy); } id objc_getAssociatedObject(id object, void *key) { - Class hiddenClass = findHiddenClass(object); - if (NULL == hiddenClass) { return nil; } - struct reference_list *list = object_getIndexedIvars(hiddenClass); + struct reference_list *list = referenceListForObject(object, NO); + if (NULL == list) { return nil; } struct reference *r = findReference(list, key); return r ? r->object : nil; } @@ -268,8 +287,5 @@ id objc_getAssociatedObject(id object, void *key) void objc_removeAssociatedObjects(id object) { - Class hiddenClass = findHiddenClass(object); - if (NULL == hiddenClass) { return; } - struct reference_list *list = object_getIndexedIvars(hiddenClass); - cleanupReferenceList(list); + cleanupReferenceList(referenceListForObject(object, NO)); } diff --git a/class.h b/class.h index de0b988..2e592d4 100644 --- a/class.h +++ b/class.h @@ -76,7 +76,7 @@ struct objc_class /** * Linked list of extra data attached to this class. */ - struct class_annotation *extra_data; + struct reference_list *extra_data; /** * New ABI. The following fields are only available with classes compiled to * support the new ABI. You may test whether any given class supports this diff --git a/dtable.c b/dtable.c index 899b3d9..d15e99e 100644 --- a/dtable.c +++ b/dtable.c @@ -28,7 +28,6 @@ static void collectMethodsForMethodListToSparseArray( } for (unsigned i=0 ; icount ; i++) { - //fprintf(stderr, "Adding method %s (%d)\n", sel_getName(list->methods[i].selector), PTR_TO_IDX(list->methods[i].selector->name)); SparseArrayInsert(sarray, list->methods[i].selector->index, (void*)&list->methods[i]); } @@ -74,7 +73,7 @@ PRIVATE void update_dispatch_table_for_class(Class cls) } } -static dtable_t create_dtable_for_class(Class class) +static SparseArray *create_dtable_for_class(Class class, dtable_t root_dtable) { // Don't create a dtable for a class that already has one if (classHasDtable(class)) { return dtable_for_class(class); } @@ -85,8 +84,6 @@ static dtable_t create_dtable_for_class(Class class) // waiting on the lock. if (classHasDtable(class)) { return dtable_for_class(class); } - Class super = class_getSuperclass(class); - /* Allocate dtable if necessary */ dtable_t dtable = calloc(1, sizeof(struct objc_dtable)); dtable->cls = class; @@ -179,7 +176,6 @@ static void insert_slot(dtable_t dtable, struct objc_slot *slot, uint32_t idx) static void update_dtable(dtable_t dtable) { - //fprintf(stderr, "Updating dtable for %s! %d\n", dtable->cls->name, dtable_depth); Class cls = dtable->cls; if (NULL == cls->methods) { return; } @@ -253,7 +249,6 @@ PRIVATE struct objc_slot* objc_dtable_lookup(dtable_t dtable, uint32_t uid) { update_dtable(dtable); } - //fprintf(stderr, "Using slow path: %d entries in table for %s\n", dtable->slot_count, dtable->cls->name); struct slots_list *s = find_slot(uid, dtable->slots, dtable->slot_count); if (NULL != s) { @@ -302,7 +297,6 @@ static BOOL installMethodInDtable(Class class, struct objc_slot *slot = SparseArrayLookup(dtable, sel_id); if (NULL != slot) { - //fprintf(stderr, "Slot already exists...\n"); // If this method is the one already installed, pretend to install it again. if (slot->method == method->imp) { return NO; } @@ -314,7 +308,6 @@ static BOOL installMethodInDtable(Class class, // Don't replace methods if we're not meant to (if they're from // later in a method list, for example) if (!replaceExisting) { return NO; } - //fprintf(stderr, "Replacing method %p %s in %s with %p\n", slot->method, sel_getName(method->selector), class->name, method->imp); slot->method = method->imp; return YES; } @@ -328,13 +321,11 @@ static BOOL installMethodInDtable(Class class, { if (installedFor == owner) { - //fprintf(stderr, "Not installing %s from %s in %s - already overridden from %s\n", sel_getName(method->selector), owner->name, class->name, slot->owner->name); return NO; } } } struct objc_slot *oldSlot = slot; - //fprintf(stderr, "Installing method %p (%d) %s in %s (previous slot owned by %s)\n", method->imp, sel_id, sel_getName(method->selector), class->name, slot? oldSlot->owner->name: "(no one)"); slot = new_slot_for_method_in_class((void*)method, owner); SparseArrayInsert(dtable, sel_id, slot); // In TDD mode, we also register the first typed method that we @@ -345,7 +336,6 @@ static BOOL installMethodInDtable(Class class, // Invalidate the old slot, if there is one. if (NULL != oldSlot) { - //fprintf(stderr, "Overriding method %p %s from %s in %s with %x\n", slot->method, sel_getName(method->selector), oldSlot->owner->name, class->name, method->imp); oldSlot->version++; } return YES; @@ -398,11 +388,9 @@ PRIVATE void objc_update_dtable_for_class(Class cls) { // Only update real dtables if (!classHasDtable(cls)) { return; } - //fprintf(stderr, "Updating dtable for %s\n", cls->name); LOCK_RUNTIME_FOR_SCOPE(); - //fprintf(stderr, "Adding methods to %s\n", cls->name); SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); collectMethodsForMethodListToSparseArray((void*)cls->methods, methods); installMethodsInClass(cls, cls, methods, YES); @@ -422,7 +410,7 @@ PRIVATE void update_dispatch_table_for_class(Class cls) objc_update_dtable_for_class(cls); } -static SparseArray *create_dtable_for_class(Class class) +static SparseArray *create_dtable_for_class(Class class, dtable_t root_dtable) { // Don't create a dtable for a class that already has one if (classHasDtable(class)) { return dtable_for_class(class); } @@ -446,9 +434,16 @@ static SparseArray *create_dtable_for_class(Class class) dtable_t super_dtable = dtable_for_class(super); if (super_dtable == uninstalled_dtable) { - super_dtable = create_dtable_for_class(super); + if (super->isa == class) + { + super_dtable = root_dtable; + } + else + { + abort(); + } } - dtable = SparseArrayCopy(dtable_for_class(super)); + dtable = SparseArrayCopy(super_dtable); } // When constructing the initial dtable for a class, we iterate along the @@ -508,6 +503,19 @@ PRIVATE dtable_t objc_copy_dtable_for_class(dtable_t old, Class cls) void objc_resolve_class(Class); +int objc_sync_enter(id); +int objc_sync_exit(id); + +__attribute__((unused)) static void objc_release_object_lock(id *x) +{ + objc_sync_exit(*x); +} +#define LOCK_OBJECT_FOR_SCOPE(obj) \ + __attribute__((cleanup(objc_release_object_lock)))\ + __attribute__((unused)) id lock_object_pointer = obj;\ + objc_sync_enter(obj); + + /** * Send a +initialize message to the receiver, if required. */ @@ -532,7 +540,6 @@ PRIVATE void objc_send_initialize(id object) // NOTE: Ideally, we would actually lock on the class object using // objc_sync_enter(). This should be fixed once sync.m contains a (fast) // special case for classes. - LOCK_FOR_SCOPE(&initialize_lock); // Make sure that the class is resolved. objc_resolve_class(class); @@ -543,19 +550,48 @@ PRIVATE void objc_send_initialize(id object) objc_send_initialize((id)class->super_class); } + LOCK(&initialize_lock); + // Superclass +initialize might possibly send a message to this class, in // which case this method would be called again. See NSObject and // NSAutoreleasePool +initialize interaction in GNUstep. - if (objc_test_class_flag(class, objc_class_flag_initialized)) { return; } + if (objc_test_class_flag(class, objc_class_flag_initialized)) + { + UNLOCK(&initialize_lock); + return; + } // Set the initialized flag on both this class and its metaclass, to make // sure that +initialize is only ever sent once. objc_set_class_flag(class, objc_class_flag_initialized); objc_set_class_flag(meta, objc_class_flag_initialized); + dtable_t class_dtable = create_dtable_for_class(class, uninstalled_dtable); + dtable_t dtable = create_dtable_for_class(meta, class_dtable); + + static SEL initializeSel = 0; + if (0 == initializeSel) + { + initializeSel = sel_registerName("initialize"); + } + + struct objc_slot *initializeSlot = + objc_dtable_lookup(dtable, initializeSel->index); + + // If there's no initialize method, then don't bother installing and + // removing the initialize dtable, just install both dtables correctly now + if (0 == initializeSlot || initializeSlot->owner != meta) + { + meta->dtable = dtable; + class->dtable = class_dtable; + UNLOCK(&initialize_lock); + return; + } + + LOCK_OBJECT_FOR_SCOPE((id)class); + // Create a temporary dtable, to be installed later. - dtable_t class_dtable = create_dtable_for_class(class); InitializingDtable buffer = { class, class_dtable, temporary_dtables }; temporary_dtables = &buffer; @@ -563,9 +599,9 @@ PRIVATE void objc_send_initialize(id object) // a message to this class in future, the lookup function will check this // buffer if the receiver's dtable is not installed, and block if // attempting to send a message to this class. - dtable_t dtable = create_dtable_for_class(meta); InitializingDtable meta_buffer = { meta, dtable, temporary_dtables }; temporary_dtables = &meta_buffer; + UNLOCK(&initialize_lock); // Store the buffer in the temporary dtables list. Note that it is safe to // insert it into a global list, even though it's a temporary variable, @@ -573,44 +609,15 @@ PRIVATE void objc_send_initialize(id object) // // FIXME: This will actually break if +initialize throws an exception... - static SEL initializeSel = 0; - if (0 == initializeSel) - { - initializeSel = sel_registerName("initialize"); - } - struct objc_slot *initializeSlot = - objc_dtable_lookup(dtable, initializeSel->index); - - if (0 != initializeSlot) - { - if (Nil != class->super_class) - { - // The dtable to use for sending messages to the superclass. This - // is the superclass's metaclass' dtable. - dtable_t super_dtable = dtable_for_class(class->super_class->isa); - struct objc_slot *superSlot = objc_dtable_lookup(super_dtable, - initializeSel->index); - // Check that this IMP comes from the class, not from its - // superclass. We still have to use dtable_for_class() here - // because our +initialize call might be in response to a message - // sent from a subclass (e.g. NSObject +initialize sending a - // message to NSAutoreleasePool: NSObject's dtable won't have been - // installed at this point. - if (0 == superSlot || superSlot->method != initializeSlot->method) - { - initializeSlot->method((id)class, initializeSel); - } - } - else - { - initializeSlot->method((id)class, initializeSel); - } - } + initializeSlot->method((id)class, initializeSel); - // Install the real dtable for both the class and the metaclass. + // Install the real dtable for both the class and the metaclass. We can do + // this without the lock, because now both ways of accessing the dtable are + // safe. We only need the lock when we remove the cached version. meta->dtable = dtable; class->dtable = class_dtable; + LOCK(&initialize_lock); // Remove the look-aside buffer entry. if (temporary_dtables == &meta_buffer) { @@ -625,4 +632,5 @@ PRIVATE void objc_send_initialize(id object) } prev->next = buffer.next; } + UNLOCK(&initialize_lock); } diff --git a/dtable.h b/dtable.h index 8b670f1..6ee6c00 100644 --- a/dtable.h +++ b/dtable.h @@ -4,6 +4,7 @@ #include "objc/slot.h" #include "visibility.h" #include +#include #ifdef __OBJC_LOW_MEMORY__ typedef struct objc_dtable* dtable_t; @@ -56,6 +57,7 @@ static inline dtable_t dtable_for_class(Class cls) { return cls->dtable; } + LOCK_FOR_SCOPE(&initialize_lock); if (classHasInstalledDtable(cls)) { diff --git a/sendmsg2.c b/sendmsg2.c index 4e83003..8a918de 100644 --- a/sendmsg2.c +++ b/sendmsg2.c @@ -116,7 +116,6 @@ Slot_t (*objc_plane_lookup)(id *receiver, SEL op, id sender) = */ Slot_t objc_msg_lookup_sender(id *receiver, SEL selector, id sender) { - //fprintf(stderr, "Looking up slot %s\n", sel_get_name(selector)); // Returning a nil slot allows the caller to cache the lookup for nil too, // although this is not particularly useful because the nil method can be // inlined trivially. @@ -140,7 +139,6 @@ Slot_t objc_msg_lookup_sender(id *receiver, SEL selector, id sender) void *receiverPlaneID = *((void**)receiver - 1); if (senderPlaneID == receiverPlaneID) { - //fprintf(stderr, "Intraplane message\n"); return objc_msg_lookup_internal(receiver, selector, sender); } return objc_plane_lookup(receiver, selector, sender); @@ -260,7 +258,6 @@ Slot_t objc_get_slot(Class cls, SEL selector) /* Install the dtable if it hasn't already been initialized. */ if (dtable == uninstalled_dtable) { - //objc_send_initialize((id)cls); dtable = dtable_for_class(cls); result = objc_dtable_lookup(dtable, selector->index); } @@ -270,6 +267,7 @@ Slot_t objc_get_slot(Class cls, SEL selector) // weren't looking result = objc_dtable_lookup(dtable, selector->index); } + Class class = cls; if (NULL == result) { if (!isSelRegistered(selector))