NSInvocation getArgument NSString/CFString 会导致访问错误

NSInvocation getArgument NSString/CFString will lead to bad access

我有一个要求,使用 NSInvocation 的

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

获取参数。参数主要是 NSString,所以,在我的函数中:

- (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]];
   ...
}

然而我遇到了双免死机。调试后发现问题是,

因为调用实际上持有一个__CFString对象,它被分配在堆上,并且长度>=10,例如'1234567890',所以当我调用 [invocation getArgument:&statKey atIndex:3]; 时,statKey 是用指向 1234567890 的指针写入的:

例如,

(lldb) p statKey  // set by `getArgument:&statKey`
(__NSCFString *) [=12=] = 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 *)  = 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  ................

所以新的 NSString *statKey 实际上是一个指针。

当调用完成后,我会遇到一个crash,就像一个double free,因为它们都指向0x21a1d8bee901

如果字符串类似于 [[NSMutableString alloc] initWithString:@'123456789'],即使这是一个 CFString,但在调用 [invocation getArgument:&statKey atIndex:3] 时,它将是

NSTaggedPointerString * @"123456789" 0x9c98d935e3d914c6.

所以我假设字符串的长度为 10 是边界。

所以我想问一下,我该如何解决这个问题?我尝试了 [statKey copy] 或 [invocation retainArguments];,但没有用。谢谢!

这很可能与 NSInvocation returns value but makes app crash with EXC_BAD_ACCESS 非常相似,但对于 [invocation getArgument:...] 而不是 return 值。
发生的事情是 NSInvocation 方法不知道 value-fit-in-pointer 优化的底层类型(NSTaggedPointerString *),因此 ARC 尝试释放它。

修正应该是:

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

或:

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

这里也描述了类似的问题: