// // NSInvocation.m // CoreFoundation // // Copyright (c) 2014 Apportable. All rights reserved. // #import #import #import #import #import #import "Block_private.h" #import "NSInvocationInternal.h" #import "NSObjCRuntimeInternal.h" #import #import #import @implementation NSInvocation + (void)load { #if LEGACY_METHOD // signature should be @encode(long long) @encode(id) @encode(SEL) @encode(SEL) @encode(marg_list) class_addMethod([NSObject class], @selector(forward::), (IMP)&_CF_forwarding_prep_0, "q@::^v"); #else objc_setForwardHandler(&_CF_forwarding_prep_0, &_CF_forwarding_prep_1); #endif } - (void **)_idxToArg:(NSUInteger)idx { if (idx == 0) { return _retdata; } NSMethodType *argType = [_signature _argInfo:idx]; return _frame + argType->offset; } // NSInvocation can retain its arguments, including copies of C // strings which it places in an NSData. For such strings, this method // therefore modifies the frame to use an internal pointer to the // NSData rather than to the original string. - (void)_retainArgument:(NSUInteger)idx { NSMethodType *argInfo = [_signature _argInfo: idx]; const char *type = stripQualifiersAndComments(argInfo->type); void **arg = [self _idxToArg: idx]; if (type[0] == _C_ID) { id object = (id) *arg; if (object != nil) { [_container addObject: (id) *arg]; } } else if (type[0] == _C_CHARPTR) { char *str = (char *) *arg; if (str != NULL) { NSUInteger length = strlen(str) + 1; NSData *data = [NSData dataWithBytes: str length: length]; *arg = [data bytes]; [_container addObject: data]; } } } - (void) _addAttachedObject: (id) object { if (_container == nil) { _container = [[NSMutableArray alloc] init]; } [_container addObject: object]; } - (instancetype)_initWithMethodSignature: (NSMethodSignature*)sig frame: (void*)frame { if (sig == nil) { [self release]; [NSException raise:NSInvalidArgumentException format:@"signature cannot be nil"]; return nil; } self = [super init]; if (self) { _signature = [sig retain]; NSUInteger retSize = 0; NSGetSizeAndAlignment([_signature methodReturnType], &retSize, NULL); retSize = MAX(retSize, RET_SIZE_ARGS); _retdata = calloc(retSize + [_signature frameLength], 1); _frame = _retdata + retSize; if (frame) { memcpy(_frame, frame, [_signature frameLength]); } if ([sig _stret]) { // Set up the return value pointer for the objc_msgSend_stret call. void **ret = _frame; *ret = _retdata; } } return self; } - (instancetype)initWithMethodSignature:(NSMethodSignature *)sig { return [self _initWithMethodSignature: sig frame: NULL]; } - (instancetype)init { [self release]; // init should not work on NSInvocation return nil; } - (void)dealloc { [_container release]; [_signature release]; free(_retdata); [super dealloc]; } + (instancetype)_invocationWithMethodSignature: (NSMethodSignature*)sig frame: (void*)frame { return [[[self alloc] _initWithMethodSignature: sig frame: frame] autorelease]; } + (instancetype)invocationWithMethodSignature:(NSMethodSignature *)sig { return [[[self alloc] initWithMethodSignature:sig] autorelease]; } - (NSMethodSignature *)methodSignature { return _signature; } - (void)retainArguments { if (_retainedArgs) { return; } _retainedArgs = YES; NSUInteger capacity = [_signature numberOfArguments] + 1; // Add one for return value. if (_container == nil) { _container = [[NSMutableArray alloc] initWithCapacity: capacity]; } for (NSUInteger idx = 0; idx < capacity; idx++) { [self _retainArgument:idx]; } } - (BOOL)argumentsRetained { return _retainedArgs; } - (id)target { id t = nil; [self getArgument:&t atIndex:0]; return t; } - (void)setTarget:(id)target { [self setArgument:&target atIndex:0]; } - (SEL)selector { SEL sel; [self getArgument:&sel atIndex:1]; return sel; } - (void)setSelector:(SEL)selector { [self setArgument:&selector atIndex:1]; } - (void)getReturnValue:(void *)retLoc { [self getArgument:retLoc atIndex:-1]; } - (void)setReturnValue:(void *)retLoc { [self setArgument:retLoc atIndex:-1]; } - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx { // idx initially goes like this: // -1: return value // 0: self // 1: _cmd // 2+: arguments // Thus we add 1 to get an index into _frame. idx++; if (idx > [_signature numberOfArguments] || idx < 0) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:nil userInfo:nil]; } NSMethodType *argInfo = [_signature _argInfo:idx]; void **arg = [self _idxToArg:idx]; memcpy(argumentLocation, arg, argInfo->size); } - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx { // idx initially goes like this: // -1: return value // 0: self // 1: _cmd // 2+: arguments // Thus we add 1 to get an index into _frame. idx++; if (idx > [_signature numberOfArguments] || idx < 0) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:nil userInfo:nil]; } NSMethodType *argInfo = [_signature _argInfo:idx]; void **arg = [self _idxToArg:idx]; memcpy(arg, argumentLocation, argInfo->size); if (_retainedArgs) { [self _retainArgument:idx]; } } static BOOL isBlock(id object) { static Class NSBlockClass = Nil; static dispatch_once_t once = 0L; dispatch_once(&once, ^{ // __NSGlobalBlock -> NSBlock NSBlockClass = class_getSuperclass(class_getSuperclass(object_getClass(^{}))); }); for ( Class class = object_getClass(object); class != Nil; class = class_getSuperclass(class) ) { if (class == NSBlockClass) { return YES; } } return NO; } - (void) _invokeUsingIMP: (IMP) imp withFrame: (void *) frame { if ([self target] == nil) { return; } char rettype = [_signature methodReturnType][0]; if ([_signature _stret]) { char dummy[RET_SIZE_ARGS]; __invoke__(imp, &dummy, frame, [_signature frameLength], rettype); } else { __invoke__(imp, _retdata, frame, [_signature frameLength], rettype); } if (_retainedArgs) { // Retain the return value. [self _retainArgument:0]; } } - (void) invokeUsingIMP: (IMP) imp { [self _invokeUsingIMP: imp withFrame: _frame]; } - (void)invoke { id target = [self target]; if (target == nil) { return; } IMP imp; if (isBlock([self target])) { struct Block_layout *block_layout = (struct Block_layout *) target; imp = (IMP)block_layout->invoke; } #if !defined(__arm64__) else if ([_signature _stret]) { imp = (IMP)&objc_msgSend_stret; } #endif else { imp = &objc_msgSend; } [self _invokeUsingIMP: imp withFrame: _frame]; } - (void) invokeWithTarget: (id) target { [self setTarget: target]; [self invoke]; } - (void) invokeSuper { id target = [self target]; if (target == nil) { return; } unsigned char *frameCopy = malloc([_signature frameLength]); memcpy(frameCopy, _frame, [_signature frameLength]); struct objc_super super = { .receiver = target, #ifdef __OBJC2__ .super_class #else .class #endif = class_getSuperclass([target class]) }; NSMethodType *argType = [_signature _argInfo: 1]; *(struct objc_super **) (frameCopy + argType->offset) = &super; #if !defined(__arm64__) IMP imp = [_signature _stret] ? &objc_msgSendSuper_stret : &objc_msgSendSuper; #else IMP imp = &objc_msgSendSuper; #endif [self _invokeUsingIMP: imp withFrame: frameCopy]; free(frameCopy); } - (NSString *) debugDescription { CFMutableStringRef description = CFStringCreateMutable(NULL, 0); CFStringAppend(description, (CFStringRef) [self description]); for (NSInteger index = -1; index < (NSInteger) [_signature numberOfArguments]; index++) { CFStringAppend(description, @"\n"); switch (index) { case -1: CFStringAppend(description, @"return value"); break; case 0: CFStringAppend(description, @"target"); break; case 1: CFStringAppend(description, @"selector"); break; default: CFStringAppendFormat(description, NULL, @"argument %ld", (long) index); break; } CFStringAppend(description, @": "); NSMethodType *argInfo = [_signature _argInfo: index + 1]; CFStringAppendFormat(description, NULL, @"{%s} ", argInfo->type); char type = stripQualifiersAndComments(argInfo->type)[0]; switch (type) { case _C_VOID: CFStringAppend(description, @"void"); break; case _C_CLASS: { Class class; [self getArgument: &class atIndex: index]; if (class == Nil) { CFStringAppend(description, @"Nil"); } else { CFStringAppendFormat(description, NULL, @"%s", class_getName(class)); } break; } case _C_SEL: { SEL selector; [self getArgument: &selector atIndex: index]; if (!selector) { CFStringAppend(description, @"null"); } else { CFStringAppendFormat(description, NULL, @"%s", sel_getName(selector)); } break; } #define HANDLE(_c_type, type, format) \ case _c_type: \ { \ type value; \ [self getArgument: &value atIndex: index]; \ CFStringAppendFormat(description, NULL, format, value); \ break; \ } HANDLE(_C_CHR, char, @"%c"); HANDLE(_C_UCHR, unsigned char, @"%u"); HANDLE(_C_BOOL, _Bool, @"%d"); HANDLE(_C_SHT, short, @"%d"); HANDLE(_C_USHT, unsigned short, @"%u"); HANDLE(_C_INT, int, @"%d"); HANDLE(_C_UINT, unsigned int, @"%u"); HANDLE(_C_LNG, long, @"%ld"); HANDLE(_C_ULNG, unsigned long, @"%lu"); HANDLE(_C_LNG_LNG, long long, @"%lld"); HANDLE(_C_ULNG_LNG, unsigned long long, @"%llu"); HANDLE(_C_FLT, float, @"%f"); HANDLE(_C_DBL, double, @"%f"); HANDLE(_C_CHARPTR, const char *, @"%s"); HANDLE(_C_ID, id, @"%p"); HANDLE(_C_PTR, void *, @"%p"); #undef HANDLE default: { const void *ptr = [self _idxToArg: index + 1]; CFDataRef data = CFDataCreateWithBytesNoCopy(NULL, ptr, argInfo->size, kCFAllocatorNull); CFStringAppend(description, (CFStringRef) [data description]); CFRelease(data); break; } } } CFAutorelease(description); return description; } @end