// These options must match customrr2.m
// TEST_CONFIG MEM=mrc
/*
TEST_BUILD
    $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr.exe -fno-objc-convert-messages-to-runtime-calls
    $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat1.m -o customrr-cat1.bundle
    $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat2.m -o customrr-cat2.bundle
END
*/


#include "test.h"
#include <dlfcn.h>
#include <Foundation/NSObject.h>

static int Retains;
static int Releases;
static int Autoreleases;
static int RetainCounts;
static int PlusRetains;
static int PlusReleases;
static int PlusAutoreleases;
static int PlusRetainCounts;
static int Allocs;
static int AllocWithZones;

static int SubRetains;
static int SubReleases;
static int SubAutoreleases;
static int SubRetainCounts;
static int SubPlusRetains;
static int SubPlusReleases;
static int SubPlusAutoreleases;
static int SubPlusRetainCounts;
static int SubAllocs;
static int SubAllocWithZones;

static int Imps;

static id imp_fn(id self, SEL _cmd __unused, ...)
{
    Imps++;
    return self;
}

static void zero(void) {
    Retains = 0;
    Releases = 0;
    Autoreleases = 0;
    RetainCounts = 0;
    PlusRetains = 0;
    PlusReleases = 0;
    PlusAutoreleases = 0;
    PlusRetainCounts = 0;
    Allocs = 0;
    AllocWithZones = 0;

    SubRetains = 0;
    SubReleases = 0;
    SubAutoreleases = 0;
    SubRetainCounts = 0;
    SubPlusRetains = 0;
    SubPlusReleases = 0;
    SubPlusAutoreleases = 0;
    SubPlusRetainCounts = 0;
    SubAllocs = 0;
    SubAllocWithZones = 0;

    Imps = 0;
}


id HackRetain(id self, SEL _cmd __unused) { Retains++; return self; }
void HackRelease(id self __unused, SEL _cmd __unused) { Releases++; }
id HackAutorelease(id self, SEL _cmd __unused) { Autoreleases++; return self; }
NSUInteger HackRetainCount(id self __unused, SEL _cmd __unused) { RetainCounts++; return 1; }
id HackPlusRetain(id self, SEL _cmd __unused) { PlusRetains++; return self; }
void HackPlusRelease(id self __unused, SEL _cmd __unused) { PlusReleases++; }
id HackPlusAutorelease(id self, SEL _cmd __unused) { PlusAutoreleases++; return self; }
NSUInteger HackPlusRetainCount(id self __unused, SEL _cmd __unused) { PlusRetainCounts++; return 1; }
id HackAlloc(Class self, SEL _cmd __unused) { Allocs++; return class_createInstance(self, 0); }
id HackAllocWithZone(Class self, SEL _cmd __unused) { AllocWithZones++; return class_createInstance(self, 0); }


@interface OverridingSub : NSObject @end
@implementation OverridingSub 

-(id) retain { SubRetains++; return self; }
+(id) retain { SubPlusRetains++; return self; }
-(oneway void) release { SubReleases++; }
+(oneway void) release { SubPlusReleases++; }
-(id) autorelease { SubAutoreleases++; return self; }
+(id) autorelease { SubPlusAutoreleases++; return self; }
-(NSUInteger) retainCount { SubRetainCounts++; return 1; }
+(NSUInteger) retainCount { SubPlusRetainCounts++; return 1; }

@end

@interface OverridingASub : NSObject @end
@implementation OverridingASub
+(id) alloc { SubAllocs++; return class_createInstance(self, 0); }
@end

@interface OverridingAWZSub : NSObject @end
@implementation OverridingAWZSub
+(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); }
@end

@interface OverridingAAWZSub : NSObject @end
@implementation OverridingAAWZSub
+(id) alloc { SubAllocs++; return class_createInstance(self, 0); }
+(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); }
@end


@interface InheritingSub : NSObject @end
@implementation InheritingSub @end

@interface InheritingSub2 : NSObject @end
@implementation InheritingSub2 @end
@interface InheritingSub2_2 : InheritingSub2 @end
@implementation InheritingSub2_2 @end

@interface InheritingSub3 : NSObject @end
@implementation InheritingSub3 @end
@interface InheritingSub3_2 : InheritingSub3 @end
@implementation InheritingSub3_2 @end

@interface InheritingSub4 : NSObject @end
@implementation InheritingSub4 @end
@interface InheritingSub4_2 : InheritingSub4 @end
@implementation InheritingSub4_2 @end

@interface InheritingSub5 : NSObject @end
@implementation InheritingSub5 @end
@interface InheritingSub5_2 : InheritingSub5 @end
@implementation InheritingSub5_2 @end

@interface InheritingSub6 : NSObject @end
@implementation InheritingSub6 @end
@interface InheritingSub6_2 : InheritingSub6 @end
@implementation InheritingSub6_2 @end

@interface InheritingSub7 : NSObject @end
@implementation InheritingSub7 @end
@interface InheritingSub7_2 : InheritingSub7 @end
@implementation InheritingSub7_2 @end

@interface InheritingSubCat : NSObject @end
@implementation InheritingSubCat @end
@interface InheritingSubCat_2 : InheritingSubCat @end
@implementation InheritingSubCat_2 @end


extern uintptr_t OBJC_CLASS_$_UnrealizedSubA1;
@interface UnrealizedSubA1 : NSObject @end
@implementation UnrealizedSubA1 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubA2;
@interface UnrealizedSubA2 : NSObject @end
@implementation UnrealizedSubA2 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubA3;
@interface UnrealizedSubA3 : NSObject @end
@implementation UnrealizedSubA3 @end

extern uintptr_t OBJC_CLASS_$_UnrealizedSubB1;
@interface UnrealizedSubB1 : NSObject @end
@implementation UnrealizedSubB1 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubB2;
@interface UnrealizedSubB2 : NSObject @end
@implementation UnrealizedSubB2 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubB3;
@interface UnrealizedSubB3 : NSObject @end
@implementation UnrealizedSubB3 @end

extern uintptr_t OBJC_CLASS_$_UnrealizedSubC1;
@interface UnrealizedSubC1 : NSObject @end
@implementation UnrealizedSubC1 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubC2;
@interface UnrealizedSubC2 : NSObject @end
@implementation UnrealizedSubC2 @end
extern uintptr_t OBJC_CLASS_$_UnrealizedSubC3;
@interface UnrealizedSubC3 : NSObject @end
@implementation UnrealizedSubC3 @end


int main(int argc __unused, char **argv)
{
    objc_autoreleasePoolPush();

    // Hack NSObject's RR methods.
    // Don't use runtime functions to do this - 
    // we want the runtime to think that these are NSObject's real code
    {
#if __has_feature(ptrauth_calls)
        typedef IMP __ptrauth_objc_method_list_imp MethodListIMP;
#else
        typedef IMP MethodListIMP;
#endif

        Class cls = [NSObject class];
        IMP imp = class_getMethodImplementation(cls, @selector(retain));
        MethodListIMP *m = (MethodListIMP *)
            class_getInstanceMethod(cls, @selector(retain));
        testassert(m[2] == imp);  // verify Method struct is as we expect

        m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retain));
        m[2] = (IMP)HackRetain;
        m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(release));
        m[2] = (IMP)HackRelease;
        m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(autorelease));
        m[2] = (IMP)HackAutorelease;
        m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retainCount));
        m[2] = (IMP)HackRetainCount;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(retain));
        m[2] = (IMP)HackPlusRetain;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(release));
        m[2] = (IMP)HackPlusRelease;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(autorelease));
        m[2] = (IMP)HackPlusAutorelease;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(retainCount));
        m[2] = (IMP)HackPlusRetainCount;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(alloc));
        m[2] = (IMP)HackAlloc;
        m = (MethodListIMP *)class_getClassMethod(cls, @selector(allocWithZone:));
        m[2] = (IMP)HackAllocWithZone;

        _objc_flush_caches(cls);

        imp = class_getMethodImplementation(cls, @selector(retain));
        testassert(imp == (IMP)HackRetain);  // verify hack worked
    }

    Class cls = [NSObject class];
    Class icl = [InheritingSub class];
    Class ocl = [OverridingSub class];
    /*
    Class oa1 = [OverridingASub class];
    Class oa2 = [OverridingAWZSub class];
    Class oa3 = [OverridingAAWZSub class];
    */
    NSObject *obj = [NSObject new];
    InheritingSub *inh = [InheritingSub new];
    OverridingSub *ovr = [OverridingSub new];

    Class ccc;
    id ooo;
    Class cc2;
    id oo2;

    void *dlh;


#if __x86_64__
    // vtable dispatch can introduce bypass just like the ARC entrypoints
#else
    testprintf("method dispatch does not bypass\n");
    zero();
    
    [obj retain];
    testassert(Retains == 1);
    [obj release];
    testassert(Releases == 1);
    [obj autorelease];
    testassert(Autoreleases == 1);

    [cls retain];
    testassert(PlusRetains == 1);
    [cls release];
    testassert(PlusReleases == 1);
    [cls autorelease];
    testassert(PlusAutoreleases == 1);

    [inh retain];
    testassert(Retains == 2);
    [inh release];
    testassert(Releases == 2);
    [inh autorelease];
    testassert(Autoreleases == 2);

    [icl retain];
    testassert(PlusRetains == 2);
    [icl release];
    testassert(PlusReleases == 2);
    [icl autorelease];
    testassert(PlusAutoreleases == 2);
    
    [ovr retain];
    testassert(SubRetains == 1);
    [ovr release];
    testassert(SubReleases == 1);
    [ovr autorelease];
    testassert(SubAutoreleases == 1);

    [ocl retain];
    testassert(SubPlusRetains == 1);
    [ocl release];
    testassert(SubPlusReleases == 1);
    [ocl autorelease];
    testassert(SubPlusAutoreleases == 1);

    [UnrealizedSubA1 retain];
    testassert(PlusRetains == 3);
    [UnrealizedSubA2 release];
    testassert(PlusReleases == 3);
    [UnrealizedSubA3 autorelease];
    testassert(PlusAutoreleases == 3);
#endif


    testprintf("objc_msgSend() does not bypass\n");
    zero();

    id (*retain_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend;
    void (*release_fn)(id, SEL) = (void(*)(id, SEL))objc_msgSend;
    id (*autorelease_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend;

    retain_fn(obj, @selector(retain));
    testassert(Retains == 1);
    release_fn(obj, @selector(release));
    testassert(Releases == 1);
    autorelease_fn(obj, @selector(autorelease));
    testassert(Autoreleases == 1);

    retain_fn(cls, @selector(retain));
    testassert(PlusRetains == 1);
    release_fn(cls, @selector(release));
    testassert(PlusReleases == 1);
    autorelease_fn(cls, @selector(autorelease));
    testassert(PlusAutoreleases == 1);

    retain_fn(inh, @selector(retain));
    testassert(Retains == 2);
    release_fn(inh, @selector(release));
    testassert(Releases == 2);
    autorelease_fn(inh, @selector(autorelease));
    testassert(Autoreleases == 2);

    retain_fn(icl, @selector(retain));
    testassert(PlusRetains == 2);
    release_fn(icl, @selector(release));
    testassert(PlusReleases == 2);
    autorelease_fn(icl, @selector(autorelease));
    testassert(PlusAutoreleases == 2);
    
    retain_fn(ovr, @selector(retain));
    testassert(SubRetains == 1);
    release_fn(ovr, @selector(release));
    testassert(SubReleases == 1);
    autorelease_fn(ovr, @selector(autorelease));
    testassert(SubAutoreleases == 1);

    retain_fn(ocl, @selector(retain));
    testassert(SubPlusRetains == 1);
    release_fn(ocl, @selector(release));
    testassert(SubPlusReleases == 1);
    autorelease_fn(ocl, @selector(autorelease));
    testassert(SubPlusAutoreleases == 1);

    retain_fn((Class)&OBJC_CLASS_$_UnrealizedSubB1, @selector(retain));
    testassert(PlusRetains == 3);
    release_fn((Class)&OBJC_CLASS_$_UnrealizedSubB2, @selector(release));
    testassert(PlusReleases == 3);
    autorelease_fn((Class)&OBJC_CLASS_$_UnrealizedSubB3, @selector(autorelease));
    testassert(PlusAutoreleases == 3);


    testprintf("arc function bypasses instance but not class or override\n");
    zero();
    
    objc_retain(obj);
    testassert(Retains == 0);
    objc_release(obj);
    testassert(Releases == 0);
    objc_autorelease(obj);
    testassert(Autoreleases == 0);

#if SUPPORT_NONPOINTER_ISA
    objc_retain(cls);
    testassert(PlusRetains == 0);
    objc_release(cls);
    testassert(PlusReleases == 0);
    objc_autorelease(cls);
    testassert(PlusAutoreleases == 0);
#else
    objc_retain(cls);
    testassert(PlusRetains == 1);
    objc_release(cls);
    testassert(PlusReleases == 1);
    objc_autorelease(cls);
    testassert(PlusAutoreleases == 1);
#endif

    objc_retain(inh);
    testassert(Retains == 0);
    objc_release(inh);
    testassert(Releases == 0);
    objc_autorelease(inh);
    testassert(Autoreleases == 0);

#if SUPPORT_NONPOINTER_ISA
    objc_retain(icl);
    testassert(PlusRetains == 0);
    objc_release(icl);
    testassert(PlusReleases == 0);
    objc_autorelease(icl);
    testassert(PlusAutoreleases == 0);
#else
    objc_retain(icl);
    testassert(PlusRetains == 2);
    objc_release(icl);
    testassert(PlusReleases == 2);
    objc_autorelease(icl);
    testassert(PlusAutoreleases == 2);
#endif
    
    objc_retain(ovr);
    testassert(SubRetains == 1);
    objc_release(ovr);
    testassert(SubReleases == 1);
    objc_autorelease(ovr);
    testassert(SubAutoreleases == 1);

    objc_retain(ocl);
    testassert(SubPlusRetains == 1);
    objc_release(ocl);
    testassert(SubPlusReleases == 1);
    objc_autorelease(ocl);
    testassert(SubPlusAutoreleases == 1);

#if SUPPORT_NONPOINTER_ISA
    objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1);
    testassert(PlusRetains == 1);
    objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2);
    testassert(PlusReleases == 1);
    objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3);
    testassert(PlusAutoreleases == 1);
#else
    objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1);
    testassert(PlusRetains == 3);
    objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2);
    testassert(PlusReleases == 3);
    objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3);
    testassert(PlusAutoreleases == 3);
#endif

    testprintf("unrelated addMethod does not clobber\n");
    zero();

    class_addMethod(cls, @selector(unrelatedMethod), (IMP)imp_fn, "");
    
    objc_retain(obj);
    testassert(Retains == 0);
    objc_release(obj);
    testassert(Releases == 0);
    objc_autorelease(obj);
    testassert(Autoreleases == 0);


    testprintf("add class method does not clobber\n");
    zero();
    
    objc_retain(obj);
    testassert(Retains == 0);
    objc_release(obj);
    testassert(Releases == 0);
    objc_autorelease(obj);
    testassert(Autoreleases == 0);

    class_addMethod(object_getClass(cls), @selector(retain), (IMP)imp_fn, "");
    
    objc_retain(obj);
    testassert(Retains == 0);
    objc_release(obj);
    testassert(Releases == 0);
    objc_autorelease(obj);
    testassert(Autoreleases == 0);


    testprintf("addMethod clobbers (InheritingSub2, retain)\n");
    zero();

    ccc = [InheritingSub2 class];
    ooo = [ccc new];
    cc2 = [InheritingSub2_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_addMethod(ccc, @selector(retain), (IMP)imp_fn, "");

    objc_retain(ooo);
    testassert(Retains == 0);
    testassert(Imps == 1);
    objc_release(ooo);
    testassert(Releases == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 1);

    objc_retain(oo2);
    testassert(Retains == 0);
    testassert(Imps == 2);
    objc_release(oo2);
    testassert(Releases == 2);
    objc_autorelease(oo2);
    testassert(Autoreleases == 2);


    testprintf("addMethod clobbers (InheritingSub3, release)\n");
    zero();

    ccc = [InheritingSub3 class];
    ooo = [ccc new];
    cc2 = [InheritingSub3_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    
    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_addMethod(ccc, @selector(release), (IMP)imp_fn, "");

    objc_retain(ooo);
    testassert(Retains == 1);
    objc_release(ooo);
    testassert(Releases == 0);
    testassert(Imps == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 1);

    objc_retain(oo2);
    testassert(Retains == 2);
    objc_release(oo2);
    testassert(Releases == 0);
    testassert(Imps == 2);
    objc_autorelease(oo2);
    testassert(Autoreleases == 2);


    testprintf("addMethod clobbers (InheritingSub4, autorelease)\n");
    zero();

    ccc = [InheritingSub4 class];
    ooo = [ccc new];
    cc2 = [InheritingSub4_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    
    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, "");

    objc_retain(ooo);
    testassert(Retains == 1);
    objc_release(ooo);
    testassert(Releases == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    testassert(Imps == 1);

    objc_retain(oo2);
    testassert(Retains == 2);
    objc_release(oo2);
    testassert(Releases == 2);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);
    testassert(Imps == 2);


    testprintf("addMethod clobbers (InheritingSub5, retainCount)\n");
    zero();

    ccc = [InheritingSub5 class];
    ooo = [ccc new];
    cc2 = [InheritingSub5_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_addMethod(ccc, @selector(retainCount), (IMP)imp_fn, "");

    objc_retain(ooo);
    testassert(Retains == 1);
    objc_release(ooo);
    testassert(Releases == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 1);
    // no bypassing call for -retainCount

    objc_retain(oo2);
    testassert(Retains == 2);
    objc_release(oo2);
    testassert(Releases == 2);
    objc_autorelease(oo2);
    testassert(Autoreleases == 2);
    // no bypassing call for -retainCount


    testprintf("setSuperclass to clean super does not clobber (InheritingSub6)\n");
    zero();

    ccc = [InheritingSub6 class];
    ooo = [ccc new];
    cc2 = [InheritingSub6_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_setSuperclass(ccc, [InheritingSub class]);
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    
    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);


    testprintf("setSuperclass to dirty super clobbers (InheritingSub7)\n");
    zero();

    ccc = [InheritingSub7 class];
    ooo = [ccc new];
    cc2 = [InheritingSub7_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    
    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    class_setSuperclass(ccc, [OverridingSub class]);

    objc_retain(ooo);
    testassert(SubRetains == 1);
    objc_release(ooo);
    testassert(SubReleases == 1);
    objc_autorelease(ooo);
    testassert(SubAutoreleases == 1);

    objc_retain(oo2);
    testassert(SubRetains == 2);
    objc_release(oo2);
    testassert(SubReleases == 2);
    objc_autorelease(oo2);
    testassert(SubAutoreleases == 2);


    testprintf("category replacement of unrelated method does not clobber (InheritingSubCat)\n");
    zero();

    ccc = [InheritingSubCat class];
    ooo = [ccc new];
    cc2 = [InheritingSubCat_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    dlh = dlopen("customrr-cat1.bundle", RTLD_LAZY);
    testassert(dlh);

    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);


    testprintf("category replacement clobbers (InheritingSubCat)\n");
    zero();

    ccc = [InheritingSubCat class];
    ooo = [ccc new];
    cc2 = [InheritingSubCat_2 class];
    oo2 = [cc2 new];
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);

    objc_retain(oo2);
    testassert(Retains == 0);
    objc_release(oo2);
    testassert(Releases == 0);
    objc_autorelease(oo2);
    testassert(Autoreleases == 0);

    dlh = dlopen("customrr-cat2.bundle", RTLD_LAZY);
    testassert(dlh);

    objc_retain(ooo);
    testassert(Retains == 1);
    objc_release(ooo);
    testassert(Releases == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 1);

    objc_retain(oo2);
    testassert(Retains == 2);
    objc_release(oo2);
    testassert(Releases == 2);
    objc_autorelease(oo2);
    testassert(Autoreleases == 2);


    testprintf("allocateClassPair with clean super does not clobber\n");
    zero();

    objc_retain(inh);
    testassert(Retains == 0);
    objc_release(inh);
    testassert(Releases == 0);
    objc_autorelease(inh);
    testassert(Autoreleases == 0);

    ccc = objc_allocateClassPair([InheritingSub class], "CleanClassPair", 0);
    objc_registerClassPair(ccc);
    ooo = [ccc new];

    objc_retain(inh);
    testassert(Retains == 0);
    objc_release(inh);
    testassert(Releases == 0);
    objc_autorelease(inh);
    testassert(Autoreleases == 0);
    
    objc_retain(ooo);
    testassert(Retains == 0);
    objc_release(ooo);
    testassert(Releases == 0);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);


    testprintf("allocateClassPair with clobbered super clobbers\n");
    zero();

    ccc = objc_allocateClassPair([OverridingSub class], "DirtyClassPair", 0);
    objc_registerClassPair(ccc);
    ooo = [ccc new];
    
    objc_retain(ooo);
    testassert(SubRetains == 1);
    objc_release(ooo);
    testassert(SubReleases == 1);
    objc_autorelease(ooo);
    testassert(SubAutoreleases == 1);


    testprintf("allocateClassPair with clean super and override clobbers\n");
    zero();

    ccc = objc_allocateClassPair([InheritingSub class], "Dirty2ClassPair", 0);
    class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, "");
    objc_registerClassPair(ccc);
    ooo = [ccc new];
    
    objc_retain(ooo);
    testassert(Retains == 1);
    objc_release(ooo);
    testassert(Releases == 1);
    objc_autorelease(ooo);
    testassert(Autoreleases == 0);
    testassert(Imps == 1);


    // method_setImplementation and method_exchangeImplementations only 
    // clobber when manipulating NSObject. We can only test one at a time.
    // To test both, we need two tests: customrr and customrr2.

    // These tests also check recursive clobber.

#if TEST_EXCHANGEIMPLEMENTATIONS
    testprintf("exchangeImplementations clobbers (recursive)\n");
#else
    testprintf("setImplementation clobbers (recursive)\n");
#endif
    zero();

    objc_retain(obj);
    testassert(Retains == 0);
    objc_release(obj);
    testassert(Releases == 0);
    objc_autorelease(obj);
    testassert(Autoreleases == 0);

    objc_retain(inh);
    testassert(Retains == 0);
    objc_release(inh);
    testassert(Releases == 0);
    objc_autorelease(inh);
    testassert(Autoreleases == 0);

    Method meth = class_getInstanceMethod(cls, @selector(retainCount));
    testassert(meth);
#if TEST_EXCHANGEIMPLEMENTATIONS
    method_exchangeImplementations(meth, meth);
#else
    method_setImplementation(meth, (IMP)imp_fn);
#endif
    
    objc_retain(obj);
    testassert(Retains == 1);
    objc_release(obj);
    testassert(Releases == 1);
    objc_autorelease(obj);
    testassert(Autoreleases == 1);

    objc_retain(inh);
    testassert(Retains == 2);
    objc_release(inh);
    testassert(Releases == 2);
    objc_autorelease(inh);
    testassert(Autoreleases == 2);

    
    // do not add more tests here - the recursive test must be LAST

    succeed(basename(argv[0]));
}
