diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 97b66b9..4165d29 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -34,6 +34,7 @@ set(TESTS zeroSizedIVar.m exchange.m hash_table_delete.c + setSuperclass.m ) # Function for adding a test. This takes the name of the test and the list of diff --git a/Test/setSuperclass.m b/Test/setSuperclass.m new file mode 100644 index 0000000..236eff3 --- /dev/null +++ b/Test/setSuperclass.m @@ -0,0 +1,293 @@ +#include "../objc/runtime.h" +#include +#include + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +__attribute__((objc_root_class)) +@interface Root @end +@interface DefaultSuperclass: Root @end + +// test: new superclass when not initialized at the time of class_setSuperclass +@interface NotInitializedSuperclass1: Root @end +@interface Subclass1: DefaultSuperclass @end + +// test: new superclass when already initialized at the time of class_setSuperclass +@interface NotInitializedSuperclass2: Root @end +@interface Subclass2: DefaultSuperclass @end +@interface Subclass2Subclass: Subclass2 @end + +@interface ChangesDuringInitialize: DefaultSuperclass @end + +// test: class gets reparented under its parent's parent. +@interface RemovedFromHierarchy: DefaultSuperclass @end +@interface MovesUpwardsInHierarchy: RemovedFromHierarchy @end + +// test: one class initializes anotherwhile initializing during class_setSuperclass +@interface OtherInitializedClass: Root @end +@interface InitializesOneClassWhileBeingInitialized: NotInitializedSuperclass2 @end +@interface Subclass3: DefaultSuperclass @end + +@implementation Root ++ (Class)class { return self; } ++ (BOOL)respondsToSelector:(SEL)selector { + return class_respondsToSelector(object_getClass(self), selector); +} ++ (BOOL)instancesRespondToSelector:(SEL)selector { + return class_respondsToSelector(self, selector); +} +@end + +@implementation NotInitializedSuperclass1 +static BOOL _notInitializedSuperclass1Initialized = NO; ++ (void)initialize { + _notInitializedSuperclass1Initialized = YES; +} ++ (void)existsOnNotInitializedSuperclassMeta { }; ++ (int)sameNameMeta { return 12; } ++ (int)overriddenMeta { return 12; } + +- (BOOL)existsOnNotInitializedSuperclass { return YES; } +- (int)sameName { return 2; } +- (int)overridden { return 2; } +@end + +@implementation NotInitializedSuperclass2 +static BOOL _notInitializedSuperclass2Initialized = NO; ++ (void)initialize { + _notInitializedSuperclass2Initialized = YES; +} ++ (void)existsOnNotInitializedSuperclassMeta { }; ++ (int)sameNameMeta { return 13; } ++ (int)overriddenMeta { return 13; } +- (BOOL)existsOnNotInitializedSuperclass { return YES; } +- (int)sameName { return 3; } +- (int)overridden { return 3; } +@end + +@implementation DefaultSuperclass +static BOOL _alreadyInitializedSuperclassInitialized = NO; ++ (void)initialize { + _alreadyInitializedSuperclassInitialized = YES; +} ++ (void)existsOnDefaultSuperclassMeta { }; ++ (int)sameNameMeta { return 14; } ++ (int)overriddenMeta { return 14; } +- (BOOL)existsOnDefaultSuperclass { return YES; } +- (int)sameName { return 4; } +- (int)overridden { return 4; } +@end + +@implementation Subclass1 +static BOOL _subclass1Initialized = NO; ++ (void)initialize { + _subclass1Initialized = YES; +} ++ (int)overriddenMeta { return 15; } // shadows 14 +- (BOOL)existsOnSubclass1 { return YES; } +- (int)overridden { return 5; } // shadows 4 +@end + +@implementation Subclass2 +static BOOL _subclass2Initialized = NO; ++ (void)initialize { + _subclass2Initialized = YES; +} ++ (int)overriddenMeta { return 16; } // shadows 14 +- (BOOL)existsOnSubclass2 { return YES; } +- (int)overridden { return 6; } // shadows 4 +- (int)intermediateOverride { return 100; } +@end + +@implementation Subclass2Subclass +- (int)intermediateOverride { return 200; } +@end + +@implementation ChangesDuringInitialize ++ (void)initialize { + class_setSuperclass(self, objc_getClass("NotInitializedSuperclass1")); +} ++ (int)overriddenMeta { return 18; } +@end + +@implementation RemovedFromHierarchy ++ (int)overriddenMeta { return 19; } // shadows 14 on DefaultSuperClass ++ (int)sameNameMeta { return 19; } // shadows 14 on DefaultSuperClass ++ (void)onlyExistsOnRemovedClassMeta { } +- (void)onlyExistsOnRemovedClass { } +@end + +@implementation MovesUpwardsInHierarchy ++ (int)overriddenMeta { return 20; } // shadows 19 on RemovedFromHierarchy or 14 on DefaultSuperClass +@end + +@implementation OtherInitializedClass +static BOOL _otherInitializedClassInitialized = NO; ++ (void)initialize { + _otherInitializedClassInitialized = YES; +} +@end + +@implementation InitializesOneClassWhileBeingInitialized ++ (void)initialize { + [OtherInitializedClass class]; +} +@end + +@implementation Subclass3 +@end + +static int failures = 0; + +#define expect(x) do \ +{ \ + if (!(x)) \ + { \ + fprintf(stderr, "expectation FAILED: %s\n", #x); \ + ++failures; \ + } \ +} while(0) + +int main(int argc, char **argv) { + Class firstSuperclass = objc_getClass("DefaultSuperclass"); + Class subclass1 = objc_getClass("Subclass1"); + + /* Transitioning to a new superclass before +initialize has been called */ + { + Class subclass1 = objc_getClass("Subclass1"); + Class secondSuperclass = objc_getClass("NotInitializedSuperclass1"); + + assert(!_notInitializedSuperclass1Initialized); + assert(!_subclass1Initialized); + + class_setSuperclass(subclass1, secondSuperclass); + + // assert: dtable has not been installed; new superclass is still not initialized + assert(!_notInitializedSuperclass1Initialized); + + [Subclass1 class]; + // initialization and dtable installation has taken place + assert(_notInitializedSuperclass1Initialized); + + Subclass1 *subclass1instance1 = class_createInstance(subclass1, 0); + + // CLASS + // can call method on subclass + expect([subclass1instance1 existsOnSubclass1]); + // can call method on _new_ superclass + expect([subclass1instance1 existsOnNotInitializedSuperclass]); + // does not respond to selector from original superclass + expect(![subclass1 instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + // *does* respond to selector from new superclass + expect([subclass1 instancesRespondToSelector:@selector(existsOnNotInitializedSuperclass)]); + // method existing on both old and new superclass kept, IMP updated + expect(2 == [subclass1instance1 sameName]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(5 == [subclass1instance1 overridden]); + + + // METACLASS + // metaclass does not respond to selector from original meta superclass + expect(![subclass1 respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + // metaclass *does* respond to selector from new meta superclass + expect([subclass1 respondsToSelector:@selector(existsOnNotInitializedSuperclassMeta)]); + // method existing on both old and new superclass kept, IMP updated + expect(12 == [subclass1 sameNameMeta]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(15 == [subclass1 overriddenMeta]); + } + + /* Transitioning to a new superclass when +initialize has already been called */ + { + Class subclass2 = objc_getClass("Subclass2"); + Class secondSuperclass = objc_getClass("NotInitializedSuperclass2"); + assert(!_notInitializedSuperclass2Initialized); + assert(!_subclass2Initialized); + + [Subclass2 class]; + [Subclass2Subclass class]; // Make sure the subclass is initialized too. + assert(_alreadyInitializedSuperclassInitialized); + assert(_subclass2Initialized); + + Subclass2 *subclass2instance1 = class_createInstance(subclass2, 0); + assert([subclass2instance1 existsOnSubclass2]); + + class_setSuperclass(subclass2, secondSuperclass); + assert(_notInitializedSuperclass2Initialized); + + Subclass2 *subclass2instance2 = class_createInstance(subclass2, 0); + + // CLASS + // can call method on subclass + expect([subclass2instance1 existsOnSubclass2]); + // can call method on _new_ superclass + expect([subclass2instance1 existsOnNotInitializedSuperclass]); + // does not respond to selector from original superclass + expect(![subclass2 instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + // *does* respond to selector from new superclass + expect([subclass2 instancesRespondToSelector:@selector(existsOnNotInitializedSuperclass)]); + + // method existing on both old and new superclass kept, IMP updated + expect(3 == [subclass2instance1 sameName]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(6 == [subclass2instance1 overridden]); + // method existing only on subclass preserved + expect(100 == [subclass2instance1 intermediateOverride]); + + // METACLASS + // metaclass does not respond to selector from original meta superclass + expect(![subclass2 respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + // metaclass *does* respond to selector from new meta superclass + expect([subclass2 respondsToSelector:@selector(existsOnNotInitializedSuperclassMeta)]); + // method existing on both old and new superclass kept, IMP updated + expect(13 == [subclass2 sameNameMeta]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(16 == [subclass2 overriddenMeta]); + + // SUBCLASS + Subclass2 *subclass2subclassInstance = class_createInstance([Subclass2Subclass class], 0); + expect(![Subclass2Subclass instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + expect(![Subclass2Subclass respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + expect(3 == [subclass2subclassInstance sameName]); + expect(6 == [subclass2subclassInstance overridden]); + expect(200 == [subclass2subclassInstance intermediateOverride]); + expect(13 == [Subclass2Subclass sameNameMeta]); + expect(16 == [Subclass2Subclass overriddenMeta]); + } + + /* Transitioning ourselves to a new superclass while +initialize is running */ + { + expect(12 == [ChangesDuringInitialize sameNameMeta]); + expect(18 == [ChangesDuringInitialize overriddenMeta]); + } + + /* Transitioning to a superclass that's in our inheritance hierarchy already */ + { + assert(20 == [MovesUpwardsInHierarchy overriddenMeta]); + assert(19 == [MovesUpwardsInHierarchy sameNameMeta]); + assert([MovesUpwardsInHierarchy respondsToSelector:@selector(onlyExistsOnRemovedClassMeta)]); + assert([MovesUpwardsInHierarchy instancesRespondToSelector:@selector(onlyExistsOnRemovedClass)]); + + class_setSuperclass([MovesUpwardsInHierarchy class], [DefaultSuperclass class]); + + expect(20 == [MovesUpwardsInHierarchy overriddenMeta]); // still overridden + expect(14 == [MovesUpwardsInHierarchy sameNameMeta]); // falls back to DefaultSuperclass + expect(![MovesUpwardsInHierarchy respondsToSelector:@selector(onlyExistsOnRemovedClassMeta)]); + expect(![MovesUpwardsInHierarchy instancesRespondToSelector:@selector(onlyExistsOnRemovedClass)]); + } + + /* Transitioning to a superclass that may cause initialize lock contention */ + { + assert(!_otherInitializedClassInitialized); + expect(14 == [Subclass3 sameNameMeta]); + expect(14 == [Subclass3 overriddenMeta]); + + class_setSuperclass([Subclass3 class], objc_getClass("InitializesOneClassWhileBeingInitialized")); + + expect(_otherInitializedClassInitialized); + expect(13 == [Subclass3 sameNameMeta]); + expect(13 == [Subclass3 overriddenMeta]); + } + + return failures; +} diff --git a/class.h b/class.h index 8da94eb..e90db48 100644 --- a/class.h +++ b/class.h @@ -303,4 +303,15 @@ static inline Class classForObject(id obj) return obj->isa; } +static inline BOOL classIsOrInherits(Class cls, Class base) +{ + for (Class c = cls ; + Nil != c ; + c = c->super_class) + { + if (c == base) { return YES; } + } + return NO; +} + #endif //__OBJC_CLASS_H_INCLUDED diff --git a/dtable.c b/dtable.c index 4aa63e0..6f5f8a9 100644 --- a/dtable.c +++ b/dtable.c @@ -189,7 +189,7 @@ PRIVATE void init_dispatch_tables () Class class_getSuperclass(Class); -static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +PRIVATE dtable_t 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); } @@ -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) { @@ -639,7 +707,7 @@ PRIVATE void add_method_list_to_class(Class cls, checkARCAccessors(cls); } -static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +PRIVATE dtable_t 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); } 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 c7e7c62..926bd06 100644 --- a/runtime.c +++ b/runtime.c @@ -22,6 +22,10 @@ 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. */ @@ -519,8 +523,58 @@ Class class_setSuperclass(Class cls, Class newSuper) CHECK_ARG(cls); CHECK_ARG(newSuper); if (Nil == cls) { return Nil; } + + 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; + + // The super class's subclass list is used in certain method resolution scenarios. + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + + if (UNLIKELY(class_isMetaClass(cls))) + { + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; + } + else + { + 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; + } + + LOCK(&initialize_lock); + if (!objc_test_class_flag(cls, objc_class_flag_initialized)) + { + // 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((id)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; } @@ -790,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();