// TEST_CONFIG MEM=mrc
// TEST_CRASHES
/*
TEST_RUN_OUTPUT
objc\[\d+\]: Cannot form weak reference to instance \(0x[0-9a-f]+\) of class Crash. It is possible that this object was over-released, or is in the process of deallocation.
objc\[\d+\]: HALTED
END
*/

#include "test.h"

#include <Foundation/NSObject.h>

static id weak;
static id weak2;
static id weak3;
static id weak4;
static bool did_dealloc;

static int state;

@interface NSObject (WeakInternals)
-(BOOL)_tryRetain;
-(BOOL)_isDeallocating;
@end

@interface Test : NSObject @end
@implementation Test 
-(void)dealloc {
    testprintf("Weak storeOrNil does not crash while deallocating\n");
    weak4 = (id)0x100;  // old value must not be used
    id result = objc_initWeakOrNil(&weak4, self);
    testassert(result == nil);
    testassert(weak4 == nil);
    result = objc_storeWeakOrNil(&weak4, self);
    testassert(result == nil);
    testassert(weak4 == nil);

    // The value returned by objc_loadWeak() is now nil, 
    // but the storage is not yet cleared.
    testassert(weak == self);
    testassert(weak2 == self);

    // objc_loadWeak() does not eagerly clear the storage.
    testassert(objc_loadWeakRetained(&weak) == nil);
    testassert(weak != nil);

    // dealloc clears the storage.
    testprintf("Weak references clear during super dealloc\n");
    testassert(weak2 != nil);
    [super dealloc];
    testassert(weak == nil);
    testassert(weak2 == nil);

    did_dealloc = true;
}
@end

@interface CustomTryRetain : Test @end
@implementation CustomTryRetain
-(BOOL)_tryRetain { state++; return [super _tryRetain]; }
@end

@interface CustomIsDeallocating : Test @end
@implementation CustomIsDeallocating
-(BOOL)_isDeallocating { state++; return [super _isDeallocating]; }
@end

@interface CustomAllowsWeakReference : Test @end
@implementation CustomAllowsWeakReference
-(BOOL)allowsWeakReference { state++; return [super allowsWeakReference]; }
@end

@interface CustomRetainWeakReference : Test @end
@implementation CustomRetainWeakReference
-(BOOL)retainWeakReference { state++; return [super retainWeakReference]; }
@end

@interface Crash : NSObject @end
@implementation Crash
-(void)dealloc {
    testassert(weak == self);
    testassert(weak2 == self);
    testassert(objc_loadWeakRetained(&weak) == nil);
    testassert(objc_loadWeakRetained(&weak2) == nil);

    testprintf("Weak storeOrNil does not crash while deallocating\n");
    id result = objc_storeWeakOrNil(&weak, self);
    testassert(result == nil);

    testprintf("Weak store crashes while deallocating\n");
    objc_storeWeak(&weak, self);
    fail("objc_storeWeak of deallocating value should have crashed");
    [super dealloc];
}
@end


void cycle(Class cls, Test *obj, Test *obj2, bool storeOrNil)
{
    testprintf("Cycling class %s\n", class_getName(cls));

    id result;

    id (*storeWeak)(id *location, id obj);
    id (*initWeak)(id *location, id obj);
    if (storeOrNil) {
        testprintf("Using objc_storeWeakOrNil\n");
        storeWeak = objc_storeWeakOrNil;
        initWeak = objc_initWeakOrNil;
    } else {
        testprintf("Using objc_storeWeak\n");
        storeWeak = objc_storeWeak;
        initWeak = objc_initWeak;
    }

    // state counts calls to custom weak methods
    // Difference test classes have different expected values.
    int storeTarget;
    int loadTarget;
    if (cls == [Test class]) {
        storeTarget = 0;
        loadTarget = 0;
    }
    else if (cls == [CustomTryRetain class] || 
             cls == [CustomRetainWeakReference class])
    {
        storeTarget = 0;
        loadTarget = 1;
    }
    else if (cls == [CustomIsDeallocating class] || 
             cls == [CustomAllowsWeakReference class])
    {
        storeTarget = 1;
        loadTarget = 0;
    }
    else fail("wut");

    testprintf("Weak assignment\n");
    state = 0;
    result = storeWeak(&weak, obj);
    testassert(state == storeTarget);
    testassert(result == obj);
    testassert(weak == obj);

    testprintf("Weak assignment to the same value\n");
    state = 0;
    result = storeWeak(&weak, obj);
    testassert(state == storeTarget);
    testassert(result == obj);
    testassert(weak == obj);

    testprintf("Weak load\n");
    state = 0;
    result = objc_loadWeakRetained(&weak);
    if (state != loadTarget) testprintf("state %d target %d\n", state, loadTarget);
    testassert(state == loadTarget);
    testassert(result == obj);
    testassert(result == weak);
    [result release];

    testprintf("Weak assignment to different value\n");
    state = 0;
    result = storeWeak(&weak, obj2);
    testassert(state == storeTarget);
    testassert(result == obj2);
    testassert(weak == obj2);

    testprintf("Weak assignment to NULL\n");
    state = 0;
    result = storeWeak(&weak, NULL);
    testassert(state == 0);
    testassert(result == NULL);
    testassert(weak == NULL);

    testprintf("Weak re-assignment to NULL\n");
    state = 0;
    result = storeWeak(&weak, NULL);
    testassert(state == 0);
    testassert(result == NULL);
    testassert(weak == NULL);

    testprintf("Weak move\n");
    state = 0;
    result = storeWeak(&weak, obj);
    testassert(state == storeTarget);
    testassert(result == obj);
    testassert(weak == obj);
    weak2 = (id)(PAGE_MAX_SIZE-16);
    objc_moveWeak(&weak2, &weak);
    testassert(weak == nil);
    testassert(weak2 == obj);
    storeWeak(&weak2, NULL);

    testprintf("Weak copy\n");
    state = 0;
    result = storeWeak(&weak, obj);
    testassert(state == storeTarget);
    testassert(result == obj);
    testassert(weak == obj);
    weak2 = (id)(PAGE_MAX_SIZE-16);
    objc_copyWeak(&weak2, &weak);
    testassert(weak == obj);
    testassert(weak2 == obj);
    storeWeak(&weak, NULL);
    storeWeak(&weak2, NULL);

    testprintf("Weak clear\n");

    id obj3 = [cls new];

    state = 0;
    result = storeWeak(&weak, obj3);
    testassert(state == storeTarget);
    testassert(result == obj3);
    testassert(weak == obj3);

    state = 0;
    result = storeWeak(&weak2, obj3);
    testassert(state == storeTarget);
    testassert(result == obj3);
    testassert(weak2 == obj3);

    did_dealloc = false;
    [obj3 release];
    testassert(did_dealloc);
    testassert(weak == NULL);
    testassert(weak2 == NULL);


    testprintf("Weak init and destroy\n");

    id obj4 = [cls new];
    
    state = 0;
    weak = (id)0x100;  // old value must not be used
    result = initWeak(&weak, obj4);
    testassert(state == storeTarget);
    testassert(result == obj4);
    testassert(weak == obj4);
    
    state = 0;
    weak2 = (id)0x100;  // old value must not be used
    result = initWeak(&weak2, obj4);
    testassert(state == storeTarget);
    testassert(result == obj4);
    testassert(weak2 == obj4);
    
    state = 0;
    weak3 = (id)0x100;  // old value must not be used
    result = initWeak(&weak3, obj4);
    testassert(state == storeTarget);
    testassert(result == obj4);
    testassert(weak3 == obj4);

    state = 0;
    objc_destroyWeak(&weak3);
    testassert(state == 0);
    testassert(weak3 == obj4);  // storage is unchanged

    did_dealloc = false;
    [obj4 release];
    testassert(did_dealloc);
    testassert(weak == NULL);   // not destroyed earlier so cleared now
    testassert(weak2 == NULL);  // not destroyed earlier so cleared now
    testassert(weak3 == obj4);  // destroyed earlier so not cleared now

    objc_destroyWeak(&weak);
    objc_destroyWeak(&weak2);
}


void test_class(Class cls)
{
    // prime strong and weak side tables before leak checking
    Test *prime[256] = {nil};
    for (size_t i = 0; i < sizeof(prime)/sizeof(prime[0]); i++) {
        objc_storeWeak(&prime[i], [cls new]);
    }

    Test *obj = [cls new];
    Test *obj2 = [cls new];

    for (int i = 0; i < 100000; i++) {
        cycle(cls, obj, obj2, false);
        cycle(cls, obj, obj2, true);
    }
    leak_mark();
    for (int i = 0; i < 100000; i++) {
        cycle(cls, obj, obj2, false);
        cycle(cls, obj, obj2, true);
    }
    // allow some slop for side table expansion
    // 5120 is common with this configuration
    leak_check(6000);

    // rdar://14105994
    id weaks[8];
    for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) {
        objc_storeWeak(&weaks[i], obj);
    }
    for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) {
        objc_storeWeak(&weaks[i], nil);
    }
}

int main()
{
    test_class([Test class]);
    test_class([CustomTryRetain class]);
    test_class([CustomIsDeallocating class]);
    test_class([CustomAllowsWeakReference class]);
    test_class([CustomRetainWeakReference class]);


    id result;

    Crash *obj3 = [Crash new];
    result = objc_storeWeak(&weak, obj3);
    testassert(result == obj3);
    testassert(weak == obj3);

    result = objc_storeWeak(&weak2, obj3);
    testassert(result == obj3);
    testassert(weak2 == obj3);

    [obj3 release];
    fail("should have crashed in -[Crash dealloc]");
}
