Merge pull request #56 from Microsoft/cherry-pick-aa58f6f

teach class_setSuperclass about metaclasses, subclass lists, and dtables
main
davidchisnall 8 years ago committed by GitHub
commit c5b96fafe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,6 +34,7 @@ set(TESTS
zeroSizedIVar.m zeroSizedIVar.m
exchange.m exchange.m
hash_table_delete.c hash_table_delete.c
setSuperclass.m
) )
# Function for adding a test. This takes the name of the test and the list of # Function for adding a test. This takes the name of the test and the list of

@ -0,0 +1,293 @@
#include "../objc/runtime.h"
#include <assert.h>
#include <stdio.h>
#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;
}

@ -303,4 +303,15 @@ static inline Class classForObject(id obj)
return obj->isa; 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 #endif //__OBJC_CLASS_H_INCLUDED

@ -189,7 +189,7 @@ PRIVATE void init_dispatch_tables ()
Class class_getSuperclass(Class); 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 // Don't create a dtable for a class that already has one
if (classHasDtable(class)) { return dtable_for_class(class); } if (classHasDtable(class)) { return dtable_for_class(class); }
@ -360,6 +360,11 @@ PRIVATE void objc_update_dtable_for_class(Class cls)
update_dtable(dtable); 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, PRIVATE void add_method_list_to_class(Class cls,
struct objc_method_list *list) struct objc_method_list *list)
{ {
@ -622,6 +627,69 @@ PRIVATE void objc_update_dtable_for_class(Class cls)
checkARCAccessors(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, PRIVATE void add_method_list_to_class(Class cls,
struct objc_method_list *list) struct objc_method_list *list)
{ {
@ -639,7 +707,7 @@ PRIVATE void add_method_list_to_class(Class cls,
checkARCAccessors(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 // Don't create a dtable for a class that already has one
if (classHasDtable(class)) { return dtable_for_class(class); } if (classHasDtable(class)) { return dtable_for_class(class); }

@ -115,6 +115,11 @@ static inline int classHasDtable(struct objc_class *cls)
* modifying a class's method list. * modifying a class's method list.
*/ */
void objc_update_dtable_for_class(Class); 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, * Adds a single method list to a class. This is used when loading categories,
* and is faster than completely rebuilding the dtable. * and is faster than completely rebuilding the dtable.

@ -22,6 +22,10 @@
struct objc_slot *objc_get_slot(Class cls, SEL selector); struct objc_slot *objc_get_slot(Class cls, SEL selector);
#define CHECK_ARG(arg) if (0 == arg) { return 0; } #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. * Calls C++ destructors in the correct order.
*/ */
@ -519,8 +523,58 @@ Class class_setSuperclass(Class cls, Class newSuper)
CHECK_ARG(cls); CHECK_ARG(cls);
CHECK_ARG(newSuper); CHECK_ARG(newSuper);
if (Nil == cls) { return Nil; } 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; Class oldSuper = cls->super_class;
cls->super_class = newSuper; 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; return oldSuper;
} }
@ -790,8 +844,6 @@ const char *object_getClassName(id obj)
return class_getName(object_getClass(obj)); return class_getName(object_getClass(obj));
} }
PRIVATE void objc_resolve_class(Class);
void objc_registerClassPair(Class cls) void objc_registerClassPair(Class cls)
{ {
LOCK_RUNTIME_FOR_SCOPE(); LOCK_RUNTIME_FOR_SCOPE();

Loading…
Cancel
Save