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, _NSConcreteGlobalBlock);
NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock);
// Global blocks never need refcount manipulation.
objc_set_class_flag(&_NSConcreteGlobalBlock,
objc_class_flag_permanent_instances);
return YES;
}

@ -1,9 +1,32 @@
#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;
@interface ARC : Test @end
@implementation ARC
- (id __autoreleasing)loadWeakAutoreleasing
{
return var;
}
- (id)loadWeak
{
return var;
@ -11,6 +34,7 @@ id __weak var;
- (void)setWeakFromWeak: (id __weak)anObject
{
var = anObject;
anObject = nil;
}
- (void)setWeak: (id)anObject
{
@ -18,18 +42,78 @@ id __weak var;
}
@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)
{
ARC *obj = [ARC new];
BOOL f1;
BOOL f2;
// Check that storing weak references works.
{
id o1 = [Test new];
id o2 = [Test new];
id o1 = [[CheckDealloc new] initWithFlag: &f1];
id o2 = [[CheckDealloc new] initWithFlag: &f2];
[obj setWeak: o1];
assert([obj loadWeak] == o1);
[obj setWeakFromWeak: o2];
assert([obj loadWeak] == o2);
@autoreleasepool
{
id __autoreleasing o2a = [obj loadWeakAutoreleasing];
assert(o2a == o2);
}
}
assert(f1);
assert(f2);
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;
}

43
arc.m

@ -258,9 +258,27 @@ id objc_retain_fast_np(id 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)
{
if (isSmallObject(obj)) { return obj; }
if (isPersistentObject(obj)) { return obj; }
Class cls = obj->isa;
if ((Class)&_NSConcreteMallocBlock == cls ||
(Class)&_NSConcreteStackBlock == cls)
@ -322,15 +340,14 @@ void objc_release_fast_np(id obj)
static inline void release(id obj)
{
if (isSmallObject(obj)) { return; }
if (isPersistentObject(obj)) { return; }
Class cls = obj->isa;
if (cls == &_NSConcreteMallocBlock)
{
_Block_release(obj);
return;
}
if ((cls == &_NSConcreteStackBlock) ||
(cls == &_NSConcreteGlobalBlock))
if (cls == &_NSConcreteStackBlock)
{
return;
}
@ -691,18 +708,8 @@ id objc_storeWeak(id *addr, id obj)
{
return obj;
}
BOOL isGlobalObject = (obj == nil) || isSmallObject(obj);
Class cls = Nil;
if (!isGlobalObject)
{
cls = classForObject(obj);
// TODO: We probably also want to do the same for constant strings and
// classes.
if (cls == &_NSConcreteGlobalBlock)
{
isGlobalObject = YES;
}
}
BOOL isGlobalObject = isPersistentObject(obj);
Class cls = isGlobalObject ? Nil : obj->isa;
if (obj && cls && objc_test_class_flag(cls, objc_class_flag_fast_arc))
{
uintptr_t *refCount = ((uintptr_t*)obj) - 1;
@ -839,6 +846,10 @@ id objc_loadWeakRetained(id* addr)
{
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))
{
obj = _objc_weak_load(obj);

@ -238,7 +238,13 @@ enum objc_class_flags
/**
* 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->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.
// This means that all instance methods will be available to the class.
if (NULL == superclassName)

@ -936,6 +936,12 @@ PRIVATE void objc_send_initialize(id object)
return;
}
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
// sure that +initialize is only ever sent once.

Loading…
Cancel
Save