0

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!

Wingzero
  • 9,644
  • 10
  • 39
  • 80

1 Answers1

1

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

Kamil.S
  • 5,205
  • 2
  • 22
  • 51
  • Hey thanks! in my case, could I use __weak here? since I'm just using getArgument, and will finish all work before invoking to the real target. – Wingzero Feb 08 '22 at 02:20
  • I also tried in my code, after using __weak, the pointer it points to get re-allocated to another data, so I assume, __weak is working? – Wingzero Feb 08 '22 at 02:38
  • `__weak` is inappropriate because it would not behave correctly upon object deallocation. You are lucky (in a bad way unfortunately) with `NSTaggedPointerString *` which technically will never get deallocated, but you could run into problems if the argument would be object with standard memory lifecycle e.g dynamically allocated `NSString*` on the heap. I recommend sticking to what @newacct suggested in the linked answer comment. `__unsafe_unretained` is the cleanest solution imho. – Kamil.S Feb 08 '22 at 08:09
  • during my test, it seems __weak statKey will be deallocated after the release kicks in, though it's not nil, the memory seem recycled after invoking 'release' or autorelease pool pop, so, in my case, __weak is 'safe' except for the auto nil, right? because I'm not holding it as a property, but a local variable, auto nil means nothing outside the function. In my project, the argument will only be NSString, no other type, so statKey is like only used for audit. – Wingzero Feb 09 '22 at 01:15
  • I would not call it "safe" , it's an `unowned` reference disguised as `weak`. Still your understanding is correct. – Kamil.S Feb 09 '22 at 09:32