taggedPointers.m   [plain text]


// TEST_CFLAGS -framework Foundation

#include "test.h"
#include <objc/runtime.h>
#include <objc/objc-internal.h>
#import <Foundation/Foundation.h>

#if __OBJC2__ && __LP64__
/*
 gcc -o taggedPointers.out taggedPointers.m -L/tmp/bbum-products/Release/ -lobjc -undefined dynamic_lookup -framework Foundation -gdwarf-2
 env DYLD_LIBRARY_PATH=/tmp/bbum-products/Release/ DYLD_FRAMEWORK_PATH=/tmp/bbum-products/Release gdb ./taggedPointers.out
 env DYLD_LIBRARY_PATH=/tmp/bbum-products/Debug/ DYLD_FRAMEWORK_PATH=/tmp/bbum-products/Debug gdb ./taggedPointers.out
 */

static BOOL didIt;

#define TAG_VALUE(tagSlot, value) ((id)(1UL | (((uintptr_t)(tagSlot)) << 1) | (((uintptr_t)(value)) << 4)))

@interface TaggedBaseClass
@end

@implementation TaggedBaseClass
+ (void) initialize
{
    ;
}

- (void) instanceMethod
{
    didIt = YES;
}

- (uintptr_t) taggedValue
{
    return (uintptr_t) self >> 4;
}

- (NSRect) stret: (NSRect) aRect
{
    return aRect;
}

- (long double) fpret: (long double) aValue
{
    return aValue;
}


-(void) dealloc {
    fail("TaggedBaseClass dealloc called!");
}

-(id) retain {
    return _objc_rootRetain(self);
}

-(void) release {
    return _objc_rootRelease(self);
}

-(id) autorelease {
    return _objc_rootAutorelease(self);
}

-(uintptr_t) retainCount {
    return _objc_rootRetainCount(self);
}
@end

@interface TaggedSubclass: TaggedBaseClass
@end

@implementation TaggedSubclass
+ (void) initialize
{
    ;
}

- (void) instanceMethod
{
    return [super instanceMethod];
}

- (uintptr_t) taggedValue
{
    return [super taggedValue];
}

- (NSRect) stret: (NSRect) aRect
{
    return [super stret: aRect];
}

- (long double) fpret: (long double) aValue
{
    return [super fpret: aValue];
}
@end

@interface TaggedNSObjectSubclass : NSObject
@end

@implementation TaggedNSObjectSubclass
+ autorelease {
    abort();
}
- autorelease {
    didIt = YES;
    return self;
}
- retain {
    didIt = YES;
    return self;
}
- (oneway void) release {
    didIt = YES;
}

- (void) instanceMethod {
    didIt = YES;
}

- (uintptr_t) taggedValue
{
    return (uintptr_t) self >> 4;
}

- (NSRect) stret: (NSRect) aRect
{
    return aRect;
}

- (long double) fpret: (long double) aValue
{
    return aValue;
}
@end

/*

This class was used prior to integration of tagged numbers into CF.
Now that CF has tagged numbers, the test assumes their presence.
  
@interface TestTaggedNumber:NSNumber
@end
@implementation TestTaggedNumber
+(void) load
{
    _objc_insert_tagged_isa(4, self);
}

+ taggedNumberWithInt: (int) arg
{
    uint64_t value = (uint64_t) arg;
    id returnValue = (id) (((uint64_t) 0x9) | (value << 4));
    return returnValue;
}

- (void)getValue:(void *)value
{
    *(uint64_t *)value = ((uint64_t)self) >> 4;
}

- (const char *)objCType
{
    return "i";
}

- (int)intValue
{
    return (int) (((uint64_t)self) >> 4);
}
@end
*/

void testTaggedNumber()
{
    NSNumber *taggedPointer = [NSNumber numberWithInt: 1234];
    int result;
    
    testassert( CFGetTypeID(taggedPointer) == CFNumberGetTypeID() );
    
    CFNumberGetValue((CFNumberRef) taggedPointer, kCFNumberIntType, &result);
    testassert(result == 1234);

    testassert(((uintptr_t)taggedPointer) & 0x1); // make sure it is really tagged

    // do some generic object-y things to the taggedPointer instance
    CFRetain(taggedPointer);
    CFRelease(taggedPointer);
    
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject: taggedPointer forKey: @"fred"];
    testassert(taggedPointer == [dict objectForKey: @"fred"]);
    [dict setObject: @"bob" forKey: taggedPointer];
    testassert([@"bob" isEqualToString: [dict objectForKey: taggedPointer]]);
    
    NSNumber *i12345 = [NSNumber numberWithInt: 12345];
    NSNumber *i12346 = [NSNumber numberWithInt: 12346];
    NSNumber *i12347 = [NSNumber numberWithInt: 12347];
    
    NSArray *anArray = [NSArray arrayWithObjects: i12345, i12346, i12347, nil];
    testassert([anArray count] == 3);
    testassert([anArray indexOfObject: i12346] == 1);
    
    NSSet *aSet = [NSSet setWithObjects: i12345, i12346, i12347, nil];
    testassert([aSet count] == 3);
    testassert([aSet containsObject: i12346]);
    
    [taggedPointer performSelector: @selector(intValue)];
    testassert(![taggedPointer isProxy]);
    testassert([taggedPointer isKindOfClass: [NSNumber class]]);
    testassert([taggedPointer respondsToSelector: @selector(intValue)]);
    
    [taggedPointer description];
}

void testGenericTaggedPointer(uint8_t tagSlot, const char *classname)
{
    Class cls = objc_getClass(classname);
    testassert(cls);

    id taggedPointer = TAG_VALUE(tagSlot, 1234);
    testassert(object_getClass(taggedPointer) == cls);
    testassert([taggedPointer taggedValue] == 1234);

    didIt = NO;
    [taggedPointer instanceMethod];
    testassert(didIt);    
    
    NSRect originalRect = NSMakeRect(1.0, 2.0, 3.0, 4.0);
    testassert(NSEqualRects(originalRect, [taggedPointer stret: originalRect]));
    
    long double value = 3.14156789;
    testassert(value == [taggedPointer fpret: value]);

    if (!objc_collectingEnabled()) {
        // Tagged pointers should bypass refcount tables and autorelease pools
        leak_mark();
        for (uintptr_t i = 0; i < 10000; i++) {
            id o = TAG_VALUE(tagSlot, i);
            testassert(object_getClass(o) == cls);

            [o release];  testassert([o retainCount] != 0);
            [o release];  testassert([o retainCount] != 0);
            CFRelease(o);  testassert([o retainCount] != 0);
            CFRelease(o);  testassert([o retainCount] != 0);
            [o retain];
            [o retain];
            [o retain];
            CFRetain(o);
            CFRetain(o);
            CFRetain(o);
            [o autorelease];
        }
        leak_check(0);
    }
}

int main()
{
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    
    _objc_insert_tagged_isa(5, objc_getClass("TaggedBaseClass"));
    testGenericTaggedPointer(5, "TaggedBaseClass");
    
    _objc_insert_tagged_isa(2, objc_getClass("TaggedSubclass"));
    testGenericTaggedPointer(2, "TaggedSubclass");
    
    _objc_insert_tagged_isa(3, objc_getClass("TaggedNSObjectSubclass"));
    testGenericTaggedPointer(3, "TaggedNSObjectSubclass");
    
    testTaggedNumber(); // should be tested by CF... our tests are wrong, wrong, wrong.
    [p release];

    succeed(__FILE__);
}

// OBJC2 && __LP64__
#else
// not (OBJC2 && __LP64__)

    // Tagged pointers not supported. Crash if an NSNumber actually 
    // is a tagged pointer (which means this test is out of date).

int main() {
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    testassert(*(id *)[NSNumber numberWithInt:1234]);
    [p release];
    
    succeed(__FILE__);
}

#endif