diff --git a/dtable.c b/dtable.c index 5f9611a..6f5f8a9 100644 --- a/dtable.c +++ b/dtable.c @@ -360,6 +360,11 @@ PRIVATE void objc_update_dtable_for_class(Class cls) update_dtable(dtable); } +PRIVATE void objc_update_dtable_for_new_superclass(Class cls, Class newSuper) +{ + // This is not required. objc_dtable_lookup looks directly at the class's + // superclass for each lookup, and the cache only stores local slots. +} PRIVATE void add_method_list_to_class(Class cls, struct objc_method_list *list) { @@ -622,6 +627,69 @@ PRIVATE void objc_update_dtable_for_class(Class cls) checkARCAccessors(cls); } +static void rebaseDtableRecursive(Class cls, Class owner, Class newSuper) +{ + dtable_t parentDtable = dtable_for_class(newSuper); + dtable_t dtable = dtable_for_class(cls); + uint32_t idx = 0; + struct objc_slot *slot; + while ((slot = SparseArrayNext(parentDtable, &idx))) + { + struct objc_slot *existingSlot = SparseArrayLookup(dtable, idx); + if (NULL != existingSlot) + { + if (slot->method == existingSlot->method + || slot->owner == existingSlot->owner + || classIsOrInherits(existingSlot->owner, owner)) + { + // IMPs should not be changing right now; disregard anything + // that's already owned by the new parent. + // Slots from base or lower are safe from replacement. + continue; + } + // version doesn't need to be updated since we're replacing slots inherited + // directly from the old parent; this does not invalidate them. + } + // propagate changes downward + SparseArrayInsert(dtable, idx, slot); + } + + idx = 0; + while ((slot = SparseArrayNext(dtable, &idx))) + { + void *exist = SparseArrayLookup(parentDtable, idx); + // Everything that exists in the parent dtable was merged above, + // and everything implemented at base or below is safe. + if (exist || classIsOrInherits(slot->owner, owner)) { continue; } + + SparseArrayInsert(dtable, idx, 0); + } + + // merge can make a class ARC-compatible. + checkARCAccessors(cls); + + for (struct objc_class *subclass=cls->subclass_list ; + Nil != subclass ; subclass = subclass->sibling_class) + { + // Don't bother updating dtables for subclasses that haven't been + // initialized yet + if (!classHasDtable(subclass)) { continue; } + rebaseDtableRecursive(subclass, owner, newSuper); + } + +} + +PRIVATE void objc_update_dtable_for_new_superclass(Class cls, Class newSuper) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + rebaseDtableRecursive(cls, cls, newSuper); + + return; +} + PRIVATE void add_method_list_to_class(Class cls, struct objc_method_list *list) { diff --git a/dtable.h b/dtable.h index c374892..d841dbc 100644 --- a/dtable.h +++ b/dtable.h @@ -115,6 +115,11 @@ static inline int classHasDtable(struct objc_class *cls) * modifying a class's method list. */ void objc_update_dtable_for_class(Class); +/** + * Updates the dtable for a class and its subclasses. Must be called after + * changing and initializing a class's superclass. + */ +void objc_update_dtable_for_new_superclass(Class, Class); /** * Adds a single method list to a class. This is used when loading categories, * and is faster than completely rebuilding the dtable. diff --git a/runtime.c b/runtime.c index 2995c61..5ba686b 100644 --- a/runtime.c +++ b/runtime.c @@ -23,6 +23,8 @@ struct objc_slot *objc_get_slot(Class cls, SEL selector); #define CHECK_ARG(arg) if (0 == arg) { return 0; } static inline void safe_remove_from_subclass_list(Class cls); +PRIVATE void objc_resolve_class(Class); +void objc_send_initialize(id object); /** * Calls C++ destructors in the correct order. @@ -522,9 +524,12 @@ Class class_setSuperclass(Class cls, Class newSuper) CHECK_ARG(newSuper); if (Nil == cls) { return Nil; } - LOCK_RUNTIME_FOR_SCOPE(); + LOCK_RUNTIME(); + + if (cls->super_class == newSuper) { return newSuper; } safe_remove_from_subclass_list(cls); + objc_resolve_class(newSuper); Class oldSuper = cls->super_class; cls->super_class = newSuper; @@ -533,32 +538,43 @@ Class class_setSuperclass(Class cls, Class newSuper) cls->sibling_class = cls->super_class->subclass_list; cls->super_class->subclass_list = cls; - if (!class_isMetaClass(cls)) + if (UNLIKELY(class_isMetaClass(cls))) { - // Update the metaclass's superclass. - class_setSuperclass(cls->isa, newSuper->isa); + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; } else { - // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. - cls->isa = newSuper->isa; + Class meta = cls->isa, newSuperMeta = newSuper->isa; + // Update the metaclass's superclass. + safe_remove_from_subclass_list(meta); + objc_resolve_class(newSuperMeta); + + meta->super_class = newSuperMeta; + meta->isa = newSuperMeta->isa; + + // The super class's subclass list is used in certain method resolution scenarios. + meta->sibling_class = newSuperMeta->subclass_list; + newSuperMeta->subclass_list = meta; } - // Make sure the superclass is initialized if we're initialized. - if (objc_test_class_flag(cls, objc_class_flag_initialized)) + LOCK(&initialize_lock); + if (!objc_test_class_flag(cls, objc_class_flag_initialized)) { - objc_send_initialize(newSuper); - // Update the class's dtable to reflect its new superclass's dtable. - if (cls->dtable != uninstalled_dtable) - { - // we can't use objc_update_dtable_for_class here, as it doesn't take into account - // superclasses. It only walks downward. - free_dtable(cls->dtable); - cls->dtable = uninstalled_dtable; - cls->dtable = create_dtable_for_class(cls, uninstalled_dtable); - } + // Uninitialized classes don't have dtables to update + // and don't need their superclasses initialized. + UNLOCK(&initialize_lock); + UNLOCK_RUNTIME(); + return oldSuper; } + UNLOCK(&initialize_lock); + UNLOCK_RUNTIME(); + + objc_send_initialize(newSuper); // also initializes the metaclass + objc_update_dtable_for_new_superclass(cls->isa, newSuper->isa); + objc_update_dtable_for_new_superclass(cls, newSuper); + return oldSuper; } @@ -828,8 +844,6 @@ const char *object_getClassName(id obj) return class_getName(object_getClass(obj)); } -PRIVATE void objc_resolve_class(Class); - void objc_registerClassPair(Class cls) { LOCK_RUNTIME_FOR_SCOPE();