// TEST_CFLAGS -Wno-deprecated-declarations
#include "test.h"
#include <objc/runtime.h>
#include <objc/message.h>
#include <objc/objc-auto.h>
static int state = 0;
@interface Super { id isa; } @end
@implementation Super
+class { return self; }
+(void)initialize { }
+normal { state = 1; return self; }
+normal2 { testassert(0); }
+retain { state = 2; return self; }
+release { state = 3; return self; }
+autorelease { state = 4; return self; }
+(void)dealloc { state = 5; }
+(uintptr_t)retainCount { state = 6; return 6; }
@end
@interface Sub : Super @end
@implementation Sub @end
@interface Sub2 : Super @end
@implementation Sub2 @end
@interface Empty { id isa; } @end
@implementation Empty
+class { return self; }
+(void)initialize { }
+forward:(SEL)sel :(marg_list)margs {
(void)sel; (void)margs;
state = 1;
return nil;
}
@end
@interface Empty (Unimplemented)
+normal;
+retain;
+release;
+autorelease;
+(void)dealloc;
+(uintptr_t)retainCount;
@end
#define getImp(sel) \
do { \
sel##Method = class_getClassMethod(cls, @selector(sel)); \
testassert(sel##Method); \
testassert(@selector(sel) == method_getName(sel##Method)); \
sel = method_getImplementation(sel##Method); \
} while (0)
static IMP normal, normal2, retain, release, autorelease, dealloc, retainCount;
static Method normalMethod, normal2Method, retainMethod, releaseMethod, autoreleaseMethod, deallocMethod, retainCountMethod;
void cycle(Class cls)
{
id idVal;
uintptr_t intVal;
#if defined(__i386__)
if (objc_collectingEnabled()) {
// i386 GC: all ignored selectors are identical
testassert(@selector(retain) == @selector(release) &&
@selector(retain) == @selector(autorelease) &&
@selector(retain) == @selector(dealloc) &&
@selector(retain) == @selector(retainCount) );
}
else
#endif
{
// x86_64 GC or no GC: all ignored selectors are distinct
testassert(@selector(retain) != @selector(release) &&
@selector(retain) != @selector(autorelease) &&
@selector(retain) != @selector(dealloc) &&
@selector(retain) != @selector(retainCount) );
}
// no ignored selector matches a real selector
testassert(@selector(normal) != @selector(retain) &&
@selector(normal) != @selector(release) &&
@selector(normal) != @selector(autorelease) &&
@selector(normal) != @selector(dealloc) &&
@selector(normal) != @selector(retainCount) );
getImp(normal);
getImp(normal2);
getImp(retain);
getImp(release);
getImp(autorelease);
getImp(dealloc);
getImp(retainCount);
if (objc_collectingEnabled()) {
// GC: all ignored selector IMPs are identical
testassert(retain == release &&
retain == autorelease &&
retain == dealloc &&
retain == retainCount );
}
else {
// no GC: all ignored selector IMPs are distinct
testassert(retain != release &&
retain != autorelease &&
retain != dealloc &&
retain != retainCount );
}
// no ignored selector IMP matches a real selector IMP
testassert(normal != retain &&
normal != release &&
normal != autorelease &&
normal != dealloc &&
normal != retainCount );
// Test calls via method_invoke
idVal = ((id(*)(id, Method))method_invoke)(cls, normalMethod);
testassert(state == 1);
testassert(idVal == cls);
state = 0;
idVal = ((id(*)(id, Method))method_invoke)(cls, retainMethod);
testassert(state == (objc_collectingEnabled() ? 0 : 2));
testassert(idVal == cls);
idVal = ((id(*)(id, Method))method_invoke)(cls, releaseMethod);
testassert(state == (objc_collectingEnabled() ? 0 : 3));
testassert(idVal == cls);
idVal = ((id(*)(id, Method))method_invoke)(cls, autoreleaseMethod);
testassert(state == (objc_collectingEnabled() ? 0 : 4));
testassert(idVal == cls);
(void) ((void(*)(id, Method))method_invoke)(cls, deallocMethod);
testassert(state == (objc_collectingEnabled() ? 0 : 5));
intVal = ((uintptr_t(*)(id, Method))method_invoke)(cls, retainCountMethod);
testassert(state == (objc_collectingEnabled() ? 0 : 6));
testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6));
// Test calls via compiled objc_msgSend
state = 0;
idVal = [cls normal];
testassert(state == 1);
testassert(idVal == cls);
state = 0;
idVal = [cls retain];
testassert(state == (objc_collectingEnabled() ? 0 : 2));
testassert(idVal == cls);
idVal = [cls release];
testassert(state == (objc_collectingEnabled() ? 0 : 3));
testassert(idVal == cls);
idVal = [cls autorelease];
testassert(state == (objc_collectingEnabled() ? 0 : 4));
testassert(idVal == cls);
(void) [cls dealloc];
testassert(state == (objc_collectingEnabled() ? 0 : 5));
intVal = [cls retainCount];
testassert(state == (objc_collectingEnabled() ? 0 : 6));
testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6));
// Test calls via handwritten objc_msgSend
state = 0;
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(normal));
testassert(state == 1);
testassert(idVal == cls);
state = 0;
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(retain));
testassert(state == (objc_collectingEnabled() ? 0 : 2));
testassert(idVal == cls);
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(release));
testassert(state == (objc_collectingEnabled() ? 0 : 3));
testassert(idVal == cls);
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(autorelease));
testassert(state == (objc_collectingEnabled() ? 0 : 4));
testassert(idVal == cls);
(void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(dealloc));
testassert(state == (objc_collectingEnabled() ? 0 : 5));
intVal = ((uintptr_t(*)(id,SEL))objc_msgSend)(cls, @selector(retainCount));
testassert(state == (objc_collectingEnabled() ? 0 : 6));
testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6));
}
int main()
{
Class cls;
// Test selector API
testassert(sel_registerName("retain") == @selector(retain));
testassert(sel_getUid("retain") == @selector(retain));
#if defined(__i386__)
if (objc_collectingEnabled()) {
// only i386's GC currently remaps these
testassert(0 == strcmp(sel_getName(@selector(retain)), "<ignored selector>"));
} else
#endif
{
testassert(0 == strcmp(sel_getName(@selector(retain)), "retain"));
}
#if !__OBJC2__
testassert(sel_isMapped(@selector(retain)));
#endif
cls = [Sub class];
testassert(cls);
cycle(cls);
cls = [Super class];
testassert(cls);
cycle(cls);
if (objc_collectingEnabled()) {
// rdar://6200570 Method manipulation shouldn't affect ignored methods.
cls = [Super class];
testassert(cls);
cycle(cls);
method_setImplementation(retainMethod, (IMP)1);
method_setImplementation(releaseMethod, (IMP)1);
method_setImplementation(autoreleaseMethod, (IMP)1);
method_setImplementation(deallocMethod, (IMP)1);
method_setImplementation(retainCountMethod, (IMP)1);
cycle(cls);
testassert(normal2 != dealloc);
method_exchangeImplementations(retainMethod, releaseMethod);
method_exchangeImplementations(autoreleaseMethod, retainCountMethod);
method_exchangeImplementations(deallocMethod, normal2Method);
cycle(cls);
// normal2 exchanged with ignored method is now ignored too
testassert(normal2 == dealloc);
// replace == replace existing
class_replaceMethod(cls, @selector(retain), (IMP)1, "");
class_replaceMethod(cls, @selector(release), (IMP)1, "");
class_replaceMethod(cls, @selector(autorelease), (IMP)1, "");
class_replaceMethod(cls, @selector(dealloc), (IMP)1, "");
class_replaceMethod(cls, @selector(retainCount), (IMP)1, "");
cycle(cls);
cls = [Sub class];
testassert(cls);
cycle(cls);
// replace == add override
class_replaceMethod(cls, @selector(retain), (IMP)1, "");
class_replaceMethod(cls, @selector(release), (IMP)1, "");
class_replaceMethod(cls, @selector(autorelease), (IMP)1, "");
class_replaceMethod(cls, @selector(dealloc), (IMP)1, "");
class_replaceMethod(cls, @selector(retainCount), (IMP)1, "");
cycle(cls);
cls = [Sub2 class];
testassert(cls);
cycle(cls);
class_addMethod(cls, @selector(retain), (IMP)1, "");
class_addMethod(cls, @selector(release), (IMP)1, "");
class_addMethod(cls, @selector(autorelease), (IMP)1, "");
class_addMethod(cls, @selector(dealloc), (IMP)1, "");
class_addMethod(cls, @selector(retainCount), (IMP)1, "");
cycle(cls);
}
// Test calls via objc_msgSend - ignored selectors are ignored
// under GC even if the class provides no implementation for them
if (objc_collectingEnabled()) {
Class cls;
id idVal;
uintptr_t intVal;
cls = [Empty class];
state = 0;
idVal = [Empty retain];
testassert(state == 0);
testassert(idVal == cls);
idVal = [Empty release];
testassert(state == 0);
testassert(idVal == cls);
idVal = [Empty autorelease];
testassert(state == 0);
testassert(idVal == cls);
(void) [Empty dealloc];
testassert(state == 0);
intVal = [Empty retainCount];
testassert(state == 0);
testassert(intVal == (uintptr_t)cls);
idVal = [Empty normal];
testassert(state == 1);
testassert(idVal == nil);
state = 0;
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(retain));
testassert(state == 0);
testassert(idVal == cls);
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(release));
testassert(state == 0);
testassert(idVal == cls);
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(autorelease));
testassert(state == 0);
testassert(idVal == cls);
(void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(dealloc));
testassert(state == 0);
intVal = ((uintptr_t(*)(id,SEL))objc_msgSend)(cls, @selector(retainCount));
testassert(state == 0);
testassert(intVal == (uintptr_t)cls);
idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(normal));
testassert(state == 1);
testassert(idVal == nil);
}
succeed(__FILE__);
}