darling-corefoundation/NSInvocation.m
Sergey Bugaev 775d287f3c Implement -[NSInvocation debugDescription]
For some reason, LLDB is really unhappy about NSInvocation's debug info, and
refuses to display NSInvocation values (or any values when NSInvocation is in
scope). Now, we can at least `po` invocations.
2020-04-16 11:44:30 +03:00

415 lines
10 KiB
Objective-C

//
// NSInvocation.m
// CoreFoundation
//
// Copyright (c) 2014 Apportable. All rights reserved.
//
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSException.h>
#import <Foundation/NSInvocation.h>
#import <Foundation/NSMethodSignature.h>
#import "Block_private.h"
#import "NSInvocationInternal.h"
#import "NSObjCRuntimeInternal.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import <dispatch/dispatch.h>
@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
{
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 ([sig _stret])
{
// Set up the return value pointer for the objc_msgSend_stret call.
void **ret = _frame;
*ret = _retdata;
}
}
return self;
}
- (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
{
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];
}
}
- (void)invoke
{
id target = nil;
[self getArgument:&target atIndex:0];
if (target == nil)
{
return;
}
static Class NSBlockClass = Nil;
static dispatch_once_t once = 0L;
dispatch_once(&once, ^{
// __NSGlobalBlock -> NSBlock
NSBlockClass = class_getSuperclass(class_getSuperclass(object_getClass(^{})));
});
BOOL blockClass = NO;
Class cls = object_getClass(target);
while (cls != Nil)
{
if (cls == NSBlockClass)
{
blockClass = YES;
}
cls = class_getSuperclass(cls);
}
char rettype = [_signature methodReturnType][0];
if (blockClass)
{
struct Block_layout *block_layout = (struct Block_layout *)target;
__invoke__(block_layout->invoke, _retdata, _frame, [_signature frameLength], rettype);
}
else if ([_signature _stret])
{
char dummy[RET_SIZE_ARGS];
__invoke__(&objc_msgSend_stret, &dummy, _frame, [_signature frameLength], rettype);
}
else
{
__invoke__(&objc_msgSend, _retdata, _frame, [_signature frameLength], rettype);
}
if (_retainedArgs)
{
// Retain the return value.
[self _retainArgument:0];
}
}
- (void)invokeWithTarget:(id)target
{
[self setTarget:target];
[self invoke];
}
- (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