├── .gitignore ├── MAInvocation-asm.h ├── MAInvocation-asm.s ├── MAInvocation.h ├── MAInvocation.m └── tester.m /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | *.dSYM 3 | -------------------------------------------------------------------------------- /MAInvocation-asm.h: -------------------------------------------------------------------------------- 1 | 2 | struct RawArguments 3 | { 4 | void *fptr; 5 | 6 | uint64_t rdi; 7 | uint64_t rsi; 8 | uint64_t rdx; 9 | uint64_t rcx; 10 | uint64_t r8; 11 | uint64_t r9; 12 | 13 | uint64_t stackArgsCount; 14 | uint64_t *stackArgs; 15 | 16 | uint64_t rax_ret; 17 | uint64_t rdx_ret; 18 | 19 | uint64_t isStretCall; 20 | }; 21 | 22 | void MAInvocationCall(struct RawArguments *); 23 | void MAInvocationForward(void); 24 | void MAInvocationForwardStret(void); 25 | -------------------------------------------------------------------------------- /MAInvocation-asm.s: -------------------------------------------------------------------------------- 1 | 2 | .globl _MAInvocationCall 3 | _MAInvocationCall: 4 | 5 | // Save and set up frame pointer 6 | pushq %rbp 7 | movq %rsp, %rbp 8 | 9 | // Save r12-r15 so we can use them 10 | pushq %r12 11 | pushq %r13 12 | pushq %r14 13 | pushq %r15 14 | 15 | // Move the struct RawArguments into r12 so we can mess with rdi 16 | mov %rdi, %r12 17 | 18 | // Save the current stack pointer to r15 before messing with it 19 | mov %rsp, %r15 20 | 21 | // Copy stack arguments to the stack 22 | 23 | // Put the number of arguments into r10 24 | movq 56(%r12), %r10 25 | 26 | // Put the amount of stack space needed into r11 (# args << 3) 27 | movq %r10, %r11 28 | shlq $3, %r11 29 | 30 | // Put the stack argument pointer into r13 31 | movq 64(%r12), %r13 32 | 33 | // Move the stack down 34 | subq %r11, %rsp 35 | 36 | // Align the stack 37 | andq $-0x10, %rsp 38 | 39 | // Track the current argument number, start at 0 40 | movq $0, %r14 41 | 42 | // Copy loop 43 | stackargs_loop: 44 | 45 | // Stop the loop when r14 == r10 (current offset equals stack space needed 46 | cmpq %r14, %r10 47 | je done 48 | 49 | // Copy the current argument (r13[r14]) to the current stack slot 50 | movq 0(%r13, %r14, 8), %rdi 51 | movq %rdi, 0(%rsp, %r14, 8) 52 | 53 | // Increment the current argument number 54 | inc %r14 55 | 56 | // Back to the top of the loop 57 | jmp stackargs_loop 58 | 59 | done: 60 | 61 | // Copy registers over 62 | movq 8(%r12), %rdi 63 | movq 16(%r12), %rsi 64 | movq 24(%r12), %rdx 65 | movq 32(%r12), %rcx 66 | movq 40(%r12), %r8 67 | movq 48(%r12), %r9 68 | 69 | // Call the function pointer 70 | callq *(%r12) 71 | 72 | // Copy the result registers into the args struct 73 | movq %rax, 72(%r12) 74 | movq %rdx, 80(%r12) 75 | 76 | // Restore the stack pointer 77 | mov %r15, %rsp 78 | 79 | // Restore r12-15 for the caller 80 | popq %r15 81 | popq %r14 82 | popq %r13 83 | popq %r12 84 | 85 | // Restore the frame pointer and return 86 | leave 87 | ret 88 | 89 | 90 | .globl _MAInvocationForwardStret 91 | _MAInvocationForwardStret: 92 | 93 | // Set %r10 to indicate that this is a stret call 94 | movq $1, %r10 95 | 96 | // Jump to the common handler 97 | jmp _MAInvocationForwardCommon 98 | 99 | 100 | .globl _MAInvocationForward 101 | _MAInvocationForward: 102 | 103 | // Set %r10 to indicate that this is a normal call 104 | movq $0, %r10 105 | 106 | // Jump to the common handler 107 | jmp _MAInvocationForwardCommon 108 | 109 | 110 | .globl _MAInvocationForwardCommon 111 | _MAInvocationForwardCommon: 112 | 113 | // Calculate the location of the stack arguments, which are at rsp + 8 114 | movq %rsp, %r11 115 | addq $8, %r11 116 | 117 | // Save and set up frame pointer 118 | pushq %rbp 119 | movq %rsp, %rbp 120 | 121 | // Push RawArguments components one by one 122 | 123 | // Push isStretCall, which is the value of r10 124 | pushq %r10 125 | 126 | // Push a dummy rax and rdx 127 | pushq $0 128 | pushq $0 129 | 130 | // Push stackArgs pointer, which is in r11 131 | pushq %r11 132 | 133 | // Push a dummy stackArgsCount 134 | pushq $0 135 | 136 | // Push argument registers 137 | pushq %r9 138 | pushq %r8 139 | pushq %rcx 140 | pushq %rdx 141 | pushq %rsi 142 | pushq %rdi 143 | 144 | // Push a dummy fptr 145 | pushq $0 146 | 147 | // Save the pointer to the newly constructed struct to pass it to the C function 148 | movq %rsp, %rdi 149 | 150 | // Also save it in r12 so we can get to it afterwards 151 | movq %rdi, %r12 152 | 153 | // Align the stack 154 | andq $-0x10, %rsp 155 | 156 | // Call into C 157 | callq _MAInvocationForwardC 158 | 159 | // Copy the return value out of the RawArguments 160 | movq 72(%r12), %rax 161 | movq 80(%r12), %rdx 162 | 163 | // Restore the frame pointer and return 164 | leave 165 | ret 166 | 167 | -------------------------------------------------------------------------------- /MAInvocation.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface MAInvocation : NSObject 4 | 5 | + (MAInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig; 6 | 7 | - (NSMethodSignature *)methodSignature; 8 | 9 | - (void)retainArguments; 10 | - (BOOL)argumentsRetained; 11 | 12 | - (id)target; 13 | - (void)setTarget:(id)target; 14 | 15 | - (SEL)selector; 16 | - (void)setSelector:(SEL)selector; 17 | 18 | - (void)getReturnValue:(void *)retLoc; 19 | - (void)setReturnValue:(void *)retLoc; 20 | 21 | - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx; 22 | - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; 23 | 24 | - (void)invoke; 25 | - (void)invokeWithTarget:(id)target; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /MAInvocation.m: -------------------------------------------------------------------------------- 1 | #import "MAInvocation.h" 2 | 3 | #import "MAInvocation-asm.h" 4 | 5 | #import 6 | 7 | 8 | enum TypeClassification 9 | { 10 | TypeObject, 11 | TypeBlock, 12 | TypeCString, 13 | TypeInteger, 14 | TypeTwoIntegers, 15 | TypeEmptyStruct, 16 | TypeStruct, 17 | TypeOther 18 | }; 19 | 20 | @implementation MAInvocation { 21 | NSMethodSignature *_sig; 22 | struct RawArguments _raw; 23 | BOOL _argumentsRetained; 24 | void *_stretBuffer; 25 | } 26 | 27 | + (void)initialize 28 | { 29 | objc_setForwardHandler(MAInvocationForward, MAInvocationForwardStret); 30 | } 31 | 32 | + (NSInvocation *)invocationWithMethodSignature: (NSMethodSignature *)sig 33 | { 34 | return [[[self alloc] initWithMethodSignature: sig] autorelease]; 35 | } 36 | 37 | - (id)initWithMethodSignature: (NSMethodSignature *)sig 38 | { 39 | if((self = [super init])) 40 | { 41 | _sig = [sig retain]; 42 | 43 | _raw.isStretCall = [self isStretReturn]; 44 | 45 | NSUInteger argsCount = [sig numberOfArguments]; 46 | if(_raw.isStretCall) 47 | argsCount++; 48 | 49 | if(argsCount > 6) 50 | { 51 | _raw.stackArgsCount = argsCount - 6; 52 | _raw.stackArgs = calloc(argsCount - 6, sizeof(*_raw.stackArgs)); 53 | } 54 | } 55 | return self; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | if(_argumentsRetained) 61 | { 62 | [self iterateRetainableArguments: ^(NSUInteger idx, id obj, id block, char *cstr) { 63 | [obj release]; 64 | [block release]; 65 | free(cstr); 66 | }]; 67 | } 68 | 69 | [_sig release]; 70 | free(_raw.stackArgs); 71 | 72 | [super dealloc]; 73 | } 74 | 75 | - (NSString *)description 76 | { 77 | NSMutableArray *stackArgsStrings = [NSMutableArray array]; 78 | for(NSUInteger i = 0; i < _raw.stackArgsCount; i++) 79 | [stackArgsStrings addObject: [NSString stringWithFormat: @"%llx", _raw.stackArgs[i]]]; 80 | NSString *stackArgsString = [stackArgsStrings componentsJoinedByString: @" "]; 81 | return [NSString stringWithFormat: @"<%@ %p: rdi=%llx rsi=%llx rdx=%llx rcx=%llx r8=%llx r9=%llx stackArgs=%p(%llx)[%@] rax_ret=%llx rdx_ret=%llx isStretCall=%llx>", [self class], self, _raw.rdi, _raw.rsi, _raw.rdx, _raw.rcx, _raw.r8, _raw.r9, _raw.stackArgs, _raw.stackArgsCount, stackArgsString, _raw.rax_ret, _raw.rdx_ret, _raw.isStretCall]; 82 | } 83 | 84 | - (NSMethodSignature *)methodSignature 85 | { 86 | return _sig; 87 | } 88 | 89 | - (void)retainArguments 90 | { 91 | if(_argumentsRetained) 92 | return; 93 | 94 | [self iterateRetainableArguments: ^(NSUInteger idx, id obj, id block, char *cstr) { 95 | if(obj) 96 | { 97 | [obj retain]; 98 | } 99 | else if(block) 100 | { 101 | block = [block copy]; 102 | [self setArgument: &block atIndex: idx]; 103 | } 104 | else if(cstr) 105 | { 106 | if(cstr != NULL) 107 | cstr = strdup(cstr); 108 | [self setArgument: &cstr atIndex: idx]; 109 | } 110 | }]; 111 | _argumentsRetained = YES; 112 | } 113 | 114 | - (BOOL)argumentsRetained 115 | { 116 | return _argumentsRetained; 117 | } 118 | 119 | - (id)target 120 | { 121 | id target; 122 | [self getArgument: &target atIndex: 0]; 123 | return target; 124 | } 125 | 126 | - (void)setTarget: (id)target 127 | { 128 | [self setArgument: &target atIndex: 0]; 129 | } 130 | 131 | - (SEL)selector 132 | { 133 | SEL sel; 134 | [self getArgument: &sel atIndex: 1]; 135 | return sel; 136 | } 137 | 138 | - (void)setSelector: (SEL)selector 139 | { 140 | [self setArgument: &selector atIndex: 1]; 141 | } 142 | 143 | - (void)getReturnValue: (void *)retLoc 144 | { 145 | NSUInteger size = [self returnValueSize]; 146 | memcpy(retLoc, [self returnValuePtr], size); 147 | } 148 | 149 | - (void)setReturnValue: (void *)retLoc 150 | { 151 | NSUInteger size = [self returnValueSize]; 152 | memcpy([self returnValuePtr], retLoc, size); 153 | } 154 | 155 | - (void)getArgument: (void *)argumentLocation atIndex: (NSInteger)idx 156 | { 157 | NSInteger rawArgumentIndex = idx; 158 | if(_raw.isStretCall) 159 | rawArgumentIndex++; 160 | 161 | uint64_t *src = [self argumentPointerAtIndex: rawArgumentIndex]; 162 | assert(src); 163 | 164 | NSUInteger size = [self sizeAtIndex: idx]; 165 | memcpy(argumentLocation, src, size); 166 | } 167 | 168 | - (void)setArgument: (void *)argumentLocation atIndex: (NSInteger)idx 169 | { 170 | NSInteger rawArgumentIndex = idx; 171 | if(_raw.isStretCall) 172 | rawArgumentIndex++; 173 | 174 | uint64_t *dest = [self argumentPointerAtIndex: rawArgumentIndex]; 175 | assert(dest); 176 | 177 | enum TypeClassification c = [self classifyArgumentAtIndex: idx]; 178 | if(_argumentsRetained && c == TypeObject) 179 | { 180 | id old = *(id *)dest; 181 | *(id *)dest = [*(id *)argumentLocation retain]; 182 | [old release]; 183 | } 184 | else if(_argumentsRetained && c == TypeBlock) 185 | { 186 | id old = *(id *)dest; 187 | *(id *)dest = [*(id *)argumentLocation copy]; 188 | [old release]; 189 | } 190 | else if(_argumentsRetained && c == TypeCString) 191 | { 192 | char *old = *(char **)dest; 193 | 194 | char *cstr = *(char **)argumentLocation; 195 | if(cstr != NULL) 196 | cstr = strdup(cstr); 197 | *(char **)dest = cstr; 198 | 199 | free(old); 200 | } 201 | else 202 | { 203 | NSUInteger size = [self sizeAtIndex: idx]; 204 | memcpy(dest, argumentLocation, size); 205 | } 206 | } 207 | 208 | - (void)invoke 209 | { 210 | [self invokeWithTarget: [self target]]; 211 | } 212 | 213 | - (void)invokeWithTarget: (id)target 214 | { 215 | [self setTarget: target]; 216 | _raw.fptr = [target methodForSelector: [self selector]]; 217 | if(_raw.isStretCall) 218 | _raw.rdi = (uint64_t)[self returnValuePtr]; 219 | MAInvocationCall(&_raw); 220 | } 221 | 222 | #pragma mark Private 223 | 224 | - (uint64_t *)argumentPointerAtIndex: (NSInteger)idx 225 | { 226 | uint64_t *ptr = NULL; 227 | if(idx == 0) 228 | ptr = &_raw.rdi; 229 | if(idx == 1) 230 | ptr = &_raw.rsi; 231 | if(idx == 2) 232 | ptr = &_raw.rdx; 233 | if(idx == 3) 234 | ptr = &_raw.rcx; 235 | if(idx == 4) 236 | ptr = &_raw.r8; 237 | if(idx == 5) 238 | ptr = &_raw.r9; 239 | if(idx >= 6) 240 | ptr = _raw.stackArgs + idx - 6; 241 | return ptr; 242 | } 243 | 244 | - (NSUInteger)sizeAtIndex: (NSInteger)idx 245 | { 246 | return [self sizeOfType: [_sig getArgumentTypeAtIndex: idx]]; 247 | } 248 | 249 | - (NSUInteger)returnValueSize 250 | { 251 | return [self sizeOfType: [_sig methodReturnType]]; 252 | } 253 | 254 | - (NSUInteger)sizeOfType: (const char *)type 255 | { 256 | NSUInteger size; 257 | NSGetSizeAndAlignment(type, &size, NULL); 258 | return size; 259 | } 260 | 261 | - (void)iterateRetainableArguments: (void (^)(NSUInteger idx, id obj, id block, char *cstr))block 262 | { 263 | for(NSUInteger i = 0; i < [_sig numberOfArguments]; i++) 264 | { 265 | enum TypeClassification c = [self classifyArgumentAtIndex: i]; 266 | if(c == TypeObject || c == TypeBlock) 267 | { 268 | id arg; 269 | [self getArgument: &arg atIndex: i]; 270 | 271 | id o = c == TypeObject ? arg : nil; 272 | id b = c == TypeBlock ? arg : nil; 273 | block(i, o, b, NULL); 274 | } 275 | else if(c == TypeCString) 276 | { 277 | char *arg; 278 | [self getArgument: &arg atIndex: i]; 279 | 280 | block(i, nil, nil, arg); 281 | } 282 | } 283 | } 284 | 285 | - (enum TypeClassification)classifyArgumentAtIndex: (NSUInteger)idx 286 | { 287 | return [self classifyType: [_sig getArgumentTypeAtIndex: idx]]; 288 | } 289 | 290 | - (enum TypeClassification)classifyType: (const char *)type 291 | { 292 | const char *idType = @encode(id); 293 | const char *blockType = @encode(void (^)(void)); 294 | const char *charPtrType = @encode(char *); 295 | if(strcmp(type, idType) == 0) 296 | return TypeObject; 297 | if(strcmp(type, blockType) == 0) 298 | return TypeBlock; 299 | if(strcmp(type, charPtrType) == 0) 300 | return TypeCString; 301 | 302 | char intTypes[] = { @encode(signed char)[0], @encode(unsigned char)[0], @encode(short)[0], @encode(unsigned short)[0], @encode(int)[0], @encode(unsigned int)[0], @encode(long)[0], @encode(unsigned long)[0], @encode(long long)[0], @encode(unsigned long long)[0], '?', '^', 0 }; 303 | if(strchr(intTypes, type[0])) 304 | return TypeInteger; 305 | 306 | if(type[0] == '{') 307 | return [self classifyStructType: type]; 308 | 309 | return TypeOther; 310 | } 311 | 312 | - (enum TypeClassification)classifyStructType: (const char *)type 313 | { 314 | __block enum TypeClassification structClassification = TypeEmptyStruct; 315 | [self enumerateStructElementTypes: type block: ^(const char *type) { 316 | enum TypeClassification elementClassification = [self classifyType: type]; 317 | if(structClassification == TypeEmptyStruct) 318 | structClassification = elementClassification; 319 | else if([self isIntegerClass: structClassification] && [self isIntegerClass: elementClassification]) 320 | structClassification = TypeTwoIntegers; 321 | else 322 | structClassification = TypeStruct; 323 | }]; 324 | return structClassification; 325 | } 326 | 327 | - (BOOL)isIntegerClass: (enum TypeClassification)classification 328 | { 329 | return classification == TypeObject || classification == TypeBlock || classification == TypeCString || classification == TypeInteger; 330 | } 331 | 332 | - (void)enumerateStructElementTypes: (const char *)type block: (void (^)(const char *type))block 333 | { 334 | const char *equals = strchr(type, '='); 335 | const char *cursor = equals + 1; 336 | while(*cursor != '}') 337 | { 338 | block(cursor); 339 | cursor = NSGetSizeAndAlignment(cursor, NULL, NULL); 340 | } 341 | } 342 | 343 | - (BOOL)isStretReturn 344 | { 345 | return [self classifyType: [_sig methodReturnType]] == TypeStruct; 346 | } 347 | 348 | - (void *)returnValuePtr 349 | { 350 | if(_raw.isStretCall) 351 | { 352 | if(_stretBuffer == NULL) 353 | _stretBuffer = calloc(1, [self returnValueSize]); 354 | return _stretBuffer; 355 | } 356 | else 357 | { 358 | return &_raw.rax_ret; 359 | } 360 | } 361 | 362 | void MAInvocationForwardC(struct RawArguments *r) 363 | { 364 | id obj; 365 | SEL sel; 366 | 367 | if(r->isStretCall) 368 | { 369 | obj = (id)r->rsi; 370 | sel = (SEL)r->rdx; 371 | } 372 | else 373 | { 374 | obj = (id)r->rdi; 375 | sel = (SEL)r->rsi; 376 | } 377 | 378 | NSMethodSignature *sig = [obj methodSignatureForSelector: sel]; 379 | 380 | MAInvocation *inv = [[MAInvocation alloc] initWithMethodSignature: sig]; 381 | inv->_raw.rdi = r->rdi; 382 | inv->_raw.rsi = r->rsi; 383 | inv->_raw.rdx = r->rdx; 384 | inv->_raw.rcx = r->rcx; 385 | inv->_raw.r8 = r->r8; 386 | inv->_raw.r9 = r->r9; 387 | 388 | memcpy(inv->_raw.stackArgs, r->stackArgs, inv->_raw.stackArgsCount * sizeof(uint64_t)); 389 | 390 | [obj forwardInvocation: (id)inv]; 391 | 392 | r->rax_ret = inv->_raw.rax_ret; 393 | r->rdx_ret = inv->_raw.rdx_ret; 394 | 395 | if(r->isStretCall && inv->_stretBuffer) 396 | { 397 | memcpy((void *)r->rdi, inv->_stretBuffer, [inv returnValueSize]); 398 | } 399 | 400 | [inv release]; 401 | } 402 | 403 | @end 404 | -------------------------------------------------------------------------------- /tester.m: -------------------------------------------------------------------------------- 1 | // clang -framework Foundation -g -W -Wall -Wno-unused-parameter MAInvocation.m MAInvocation-asm.s tester.m 2 | 3 | #import "MAInvocation.h" 4 | 5 | #import 6 | 7 | 8 | static unsigned gFailureCount; 9 | 10 | static void Test(const char *name, void (*f)(void)) 11 | { 12 | @autoreleasepool 13 | { 14 | gFailureCount = 0; 15 | fprintf(stderr, "Testing %s...\n", name); 16 | f(); 17 | 18 | if(gFailureCount > 0) 19 | fprintf(stderr, "FAILED: %u failures\n", gFailureCount); 20 | else 21 | fprintf(stderr, "Success\n"); 22 | } 23 | } 24 | 25 | static void Assert(const char *file, int line, const char *name, _Bool cond, NSArray *toLog) 26 | { 27 | if(!cond) 28 | { 29 | gFailureCount++; 30 | const char *logString = ""; 31 | if([toLog count] > 0) 32 | { 33 | NSMutableString *s = [NSMutableString string]; 34 | for(id obj in toLog) 35 | [s appendFormat: @" %@", obj]; 36 | logString = [s UTF8String]; 37 | } 38 | fprintf(stderr, "%s: %d: assertion failed: %s%s\n", file, line, name, logString); 39 | } 40 | } 41 | 42 | #define TEST(f) Test(#f, f) 43 | #define ASSERT(cond, ...) Assert(__FILE__, __LINE__, #cond, cond, @[ __VA_ARGS__ ]) 44 | 45 | #define ARG(type, index, invocation) ^{ type temp; [invocation getArgument: &temp atIndex: (index)]; return temp; }() 46 | 47 | struct BigStruct { int a, b, c, d, e, f, g, h, i, j; }; 48 | 49 | @interface TestClass : NSObject { 50 | @public 51 | SEL _calledSelector; 52 | NSArray *_calledArguments; 53 | } 54 | 55 | - (struct BigStruct)dummyStret; 56 | 57 | @end 58 | 59 | @implementation TestClass 60 | 61 | - (void)dealloc 62 | { 63 | [_calledArguments release]; 64 | 65 | [super dealloc]; 66 | } 67 | 68 | #define RECORD(...) [self recordCall: _cmd args: @[ __VA_ARGS__ ]] 69 | 70 | - (void)recordCall: (SEL)sel args: (NSArray *)args 71 | { 72 | _calledSelector = sel; 73 | [args retain]; 74 | [_calledArguments release]; 75 | _calledArguments = args; 76 | } 77 | 78 | - (id)retain 79 | { 80 | RECORD(); 81 | return [super retain]; 82 | } 83 | 84 | - (void)empty 85 | { 86 | RECORD(); 87 | } 88 | 89 | - (void)intArg: (int)x 90 | { 91 | RECORD(@(x)); 92 | } 93 | 94 | - (void)lotsOfIntArgs: (int)a : (int)b : (int)c : (int)d : (int)e : (int)f : (int)g : (int)h : (int)i : (int)j 95 | { 96 | RECORD(@(a), @(b), @(c), @(d), @(e), @(f), @(g), @(h), @(i), @(j)); 97 | } 98 | 99 | - (int)returnInt 100 | { 101 | RECORD(); 102 | return 42; 103 | } 104 | 105 | - (NSRange)returnNSRange 106 | { 107 | RECORD(); 108 | return NSMakeRange(1111, 2222); 109 | } 110 | 111 | - (void)objArgs: a : b : c : d : e : f : g : h : i : j 112 | { 113 | RECORD(a, b, c, d, e, f, g, h, i, j); 114 | } 115 | 116 | - (void)objArg: a : b 117 | { 118 | RECORD(a, b); 119 | } 120 | 121 | - (struct BigStruct)dummyStret 122 | { 123 | return (struct BigStruct){ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 124 | } 125 | 126 | - (void)cString: (char *)param 127 | { 128 | } 129 | 130 | - (struct BigStruct)stret: a : b : c : d 131 | { 132 | RECORD(a, b, c, d); 133 | return (struct BigStruct){ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 134 | } 135 | 136 | @end 137 | 138 | 139 | @interface GenericForwarder 140 | 141 | + (id)alloc; 142 | - (void)free; 143 | - (id)id; 144 | - (void)setMethodSignature: (NSMethodSignature *)sig; 145 | - (void)setInvocationHandler: (void (^)(MAInvocation *inv))handler; 146 | 147 | @end 148 | 149 | @implementation GenericForwarder 150 | { 151 | Class isa; 152 | NSMethodSignature *_sig; 153 | void (^_invocationHandler)(MAInvocation *); 154 | } 155 | 156 | + (id)alloc 157 | { 158 | GenericForwarder *obj = calloc(1, class_getInstanceSize(self)); 159 | obj->isa = self; 160 | return obj; 161 | } 162 | 163 | - (void)free 164 | { 165 | [_sig release]; 166 | [_invocationHandler release]; 167 | free(self); 168 | } 169 | 170 | - (id)id 171 | { 172 | return self; 173 | } 174 | 175 | - (void)setMethodSignature: (NSMethodSignature *)sig 176 | { 177 | [sig retain]; 178 | [_sig release]; 179 | _sig = sig; 180 | } 181 | 182 | - (void)setInvocationHandler: (void (^)(MAInvocation *inv))handler 183 | { 184 | handler = [handler copy]; 185 | [_invocationHandler release]; 186 | _invocationHandler = handler; 187 | } 188 | 189 | - (NSMethodSignature *)methodSignatureForSelector: (SEL)sel 190 | { 191 | return _sig; 192 | } 193 | 194 | - (void)forwardInvocation: (MAInvocation *)inv 195 | { 196 | _invocationHandler(inv); 197 | } 198 | 199 | @end 200 | 201 | 202 | static void Simple(void) 203 | { 204 | TestClass *obj = [[TestClass alloc] init]; 205 | SEL sel = @selector(empty); 206 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 207 | [inv setTarget: obj]; 208 | [inv setSelector: sel]; 209 | [inv invoke]; 210 | ASSERT(obj->_calledSelector == @selector(empty), NSStringFromSelector(obj->_calledSelector)); 211 | ASSERT([obj->_calledArguments isEqual: @[]], obj->_calledArguments); 212 | [obj release]; 213 | } 214 | 215 | static void Argument(void) 216 | { 217 | TestClass *obj = [[TestClass alloc] init]; 218 | SEL sel = @selector(intArg:); 219 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 220 | [inv setTarget: obj]; 221 | [inv setSelector: sel]; 222 | [inv setArgument: &(int){ 42 } atIndex: 2]; 223 | [inv invoke]; 224 | ASSERT(obj->_calledSelector == @selector(intArg:), NSStringFromSelector(obj->_calledSelector)); 225 | ASSERT([obj->_calledArguments isEqual: @[ @42 ]], obj->_calledArguments); 226 | [obj release]; 227 | } 228 | 229 | static void LotsOfArguments(void) 230 | { 231 | TestClass *obj = [[TestClass alloc] init]; 232 | SEL sel = @selector(lotsOfIntArgs::::::::::); 233 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 234 | [inv setTarget: obj]; 235 | [inv setSelector: sel]; 236 | for(int i = 0; i < 10; i++) 237 | [inv setArgument: &(int){ i } atIndex: i + 2]; 238 | [inv invoke]; 239 | ASSERT(obj->_calledSelector == @selector(lotsOfIntArgs::::::::::), NSStringFromSelector(obj->_calledSelector)); 240 | ASSERT([obj->_calledArguments isEqual: (@[ @0, @1, @2, @3, @4, @5, @6, @7, @8, @9 ])], obj->_calledArguments); 241 | [obj release]; 242 | } 243 | 244 | static void ReturnValue(void) 245 | { 246 | TestClass *obj = [[TestClass alloc] init]; 247 | SEL sel = @selector(returnInt); 248 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 249 | [inv setTarget: obj]; 250 | [inv setSelector: sel]; 251 | [inv invoke]; 252 | ASSERT(obj->_calledSelector == @selector(returnInt), NSStringFromSelector(obj->_calledSelector)); 253 | ASSERT([obj->_calledArguments isEqual: @[]], obj->_calledArguments); 254 | 255 | int val; 256 | [inv getReturnValue: &val]; 257 | ASSERT(val == 42, @(val)); 258 | 259 | [obj release]; 260 | } 261 | 262 | static void ReturnSmallStruct(void) 263 | { 264 | TestClass *obj = [[TestClass alloc] init]; 265 | SEL sel = @selector(returnNSRange); 266 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 267 | [inv setTarget: obj]; 268 | [inv setSelector: sel]; 269 | [inv invoke]; 270 | ASSERT(obj->_calledSelector == @selector(returnNSRange), NSStringFromSelector(obj->_calledSelector)); 271 | ASSERT([obj->_calledArguments isEqual: @[]], obj->_calledArguments); 272 | 273 | NSRange r; 274 | [inv getReturnValue: &r]; 275 | ASSERT(r.location == 1111, @(r.location)); 276 | ASSERT(r.length == 2222, @(r.length)); 277 | 278 | [obj release]; 279 | } 280 | 281 | static void ObjectArguments(void) 282 | { 283 | TestClass *obj = [[TestClass alloc] init]; 284 | SEL sel = @selector(objArgs::::::::::); 285 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 286 | [inv setTarget: obj]; 287 | [inv setSelector: sel]; 288 | for(int i = 0; i < 10; i++) 289 | [inv setArgument: &(id){ @(i) } atIndex: i + 2]; 290 | [inv invoke]; 291 | ASSERT(obj->_calledSelector == @selector(objArgs::::::::::), NSStringFromSelector(obj->_calledSelector)); 292 | ASSERT([obj->_calledArguments isEqual: (@[ @0, @1, @2, @3, @4, @5, @6, @7, @8, @9 ])], obj->_calledArguments); 293 | [obj release]; 294 | } 295 | 296 | static void RetainArguments(void) 297 | { 298 | TestClass *obj = [[TestClass alloc] init]; 299 | TestClass *obj2 = [[TestClass alloc] init]; 300 | TestClass *obj3 = [[TestClass alloc] init]; 301 | SEL sel = @selector(objArg::); 302 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 303 | [inv setTarget: obj]; 304 | [inv setSelector: sel]; 305 | [inv setArgument: &obj2 atIndex: 2]; 306 | [inv retainArguments]; 307 | [inv setArgument: &obj3 atIndex: 3]; 308 | 309 | ASSERT(obj2->_calledSelector == @selector(retain), NSStringFromSelector(obj2->_calledSelector)); 310 | ASSERT(obj3->_calledSelector == @selector(retain), NSStringFromSelector(obj3->_calledSelector)); 311 | [obj release]; 312 | [obj2 release]; 313 | [obj3 release]; 314 | } 315 | 316 | static void ObjectReturn(void) 317 | { 318 | TestClass *obj = [[TestClass alloc] init]; 319 | SEL sel = @selector(self); 320 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 321 | [inv setTarget: obj]; 322 | [inv setSelector: sel]; 323 | [inv invoke]; 324 | 325 | id retval; 326 | [inv getReturnValue: &retval]; 327 | ASSERT(retval == obj, retval, obj); 328 | 329 | [obj release]; 330 | } 331 | 332 | static void BasicForwarding(void) 333 | { 334 | __block GenericForwarder *obj = [GenericForwarder alloc]; 335 | [obj setMethodSignature: [NSObject instanceMethodSignatureForSelector: @selector(self)]]; 336 | 337 | __block BOOL ran = NO; 338 | [obj setInvocationHandler: ^(MAInvocation *inv) { 339 | ASSERT(ARG(id, 0, inv) == obj); 340 | ASSERT(ARG(SEL, 1, inv) == @selector(self)); 341 | ran = YES; 342 | }]; 343 | [[obj id] self]; 344 | ASSERT(ran); 345 | [obj free]; 346 | } 347 | 348 | static void ForwardingLotsOfArguments(void) 349 | { 350 | __block GenericForwarder *obj = [GenericForwarder alloc]; 351 | 352 | [obj setMethodSignature: [TestClass instanceMethodSignatureForSelector: @selector(objArgs::::::::::)]]; 353 | [obj setInvocationHandler: ^(MAInvocation *inv) { 354 | for(int i = 0; i < 10; i++) 355 | ASSERT([ARG(id, i + 2, inv) isEqual: @(i)]); 356 | }]; 357 | [[obj id] objArgs: @0 : @1 : @2 : @3 : @4 : @5 : @6 : @7 : @8 : @9]; 358 | 359 | [obj setMethodSignature: [TestClass instanceMethodSignatureForSelector: @selector(lotsOfIntArgs::::::::::)]]; 360 | [obj setInvocationHandler: ^(MAInvocation *inv) { 361 | for(int i = 0; i < 10; i++) 362 | ASSERT(ARG(int, i + 2, inv) == i); 363 | }]; 364 | [[obj id] lotsOfIntArgs: 0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9]; 365 | 366 | [obj free]; 367 | } 368 | 369 | static void ForwardingReturn(void) 370 | { 371 | __block GenericForwarder *obj = [GenericForwarder alloc]; 372 | 373 | [obj setMethodSignature: [NSObject instanceMethodSignatureForSelector: @selector(self)]]; 374 | [obj setInvocationHandler: ^(MAInvocation *inv) { 375 | [inv setReturnValue: &obj]; 376 | }]; 377 | ASSERT([[obj id] self] == obj); 378 | 379 | [obj setMethodSignature: [NSString instanceMethodSignatureForSelector: @selector(stringByAppendingString:)]]; 380 | [obj setInvocationHandler: ^(MAInvocation *inv) { 381 | NSString *s = [@"Hello " stringByAppendingString: ARG(id, 2, inv)]; 382 | [inv setReturnValue: &s]; 383 | }]; 384 | ASSERT([[[obj id] stringByAppendingString: @"world"] isEqual: @"Hello world"]); 385 | 386 | [obj free]; 387 | } 388 | 389 | static void ForwardingReturnSmallStruct(void) 390 | { 391 | __block GenericForwarder *obj = [GenericForwarder alloc]; 392 | 393 | [obj setMethodSignature: [NSString instanceMethodSignatureForSelector: @selector(rangeOfString:)]]; 394 | [obj setInvocationHandler: ^(MAInvocation *inv) { 395 | NSRange r = { 42, 999 }; 396 | [inv setReturnValue: &r]; 397 | }]; 398 | 399 | NSRange r = [[obj id] rangeOfString: nil]; 400 | ASSERT(r.location == 42, @(r.location)); 401 | ASSERT(r.length == 999, @(r.length)); 402 | 403 | [obj free]; 404 | } 405 | 406 | static void ForwardingReturnBigStruct(void) 407 | { 408 | __block GenericForwarder *obj = [GenericForwarder alloc]; 409 | 410 | [obj setMethodSignature: [TestClass instanceMethodSignatureForSelector: @selector(dummyStret)]]; 411 | [obj setInvocationHandler: ^(MAInvocation *inv) { 412 | struct BigStruct r = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 413 | [inv setReturnValue: &r]; 414 | }]; 415 | 416 | struct BigStruct s = [[obj id] dummyStret]; 417 | for(int i = 0; i < 10; i++) 418 | ASSERT(((int *)&s)[i] == i); 419 | 420 | [obj free]; 421 | } 422 | 423 | static void StretCall(void) 424 | { 425 | TestClass *obj = [[TestClass alloc] init]; 426 | 427 | SEL sel = @selector(dummyStret); 428 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 429 | [inv setTarget: obj]; 430 | [inv setSelector: sel]; 431 | [inv invoke]; 432 | 433 | struct BigStruct s; 434 | [inv getReturnValue: &s]; 435 | 436 | for(int i = 0; i < 10; i++) 437 | ASSERT(((int *)&s)[i] == i); 438 | 439 | [obj release]; 440 | } 441 | 442 | static void CStringCopying(void) 443 | { 444 | TestClass *obj = [[TestClass alloc] init]; 445 | 446 | SEL sel = @selector(cString:); 447 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 448 | [inv setTarget: obj]; 449 | [inv setSelector: sel]; 450 | 451 | char *str = "abcdefg"; 452 | [inv setArgument: &str atIndex: 2]; 453 | 454 | char *outStr; 455 | [inv getArgument: &outStr atIndex: 2]; 456 | ASSERT(str == outStr); 457 | 458 | [inv retainArguments]; 459 | [inv getArgument: &outStr atIndex: 2]; 460 | ASSERT(str != outStr); 461 | ASSERT(strcmp(str, outStr) == 0); 462 | 463 | [inv setArgument: &str atIndex: 2]; 464 | [inv getArgument: &outStr atIndex: 2]; 465 | ASSERT(str != outStr); 466 | ASSERT(strcmp(str, outStr) == 0); 467 | 468 | [obj release]; 469 | } 470 | 471 | static void StretWithSixArguments(void) 472 | { 473 | TestClass *obj = [[TestClass alloc] init]; 474 | 475 | SEL sel = @selector(stret::::); 476 | MAInvocation *inv = [MAInvocation invocationWithMethodSignature: [obj methodSignatureForSelector: sel]]; 477 | [inv setTarget: obj]; 478 | [inv setSelector: sel]; 479 | 480 | for(int i = 0; i < 4; i++) 481 | { 482 | id arg = @(i); 483 | [inv setArgument: &arg atIndex: i + 2]; 484 | } 485 | 486 | [inv invoke]; 487 | 488 | ASSERT([obj->_calledArguments isEqual: (@[ @0, @1, @2, @3 ])], obj->_calledArguments); 489 | 490 | struct BigStruct s; 491 | [inv getReturnValue: &s]; 492 | for(int i = 0; i < 10; i++) 493 | ASSERT(((int *)&s)[i] == i); 494 | 495 | [obj release]; 496 | } 497 | 498 | int main(int argc, char **argv) 499 | { 500 | TEST(Simple); 501 | TEST(Argument); 502 | TEST(LotsOfArguments); 503 | TEST(ReturnValue); 504 | TEST(ReturnSmallStruct); 505 | TEST(ObjectArguments); 506 | TEST(RetainArguments); 507 | TEST(ObjectReturn); 508 | TEST(BasicForwarding); 509 | TEST(ForwardingLotsOfArguments); 510 | TEST(ForwardingReturn); 511 | TEST(ForwardingReturnSmallStruct); 512 | TEST(ForwardingReturnBigStruct); 513 | TEST(StretCall); 514 | TEST(CStringCopying); 515 | TEST(StretWithSixArguments); 516 | } 517 | --------------------------------------------------------------------------------