Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XCode (4.6) Analyzer - why is leak detected in one case and not the other?

Tags:

xcode

ios

Both methods below allocate an NSString and leak it. Running the XCode (4.6) Anaylzer successfully flags the leak in bar2, but makes no mention of it in bar1.

I do not undertand why.

In my real project, we found a leak that we would expect to be caught in an obvious way like the one in bar2, but it is not found because of the same behavior in bar1.

Please help me understand why. Thanks!

-(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);
}

Some of you have mentioned that the static string case is "over contrived". This less contrived example shows the same behavior:

-(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);
}

Thanks to those who pointed out that the number of iterations has an impact. With 3, it reports the leak, with 4 it does not. Here's an new example with no dead code, with only the differences in iterations:

reports the leak:

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

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

does not report the leak:

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

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

I've opened a DTS ticket with Apple, as this refined example, in my opinion, clearly demonstrates that this is a bug in Analyzer.

DTS asked me to open it as a bug with https://bugreport.apple.com wich I have done. It's Problem ID 13491388.

Update 3/29/2013: Apple reports that my bug 13491388 is a dupe of bug 11486907. However, I cannot open or read anything about bug 11486907, so that info is completely useless.

Apple developer support FAIL :-(

like image 553
Brad Edelman Avatar asked Dec 01 '25 12:12

Brad Edelman


2 Answers

Clang's static analyzer seems to be based on control flow: The report is always "if you follow this code path, this bad thing happens". My best guess is that it's an undesirable interaction between different bits of SA:

  • The leak detector might only notice the leak when the variable is next written to or the function returns.
  • There's a limit to how many times it will go through a loop (to avoid the search taking forever) — checking two iterations should be enough to detect most loop bugs.
  • At the point where it hits the iteration limit, the static analyzer "knows" that it won't exit the loop yet (because i is still less than 4), so it gives up.

I suspect reducing the iteration count causes the leak to be detected.

Doing analysis with optimizations enabled (I think Xcode supports that if you edit the scheme) might give different results if the optimizer determines that the variable is dead after NSLog() returns and uses this info for SA.

You might also be able to tune how conservative it is when reporting problems (via command-line options, or running clang directly from the command line?), but this may be difficult to do without it flagging up a lot of false positives.

Static analysis is also not a substitute for using a leak checker like Leaks.

like image 71
tc. Avatar answered Dec 04 '25 02:12

tc.


The other answer is right on track; I wanted to give some more details for posterity.

The leak detector actually does notice this leak. The problem is that leaks are considered low importance analyzer results, and are not reported if every path after the leak ends in a "sink" (basically, an abnormal termination of the analysis). This is to prevent noisy or false-positive leak reports; for example:

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

This would produce a leak report, because if the condition evaluates to true, but before exit runs, there would be a leak report since foo is no longer referenced but is still owned. (This code looks odd, but normal asserts would also trigger exactly the same false positive.) By suppressing leaks along paths that (always) lead to a sink, no false-positive is generated here.

Unfortunately, going through a loop "too many" times also produces a sink when the analyzer gives up. This is controlled by the command-line argument -analyzer-max-loop; if you pass -analyzer-max-loop 5 then you get the leak report with your example code.

This also explains why using something like rand() rather than 4 works; when exploring the program state, the analyzer considers the case where we go through the loop no times, one time, two times, three times, etc. Any higher loop count leads a sink, but since not all paths go through a sink, you still get the leak report. (i.e. the analyzer sees the path where i < rand() is false the first time and we leave the function, and so reports the leak.)

like image 24
Jesse Rusak Avatar answered Dec 04 '25 03:12

Jesse Rusak



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!