NSInvocation throw with unions or bitfields in structs return types, overrelease

Originator:chaos42
Number:rdar://14730679 Date Originated:13-Aug-2013 07:11 PM
Status:Open Resolved:
Product:OS X SDK Product Version:10.8
Classification:Serious Bug Reproducible:Always
 
Summary: NSInvocation does not work with methods that return structures that contain unions or bitfield types. Instead exceptions are thrown (and an NSException is overreleased). 

Steps to Reproduce:
Create a new Mac OS X target. In main.m, paste: 

static void test_failure(const char *file, int line)
{
    NSLog("Test failure at %s:%d\n", file, line);
}

BOOL _testassert(BOOL b, const char *file, int line)
{
    if (!(b))
    {
        test_failure(file, line);
        return NO;
    }

    return YES;
}
#define testassert(b) do { if (!_testassert(b, __FILE__, __LINE__)) return NO; } while (NO)


@interface TestHelper : NSObject
@end
@implmentation TestHelper
union floatInt {
    int a;
    float b;
};
struct hugeStruct {
    double a;
    char b;
    int c;
    short d;
    long long e;
    char *f;
    SEL g;
    id h;
    NSRange i;
    NSObject *j;
    int *k;
    void (^l)(void);
    int *(*m)(int *);
    struct charDoubleStruct n;
    union floatInt o;
    IMP q;
    Class r;
    int s[5];
    char t:1;
    char u:3;
    char v:4;
    volatile int x;
    float y;
    
    
};

int *foo(int *bar) { return bar; };
int *baz(int *qux) { return NULL;};

- (struct hugeStruct)hugeStructRetMethod
{
    int *k = NULL;
    struct hugeStruct ret = {
        M_PI,
        'b',
        1234567,
        -1234,
        1234567891011,
        (char *)NULL,
        _cmd,
        @protocol(NSCoding),
        NSMakeRange(123, 321),
        @"Foo",
        k,
        [^{} copy],
        foo,
        { 'a', M_E },
        9001,
        [self methodForSelector:_cmd],
        [self class],
        { 4321, 1234, 5678, 8765, 9101112},
        1,
        3,
        6,
        70,
        4.42f
    };
    return ret;
}

- (BOOL)testHugeStruct
{
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(hugeStructRetMethod)]];
    [inv setSelector:@selector(hugeStructRetMethod)];
    [inv setTarget:self];
    int k = 51;
    char f = 'x';
    struct hugeStruct result = {
        M_PI_2,
        'f',
        7654321,
        4321,
        1110987654321,
        &f,
        _cmd,
        @protocol(NSCopying),
        NSMakeRange(500, 50),
        @"Bar",
        &k,
        nil,
        baz,
        { 'b', M_LN2 },
        90.5f,
        [self methodForSelector:_cmd],
        [NSInvocation class],
        { 42, 0, -1, INT_MIN, INT_MAX},
        0,
        0,
        0,
        77,
        41147.7
    };
    [inv invoke];
    [inv getReturnValue:&result];
    testassert(result.a == M_PI);
    testassert(result.b == 'b');
    testassert(result.c == 1234567);
    testassert(result.d == -1234);
    testassert(result.e == 1234567891011);
    testassert(result.f == NULL);
    testassert(result.g == @selector(hugeStructRetMethod));
    testassert(result.h == @protocol(NSCoding));
    testassert(result.i.length == 321);
    testassert(result.i.location == 123);
    testassert([(id)result.j isEqualToString:@"Foo"]);
    testassert(result.k == NULL);
    testassert(result.l != nil);
    testassert(result.m == foo);
    testassert(result.n.a == 'a');
    testassert(result.n.b == M_E);
    testassert(result.o.a == 9001);
    testassert(result.q == [self methodForSelector:@selector(hugeStructRetMethod)]);
    testassert(result.r == [self class]);
    testassert(result.s[0] == 4321);
    testassert(result.s[1] == 1234);
    testassert(result.s[2] == 5678);
    testassert(result.s[3] == 8765);
    testassert(result.s[4] == 9101112);
    testassert(result.t == 1);
    testassert(result.u == 3);
    testassert(result.v == 6);
    testassert(result.x == 70);
    testassert(result.y == 4.42f);
    
    return YES;
}
@end

and in main(), inside the @autoreleasepool scope:

TestHelper *h = [[TestHelper alloc] init];
[h testHugeStruct];

Expected Results: All tests pass

Actual Results: 
Several exceptions are thrown (and caught internally) by NSInvocation in the first line of -testHugeStruct. The first test fails and the method returns. When the autorelease pool drains, the app crashes as an already-released NSException is released.

Regression:
Unknown.

Notes:
Even if bitfields are not supported, there REALLY shouldn't be an overreleased NSException here. Also occurs in iOS 6.1 SDK.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!