From 14cb89350900af2236ce8ff20907d5a5b9e897d2 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 16:48:31 -0800 Subject: [PATCH] Add a test for class_setSuperclass --- Test/CMakeLists.txt | 1 + Test/setSuperclass.m | 293 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 Test/setSuperclass.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 7db5183..a1332d7 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -33,6 +33,7 @@ set(TESTS MethodArguments.m zeroSizedIVar.m exchange.m + 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; +}