3

下面的两种方法都分配一个 NSString 并泄漏它。运行 XCode (4.6) Anaylzer 成功标记了 bar2 中的泄漏,但在 bar1 中没有提及它。

我不明白为什么。

在我的实际项目中,我们发现了一个泄漏,我们希望以一种明显的方式捕获它,就像 bar2 中的那样,但由于 bar1 中的行为相同,它没有被发现。

请帮我理解为什么。谢谢!

-(void)bar1
{
    NSString* foo = [[NSString alloc] initWithString:@"foo"];
    NSLog(@"%@", foo);

    for (int i=0; i<4; i++) {
    }
}

-(void)bar2
{
    NSString* foo = [[NSString alloc] initWithString:@"foo"];
    NSLog(@"%@", foo);
}

你们中的一些人提到静态字符串的情况是“过分做作的”。这个不那么做作的示例显示了相同的行为:

-(void)bar1
{
    NSString* foo = [[NSString alloc] initWithFormat:@"%d",rand()];
    NSLog(@"%@", foo);

    for (int i=0; i<4; i++) {
    }
}

-(void)bar2
{
    NSString* foo = [[NSString alloc] initWithFormat:@"%d",rand()];
    NSLog(@"%@", foo);
}

感谢那些指出迭代次数会产生影响的人。使用 3,它报告泄漏,使用 4,它不报告。这是一个没有死代码的新示例,只有迭代不同:

报告泄漏:

-(void)bar1
{
    int i=0;
    while (i<3) {
        i++;
    }

    NSString* foo = [[NSString alloc] initWithFormat:@"%d",i];
    NSLog(@"%@", foo);
}

不报告泄漏:

-(void)bar2
{
    int i=0;
    while (i<4) {
        i++;
    }

    NSString* foo = [[NSString alloc] initWithFormat:@"%d",i];
    NSLog(@"%@", foo);
}

我已经向 Apple 开具了 DTS 票证,因为在我看来,这个精致的示例清楚地表明这是 Analyzer 中的一个错误。

DTS 要求我使用https://bugreport.apple.com将其作为错误打开,我已经完成了。这是问题 ID 13491388。

2013 年 3 月 29 日更新:Apple 报告我的错误 13491388 是错误 11486907 的欺骗。但是,我无法打开或阅读有关错误 11486907 的任何内容,因此该信息完全无用。

苹果开发者支持失败:-(

4

2 回答 2

3

Clang 的静态分析器似乎是基于控制流的:报告总是“如果你遵循这个代码路径,就会发生这种坏事”。我最好的猜测是,这是 SA 的不同位之间的不良交互:

  • 泄漏检测器可能仅在下次写入变量或函数返回时才注意到泄漏。
  • 它通过一个循环的次数是有限制的(以避免永远进行搜索)——检查两次迭代应该足以检测到大多数循环错误。
  • 在达到迭代限制时,静态分析器“知道”它还不会退出循环(因为i仍然小于 4),所以它放弃了。

我怀疑减少迭代次数会导致检测到泄漏。

如果优化器确定变量在NSLog()返回后已失效并将此信息用于 SA,则在启用优化的情况下进行分析(我认为 Xcode 支持如果您编辑方案)可能会给出不同的结果。

您可能还可以调整报告问题时的保守程度(通过命令行选项,或直接从命令行运行 clang?),但如果不标记大量误报,这可能很难做到。

静态分析也不能替代像 Leaks 这样的泄漏检查器。

于 2013-03-16T03:24:56.480 回答
3

另一个答案是正确的。我想为后代提供更多细节。

检漏仪实际上确实注意到了这种泄漏。问题是泄漏被认为是低重要性的分析器结果,如果泄漏后的每条路径都以“接收器”结束(基本上是分析的异常终止),则不会报告泄漏。这是为了防止嘈杂或误报的泄漏报告;例如:

NSString *foo = [[NSString alloc] initWithString:@"foo"];
if (SOME_CONDITION) { 
    NSLog(@"OH NO!");
    exit(-1); 
} else { 
    [foo release]; 
}

这将产生一个泄漏报告,因为如果条件评估为真,但在exit运行之前,将会有一个泄漏报告,因为 foo 不再被引用但仍然拥有。(这段代码看起来很奇怪,但正常的断言也会触发完全相同的误报。)通过抑制(总是)导致接收器的路径上的泄漏,这里不会产生误报。

不幸的是,当分析器放弃时,通过“太多”次循环也会产生接收器。这由命令行参数控制-analyzer-max-loop;如果您通过-analyzer-max-loop 5了,那么您将获得带有示例代码的泄漏报告。

这也解释了为什么使用类似的东西rand()而不是4工作;在探索程序状态时,分析器会考虑我们没有通过循环的情况,一次,两次,三次等。任何更高的循环计数都会导致接收器,但由于并非所有路径都通过接收器,所以你仍然得到泄漏报告。(即分析器第一次看到i < rand()错误的路径,我们离开函数,因此报告泄漏。)

于 2013-03-23T15:04:13.100 回答