Wingzero
Wingzero

Reputation: 9754

NSInvocation getArgument NSString/CFString will lead to bad access

I had a requirement, to use NSInvocation's

  • (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

to get the argument. The argument is primarily NSString, So, in my function:

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation retainArguments];
    NSString *biz;
   [invocation getArgument:&biz atIndex:2];
   NSString *statKey;
   [invocation getArgument:&statKey atIndex:3];
   getOriginalSelectorName:rawSelName blockArgCount:blockArgCount];
   allowed = [self allowPerformSensitiveSelector:rawSelector biz:[biz copy] statKey:[statKey copy]];
   ...
}

However I met a double free crash. After debugging, I found the issue is that,

because invocation is actually holding a __CFString object, which is malloced on heap, and the length is >= 10, e.g. '1234567890', so when I call [invocation getArgument:&statKey atIndex:3];, the statKey is written with the pointer to 1234567890:

for example,

(lldb) p statKey  // set by `getArgument:&statKey`
(__NSCFString *) $0 = 0x00000002839492e0 @"1234567890"
(lldb) mem read 0x00000002839492e0
0x2839492e0: 01 e9 be d8 a1 21 00 00 8c 07 00 00 04 00 00 00  .....!..........
0x2839492f0: 0a 31 32 33 34 35 36 37 38 39 30 00 00 00 00 00  .1234567890.....
(lldb) p statKey // outer one, passed from method parameters, resided in invocation
(__NSCFString *) $1 = 0x000000028372f780 @"1234567890"
(lldb) mem read 0x000000028372f780
0x28372f780: 01 e9 be d8 a1 21 00 00 ad 07 00 00 04 00 00 00  .....!..........
0x28372f790: e0 fd 97 83 02 00 00 00 0a 00 00 00 00 00 00 00  ................

So the newly NSString *statKey is actually is a pointer.

When the invocation is finished, I will met a crash, like a double free, because they both point to 0x21a1d8bee901

if the string is like [[NSMutableString alloc] initWithString:@'123456789'], even though this is a CFString, but when calling [invocation getArgument:&statKey atIndex:3], it will be

NSTaggedPointerString * @"123456789" 0x9c98d935e3d914c6.

So I assuem length of 10 of the string is the boundary.

So I want to ask, how do I fix this? I tried [statKey copy], or [invocation retainArguments];, not working. Thanks!

Upvotes: 0

Views: 188

Answers (2)

Wingzero
Wingzero

Reputation: 9754

[UPDATE] I know this is a very late reply, but it is also Apple that lately responded to me that, they have fixed the documentation about this topic (it takes TWO YEARS to fix the doc, yeah good job apple)

to quote:

The documentation has been updated, please verify here: https://developer.apple.com/documentation/foundation/nsinvocation/1437832-getreturnvalue https://developer.apple.com/documentation/foundation/nsinvocation/1437830-getargument

I could see the precise description with the recent Xcode and its doc that they have updated so we will know we need to handle the ownership by ourselves. There is a big warning that you can't simply miss.

Upvotes: 0

Kamil.S
Kamil.S

Reputation: 5543

It's most likely very similar problem to NSInvocation returns value but makes app crash with EXC_BAD_ACCESS but for [invocation getArgument:...] and not a return value.
What happens is NSInvocation method is unaware of the value-fit-in-pointer optimised underlying type (NSTaggedPointerString *) and as a result ARC attempts to release it.

Fix should be:

NSString __unsafe_unretained *statKey;
[invocation getArgument:&statKey atIndex:3];

or:

void *statKey;
[invocation getArgument:&statKey atIndex:3];

Similar problem also described here: https://stackoverflow.com/a/56604328/5329717

Upvotes: 1

Related Questions