NSInvocation crashes when sending a message to the block object

Originator:pohilets
Number:rdar://25289979 Date Originated:22-Mar-2016
Status:Open Resolved:
Product:iOS SDK Product Version:
Classification:Crash/Hang/Data Loss Reproducible:Always
 
Summary:
Attached code snippet crashes.

On the other hand, being able to call blocks in a NSInvocation-like manner allows writing generic library code that handles blocks of different signatures. Would be great to have a separate public API for doing this that would not interfere with normal message sending.

Note that block may be taking a selector as a first argument. In this case block type encoding will be very similar to the method type encoding: "#8@?0:4" vs "#8@0:4" in attached example.

Steps to Reproduce:
Compile and run attached code snippet

Expected Results:
First two NSLog's should print the same result. App should not crash. It should be possible to send messages to block objects. Sending messages to blocks should not be confused with calling blocks that take a selector as a first argument.

Actual Results:
Block is called instead of sending message to the block object. App crashes inside the block code

Version:
iOS 8.4.1 (12H321), iOS 9.2.1 (13D15)

Notes:


Configuration:
iPhone 6s, iPhone 5c

// Attached code
@import Foundation;
#import <objc/runtime.h>

struct block_literal
{
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved;                 // NULL
        unsigned long int size;                     // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);  // IFF (1<<25)
        void (*dispose_helper)(void *src);          // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                      // IFF (1<<30)
    } *descriptor;
};

char const * block_signature(id x) {
    struct block_literal* def = (__bridge struct block_literal*)x;
    if (def == NULL) {
        return NULL;
    }
    
    if (!(def->flags & (1 << 30))) {
        return NULL;
    }
    
    if (def->flags & (1 << 25)) {
        return def->descriptor->signature;
    } else {
        return *(char const * const *)&def->descriptor->copy_helper;
    }
}

int main()
{
    {
        void (^block)(id x) = ^(id x){
        	NSLog(@"%@", x); // Crash
        };
        NSLog(@"%@", [block class]);
        NSMethodSignature* sign = [block methodSignatureForSelector:@selector(class)];
        NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sign];
        [invocation setTarget:block];
        [invocation setSelector:@selector(class)];
        [invocation invoke]; // This invokes the block, rather then sending a message to the block object
        Class k = nil;
        [invocation getReturnValue:&k];
        NSLog(@"%@", k);
    }

    // This is a very cool functionality that is useful for library writers.
	// But unfortunately undocumented.
	{
        CGAffineTransform (^block)(id x, int y, CGSize z) = ^(id x, int y, CGSize z){
            NSLog(@"%@,%d,%f", x, y, z.width);
            CGAffineTransform t = { 1, 2, 3, 4, 5, 6 };
            return t;
        };
    
        NSMethodSignature* sign = [NSMethodSignature signatureWithObjCTypes:block_signature(block)];
        NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sign];
        [invocation setTarget:block];
        void* x = (__bridge void*)@"Foo";
        int y = 42;
        CGSize z = { 320, 480 };
        [invocation setArgument:&x atIndex:1];
        [invocation setArgument:&y atIndex:2];
        [invocation setArgument:&z atIndex:3];
        [invocation invoke];
        CGAffineTransform t;
        [invocation getReturnValue:&t];
        NSLog(@"%@", NSStringFromCGAffineTransform(t));
    }
    
    // Mind this case as well
    {
        Class (^blockThatLooksLikeASelector)(SEL sel) = ^(SEL sel){ NSLog(@"%@", NSStringFromSelector(sel)); return [NSString class]; };
        char const * s1 = block_signature(blockThatLooksLikeASelector);
        Class k = object_getClass(blockThatLooksLikeASelector);
        Method m = class_getInstanceMethod(k, @selector(class));
        char const * s2 = method_getTypeEncoding(m);
        NSLog(@"\n%s\n%s", s1, s2);
    }
}

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!