ignoredSelector.m   [plain text]


#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 (objc_collecting_enabled()) {
        // GC: all ignored selectors are identical
        testassert(@selector(retain) == @selector(release)      &&  
                   @selector(retain) == @selector(autorelease)  &&  
                   @selector(retain) == @selector(dealloc)      &&  
                   @selector(retain) == @selector(retainCount)  );
    }
    else {
        // 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_collecting_enabled()) {
        // 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_collecting_enabled() ? 0 : 2));
    testassert(idVal == cls);

    idVal =         ((id(*)(id, Method))method_invoke)(cls, releaseMethod);
    testassert(state == (objc_collecting_enabled() ? 0 : 3));
    testassert(idVal == cls);

    idVal =         ((id(*)(id, Method))method_invoke)(cls, autoreleaseMethod);
    testassert(state == (objc_collecting_enabled() ? 0 : 4));
    testassert(idVal == cls);

    (void)        ((void(*)(id, Method))method_invoke)(cls, deallocMethod);
    testassert(state == (objc_collecting_enabled() ? 0 : 5));

    intVal = ((uintptr_t(*)(id, Method))method_invoke)(cls, retainCountMethod);
    testassert(state == (objc_collecting_enabled() ? 0 : 6));
    testassert(intVal == (objc_collecting_enabled() ? (uintptr_t)cls : 6));


    // Test calls via objc_msgSend

    state = 0;
    idVal  = [cls normal];
    testassert(state == 1);
    testassert(idVal == cls);

    state = 0;
    idVal  = [cls retain];
    testassert(state == (objc_collecting_enabled() ? 0 : 2));
    testassert(idVal == cls);

    idVal  = [cls release];
    testassert(state == (objc_collecting_enabled() ? 0 : 3));
    testassert(idVal == cls);

    idVal  = [cls autorelease];
    testassert(state == (objc_collecting_enabled() ? 0 : 4));
    testassert(idVal == cls);

    (void)   [cls dealloc];
    testassert(state == (objc_collecting_enabled() ? 0 : 5));

    intVal = [cls retainCount];
    testassert(state == (objc_collecting_enabled() ? 0 : 6));
    testassert(intVal == (objc_collecting_enabled() ? (uintptr_t)cls : 6));
}

int main()
{
    Class cls;

    // Test selector API

    testassert(sel_registerName("retain") == @selector(retain));
    testassert(sel_getUid("retain") == @selector(retain));
    if (objc_collecting_enabled()) {
        testassert(0 == strcmp(sel_getName(@selector(retain)), "<ignored selector>"));
    } else {
        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_collecting_enabled()) {
        // 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_collecting_enabled()) {
        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);
    }    

    succeed(__FILE__);
}