On Tue, 2021-03-30 at 16:36 +0530, Ankur Saini wrote:
> hello sir 
> 
> in my quest of finding a bug ( which ended up being a feature ) along
> with it’s source in the analyzer, I tested the code on these 2 code
> snippets and here’s how I went towards it 
> 
> (1)
> int main()
> {
>     int *ptr = (int *)malloc(sizeof(int));
>     return 0;
> }
> 
> link to running example (https://godbolt.org/z/1jGW1qYez <
> https://godbolt.org/z/1jGW1qYez>)
> 
> (2)
> int definaltly_main()
> {
>     int *ptr = (int *)malloc(sizeof(int));
>     return 0;
> }
> 
> link to running example (https://godbolt.org/z/bzjMYex4M <
> https://godbolt.org/z/bzjMYex4M>)
> 
> 
> where on second snipper analyzer is warning us about the leak as it
> should be, but in the first one it isn’t. 
> 
> and as the gimple representation of both looks exactly the same apart
> from function name, which made me think that either intentionally or
> unintentionally, analyzer handles case of main() differently than any
> other function.

Correct - the analyzer special-cases "main".

Specifically, in impl_region_model_context::on_state_leak, there's this
code:
  
https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/analyzer/engine.cc;h=d7866b5598b4fcb791ec6ff511dde9b7615e7794;hb=HEAD#l624

 624   /* Don't complain about leaks when returning from "main".  */
 625   if (m_enode_for_diag->get_supernode ()
 626       && m_enode_for_diag->get_supernode ()->return_p ())
 627     {
 628       tree fndecl = m_enode_for_diag->get_function ()->decl;
 629       if (id_equal (DECL_NAME (fndecl), "main"))
 630         {
 631           if (logger)
 632             logger->log ("not reporting leak from main");
 633           return;
 634         }
 635     }

on the grounds that for the resources that the analyzer currently
tracks, it doesn't matter if they aren't cleaned up as the process
exits, so we don't bother the user with a report about them.

> so while looking at it’s exploded graphs I found out that the only 2
> differences in them 
> 
> 1. There is one less exploded node(after node E-8) created in first
> one ( I earlier thought state merging or state pruning is taking
> place here but it isn’t because the results are not affected even
> after disabling those using  `-fno-analyzer-state-purge` and `-fno-
> analyzer-state-merge` options )

Well spotted.

I see that too.

For (1) EN 8, has 2 stmts, the label and the return, but for (2), it
splits them with EN: 8 having just the label, and the return split into
EN 9.

I was surprised by this and did some digging in gdb, for both (1) and
(2).  The reason seems to be rather arbitrary; specifically in (2),
stmt_requires_new_enode_p is returning true due to hitting this case:

2894      /* If we had a PREV_STMT with an unknown location, and this stmt
2895         has a known location, then if a state change happens here, it
2896         could be consolidated into PREV_STMT, giving us an event with
2897         no location.  Ensure that STMT gets its own exploded_node to
2898         avoid this.  */
2899      if (get_pure_location (prev_stmt->location) == UNKNOWN_LOCATION
2900          && get_pure_location (stmt->location) != UNKNOWN_LOCATION)
2901        return true;

and presumably it isn't hitting this for (1).

Specifically, where "stmt" is the return stmt and "prev_stmt" is the
label statement.

for (1):

(gdb) p /x stmt->location
$1 = 0x0
(gdb) p /x prev_stmt->location
$2 = 0x0

whereas for (2):

(gdb) p /x stmt->location
$5 = 0x800000f2
(gdb) p /x prev_stmt->location
$6 = 0x0

So with (1) the return stmt has UNKNOWN_LOCATION:

(gdb) call inform (stmt->location, "return stmt in (1)")
In function ‘main’:
cc1: note: return stmt in (1)

whereas for (2) the return stmt has a source location:

(gdb) call inform (stmt->location, "return stmt in (2)")
In function ‘definaltly_main’:
/tmp/2.c:6:12: note: return stmt in (2)
    6 |     return 0;
      |            ^

This slight difference in the recorded location of the return stmt for
the "main" vs non-"main" case affects the splitting of the nodes.

Athough a curiosity, I don't think this is significant.  (In theory one
could use a hardware watchpoint on stmt->location to track this
discrepancy down further, but I don't think it's important enough to
bother).


> 2. no diagnosis for malloc leak happening at the end of first one
> even though there exist a pointer in unchecked state at the end (
> according to the malloc state machine )

Correct - the analyzer specialcases "main" and ignores it, as noted
above.


> In quest to find the cause I started navigating through the source
> code of the analyser starting off with the run_checkers() function in
> engine.cc which looks like the entry point of the analyser ( found
> via the commit history of the analyzer ). But finally it ended at
> `impl_region_model_context::on_state_leak()` function where I found
> out that analyzer is intentionally skipping the leak report when it
> is in main. 

Sounds like you successfully tracked this down.

> This gave rise to some questions
> 
> 1. why does the analyzer make exceptions with the main() function ?

The user's attention is important - we don't want to spam the user with
unnecessary reports if we can help it.

> 2. even if it is not complaining about the leak then this still
> doesn’t explain the reason for one less exploded node in this case of
> main() function.

As above, it seems to be a side-effect of differences in source
locations.

> thanks
> 
> - Ankur

Hope this is helpful
Dave

Reply via email to