Improve ARC test and fix bugs it uncovered.

This cleans up handling of objects that are not reference counted and
makes their interactions with ARC more consistent.  We should probably
generalise this somewhat - it currently special cases NSConstantString
and NSGlobalBlock, but it would be nice to have an API for constant
objects.
main
David Chisnall 8 years ago
parent ca1d45a4e6
commit 0828b11125

@ -48,5 +48,8 @@ BOOL objc_create_block_classes_as_subclasses_of(Class super)
NEW_CLASS(&_NSBlock, _NSConcreteStackBlock); NEW_CLASS(&_NSBlock, _NSConcreteStackBlock);
NEW_CLASS(&_NSBlock, _NSConcreteGlobalBlock); NEW_CLASS(&_NSBlock, _NSConcreteGlobalBlock);
NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock); NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock);
// Global blocks never need refcount manipulation.
objc_set_class_flag(&_NSConcreteGlobalBlock,
objc_class_flag_permanent_instances);
return YES; return YES;
} }

@ -1,9 +1,32 @@
#import "Test.h" #import "Test.h"
// Checks using non-portable APIs
#ifdef GNUSTEP_RUNTIME
void check_retain_count(id obj, size_t rc)
{
assert(object_getRetainCount_np(obj) == rc);
}
void check_autorelease_count(id obj, size_t rc)
{
assert(objc_arc_autorelease_count_for_object_np(obj) == rc);
}
#else
void check_retain_count(id obj, size_t rc)
{
}
void check_autorelease_count(id obj, size_t rc)
{
}
#endif
id __weak var; id __weak var;
@interface ARC : Test @end @interface ARC : Test @end
@implementation ARC @implementation ARC
- (id __autoreleasing)loadWeakAutoreleasing
{
return var;
}
- (id)loadWeak - (id)loadWeak
{ {
return var; return var;
@ -11,6 +34,7 @@ id __weak var;
- (void)setWeakFromWeak: (id __weak)anObject - (void)setWeakFromWeak: (id __weak)anObject
{ {
var = anObject; var = anObject;
anObject = nil;
} }
- (void)setWeak: (id)anObject - (void)setWeak: (id)anObject
{ {
@ -18,18 +42,78 @@ id __weak var;
} }
@end @end
@interface CheckDealloc : Test
@end
@implementation CheckDealloc
{
BOOL *flag;
}
- (id)initWithFlag: (BOOL*)aFlag
{
flag = aFlag;
*flag = NO;
return self;
}
- (void)dealloc
{
*flag = YES;
}
@end
int main(void) int main(void)
{ {
ARC *obj = [ARC new]; ARC *obj = [ARC new];
BOOL f1;
BOOL f2;
// Check that storing weak references works.
{ {
id o1 = [Test new]; id o1 = [[CheckDealloc new] initWithFlag: &f1];
id o2 = [Test new]; id o2 = [[CheckDealloc new] initWithFlag: &f2];
[obj setWeak: o1]; [obj setWeak: o1];
assert([obj loadWeak] == o1); assert([obj loadWeak] == o1);
[obj setWeakFromWeak: o2]; [obj setWeakFromWeak: o2];
assert([obj loadWeak] == o2); assert([obj loadWeak] == o2);
@autoreleasepool
{
id __autoreleasing o2a = [obj loadWeakAutoreleasing];
assert(o2a == o2);
} }
}
assert(f1);
assert(f2);
assert([obj loadWeak] == nil); assert([obj loadWeak] == nil);
@autoreleasepool
{
id __autoreleasing o1a;
{
id o1 = [[CheckDealloc new] initWithFlag: &f1];
assert(!f1);
[obj setWeak: o1];
assert([obj loadWeak] == o1);
o1a = [obj loadWeakAutoreleasing];
assert(o1a == o1);
check_autorelease_count(o1a, 1);
check_retain_count(o1a, 1);
}
assert(o1a == [obj loadWeak]);
}
assert(f1);
assert([obj loadWeak] == nil);
// Try to trigger an objc_moveWeak call
{
id o1 = [Test new];
{
id __weak x = o1;
var = x;
}
}
assert([obj loadWeak] == nil);
// Now check what happens with a constant string in a weak variable.
{
id x = @"foo";
[obj setWeak: x];
}
assert([obj loadWeak] != nil);
return 0; return 0;
} }

43
arc.m

@ -258,9 +258,27 @@ id objc_retain_fast_np(id obj)
return obj; return obj;
} }
__attribute__((always_inline))
static inline BOOL isPersistentObject(id obj)
{
// No reference count manipulations on nil objects.
if (obj == nil)
{
return YES;
}
// Small objects are never accessibly by reference
if (isSmallObject(obj))
{
return YES;
}
// Persistent objects are persistent. Safe to access isa directly here
// because we've already handled the small object case separately.
return objc_test_class_flag(obj->isa, objc_class_flag_permanent_instances);
}
static inline id retain(id obj) static inline id retain(id obj)
{ {
if (isSmallObject(obj)) { return obj; } if (isPersistentObject(obj)) { return obj; }
Class cls = obj->isa; Class cls = obj->isa;
if ((Class)&_NSConcreteMallocBlock == cls || if ((Class)&_NSConcreteMallocBlock == cls ||
(Class)&_NSConcreteStackBlock == cls) (Class)&_NSConcreteStackBlock == cls)
@ -322,15 +340,14 @@ void objc_release_fast_np(id obj)
static inline void release(id obj) static inline void release(id obj)
{ {
if (isSmallObject(obj)) { return; } if (isPersistentObject(obj)) { return; }
Class cls = obj->isa; Class cls = obj->isa;
if (cls == &_NSConcreteMallocBlock) if (cls == &_NSConcreteMallocBlock)
{ {
_Block_release(obj); _Block_release(obj);
return; return;
} }
if ((cls == &_NSConcreteStackBlock) || if (cls == &_NSConcreteStackBlock)
(cls == &_NSConcreteGlobalBlock))
{ {
return; return;
} }
@ -691,18 +708,8 @@ id objc_storeWeak(id *addr, id obj)
{ {
return obj; return obj;
} }
BOOL isGlobalObject = (obj == nil) || isSmallObject(obj); BOOL isGlobalObject = isPersistentObject(obj);
Class cls = Nil; Class cls = isGlobalObject ? Nil : obj->isa;
if (!isGlobalObject)
{
cls = classForObject(obj);
// TODO: We probably also want to do the same for constant strings and
// classes.
if (cls == &_NSConcreteGlobalBlock)
{
isGlobalObject = YES;
}
}
if (obj && cls && objc_test_class_flag(cls, objc_class_flag_fast_arc)) if (obj && cls && objc_test_class_flag(cls, objc_class_flag_fast_arc))
{ {
uintptr_t *refCount = ((uintptr_t*)obj) - 1; uintptr_t *refCount = ((uintptr_t*)obj) - 1;
@ -839,6 +846,10 @@ id objc_loadWeakRetained(id* addr)
{ {
obj = block_load_weak(obj); obj = block_load_weak(obj);
} }
else if (objc_test_class_flag(cls, objc_class_flag_permanent_instances))
{
return obj;
}
else if (!objc_test_class_flag(cls, objc_class_flag_fast_arc)) else if (!objc_test_class_flag(cls, objc_class_flag_fast_arc))
{ {
obj = _objc_weak_load(obj); obj = _objc_weak_load(obj);

@ -238,7 +238,13 @@ enum objc_class_flags
/** /**
* This class is a hidden class used to store associated values. * This class is a hidden class used to store associated values.
*/ */
objc_class_flag_assoc_class = (1<<8) objc_class_flag_assoc_class = (1<<8),
/**
* This class has instances that are never deallocated and are therefore
* safe to store directly into weak variables and to skip all reference
* count manipulations.
*/
objc_class_flag_permanent_instances = (1<<14)
}; };
/** /**

@ -413,6 +413,12 @@ PRIVATE void objc_load_class(struct objc_class *class)
class->dtable = uninstalled_dtable; class->dtable = uninstalled_dtable;
class->isa->dtable = uninstalled_dtable; class->isa->dtable = uninstalled_dtable;
// Mark constant string instances as never needing refcount manipulation.
if (strcmp(class->name, "NSConstantString") == 0)
{
objc_set_class_flag(class, objc_class_flag_permanent_instances);
}
// If this is a root class, make the class into the metaclass's superclass. // If this is a root class, make the class into the metaclass's superclass.
// This means that all instance methods will be available to the class. // This means that all instance methods will be available to the class.
if (NULL == superclassName) if (NULL == superclassName)

@ -936,6 +936,12 @@ PRIVATE void objc_send_initialize(id object)
return; return;
} }
BOOL skipMeta = objc_test_class_flag(meta, objc_class_flag_initialized); BOOL skipMeta = objc_test_class_flag(meta, objc_class_flag_initialized);
// Mark metaclasses as never needing refcount manipulation for their
// instances (classes).
if (!skipMeta)
{
objc_set_class_flag(meta, objc_class_flag_permanent_instances);
}
// Set the initialized flag on both this class and its metaclass, to make // Set the initialized flag on both this class and its metaclass, to make
// sure that +initialize is only ever sent once. // sure that +initialize is only ever sent once.

Loading…
Cancel
Save