XCode(4.6)分析仪 - 为什么在一个案例中检测到泄漏而在另一个案例中没有检测到泄漏?

时间:2013-03-15 19:30:52

标签: ios xcode

下面的两种方法都会分配一个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门票,我认为这个精致的例子清楚地表明这是分析器中的一个错误。

DTS让我打开它作为我https://bugreport.apple.com的错误。这是问题ID 13491388.

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

Apple开发人员支持FAIL: - (

2 个答案:

答案 0 :(得分:3)

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

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

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

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

你可能还能够调整报告问题时的保守程度(通过命令行选项,或直接从命令行运行clang?),但如果没有标记很多错误,这可能很难做到阳性。

静态分析也不能替代使用Leaks等泄漏检查器。

答案 1 :(得分:3)

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

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

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

这会产生泄漏报告,因为如果条件的计算结果为true,但在exit运行之前,则会出现泄漏报告,因为foo不再被引用但仍然拥有。 (此代码看起来很奇怪,但正常断言也会触发完全相同的误报。)通过抑制(始终)导致接收器的路径泄漏,此处不会产生误报。

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

这也解释了为什么使用像rand()而不是4这样的东西;在探索程序状态时,分析器会考虑我们不经历一次,两次,三次等循环的情况。任何更高的循环计数都会导致接收器,但是因为不是所有路径通过水槽,你仍然得到泄漏报告。 (即分析器第一次看到i < rand()为假的路径,我们离开该功能,因此报告泄漏。)