https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114594

            Bug ID: 114594
           Summary: Issues seen with -Wanalyzer-malloc-leak on
                    htop/XUtils.c: String_split
           Product: gcc
           Version: 14.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: analyzer
          Assignee: dmalcolm at gcc dot gnu.org
          Reporter: dmalcolm at gcc dot gnu.org
                CC: BenBE at geshi dot org
  Target Milestone: ---

Created attachment 57881
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=57881&action=edit
Reduced reproducer

User "BenBE2" on #gcc on IRC noted some issues with the attached file; see also
at https://godbolt.org/z/vKbhqMq4T

The analyzer reports a leak, arguably falsely:

<source>: In function 'xRealloc':
<source>:32:7: warning: leak of '<unknown>' [CWE-401] [-Wanalyzer-malloc-leak]
   32 |       free(ptr);
      |       ^~~~~~~~~
  'String_split': events 1-2
    |
    |   38 | char** String_split(const char* s, char sep, size_t* n) {
    |      |        ^~~~~~~~~~~~
    |      |        |
    |      |        (1) entry to 'String_split'
    |   39 |    const size_t rate = 10;
    |   40 |    char** out = xCalloc(rate, sizeof(char*));
    |      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |                 |
    |      |                 (2) calling 'xCalloc' from 'String_split'
    |
    +--> 'xCalloc': event 3
           |
           |   15 | void* xCalloc(size_t nmemb, size_t size) {
           |      |       ^~~~~~~
           |      |       |
           |      |       (3) entry to 'xCalloc'
           |
         'xCalloc': event 4
           |
           |   16 |    assert(nmemb > 0);
           |      |    ^~~~~~
           |      |    |
           |      |    (4) following 'true' branch (when 'nmemb != 0')...
           |
         'xCalloc': event 5
           |
           |   17 |    assert(size > 0);
           |      |    ^~~~~~
           |      |    |
           |      |    (5) ...to here
           |
         'xCalloc': event 6
           |
           |   17 |    assert(size > 0);
           |      |    ^~~~~~
           |      |    |
           |      |    (6) following 'true' branch (when 'size != 0')...
           |
         'xCalloc': events 7-11
           |
           |   18 |    if (SIZE_MAX / nmemb < size) {
           |      |       ~                 ^
           |      |       |                 |
           |      |       |                 (7) ...to here
           |      |       (8) following 'false' branch...
           |......
           |   21 |    void* data = calloc(nmemb, size);
           |      |                 ~~~~~~~~~~~~~~~~~~~
           |      |                 |
           |      |                 (9) ...to here
           |   22 |    if (!data) {
           |      |       ~                  
           |      |       |
           |      |       (10) following 'false' branch (when 'data' is
non-NULL)...
           |......
           |   25 |    return data;
           |      |           ~~~~           
           |      |           |
           |      |           (11) ...to here
           |
    <------+
    |
  'String_split': events 12-13
    |
    |   40 |    char** out = xCalloc(rate, sizeof(char*));
    |      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |                 |
    |      |                 (12) returning to 'String_split' from 'xCalloc'
    |......
    |   44 |    while ((where = strchr(s, sep)) != NULL) {
    |      |                    ~~~~~~~~~~~~~~
    |      |                    |
    |      |                    (13) when 'strchr' returns non-NULL
    |
  'String_split': events 14-16
    |
    |   44 |    while ((where = strchr(s, sep)) != NULL) {
    |      |                                    ^
    |      |                                    |
    |      |                                    (14) following 'true' branch
(when 'where' is non-NULL)...
    |   45 |       size_t size = (size_t)(where - s);
    |      |                             ~~~~~~~~~~~
    |      |                                    |
    |      |                                    (15) ...to here
    |   46 |       out[ctr] = xStrndup(s, size);
    |      |                  ~~~~~~~~~~~~~~~~~  
    |      |                  |
    |      |                  (16) calling 'xStrndup' from 'String_split'
    |
    +--> 'xStrndup': events 17-21
           |
           |   67 | char* xStrndup(const char* str, size_t len) {
           |      |       ^~~~~~~~
           |      |       |
           |      |       (17) entry to 'xStrndup'
           |   68 |    char* data = strndup(str, len);
           |      |                 ~~~~~~~~~~~~~~~~~
           |      |                 |
           |      |                 (18) allocated here
           |   69 |    if (!data) {
           |      |       ~
           |      |       |
           |      |       (19) assuming 'data' is non-NULL
           |      |       (20) following 'false' branch (when 'data' is
non-NULL)...
           |......
           |   72 |    return data;
           |      |           ~~~~
           |      |           |
           |      |           (21) ...to here
           |
    <------+
    |
  'String_split': events 22-25
    |
    |   44 |    while ((where = strchr(s, sep)) != NULL) {
    |      |                    ~~~~~~~~~~~~~~
    |      |                    |
    |      |                    (25) when 'strchr' returns NULL
    |   45 |       size_t size = (size_t)(where - s);
    |   46 |       out[ctr] = xStrndup(s, size);
    |      |                  ^~~~~~~~~~~~~~~~~
    |      |                  |
    |      |                  (22) returning to 'String_split' from 'xStrndup'
    |   47 |       ctr++;
    |   48 |       if (ctr == blocks) {
    |      |          ~        
    |      |          |
    |      |          (23) following 'false' branch (when 'ctr != blocks')...
    |......
    |   52 |       s += size + 1;
    |      |         ~~        
    |      |         |
    |      |         (24) ...to here
    |
  'String_split': events 26-30
    |
    |   44 |    while ((where = strchr(s, sep)) != NULL) {
    |      |                                    ^
    |      |                                    |
    |      |                                    (26) following 'false' branch
(when 'where' is NULL)...
    |......
    |   54 |    if (s[0] != '\0') {
    |      |       ~~~~~                         
    |      |       | |
    |      |       | (27) ...to here
    |      |       (28) following 'false' branch...
    |......
    |   58 |    out = xRealloc(out, sizeof(char*) * (ctr + 1));
    |      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    |      |          |                                  |
    |      |          |                                  (29) ...to here
    |      |          (30) calling 'xRealloc' from 'String_split'
    |
    +--> 'xRealloc': event 31
           |
           |   28 | void* xRealloc(void* ptr, size_t size) {
           |      |       ^~~~~~~~
           |      |       |
           |      |       (31) entry to 'xRealloc'
           |
         'xRealloc': event 32
           |
           |   29 |    assert(size > 0);
           |      |    ^~~~~~
           |      |    |
           |      |    (32) following 'true' branch (when 'size != 0')...
           |
         'xRealloc': events 33-37
           |
           |   30 |    void* data = realloc(ptr, size);
           |      |                 ^~~~~~~~~~~~~~~~~~
           |      |                 |
           |      |                 (33) ...to here
           |      |                 (34) when 'realloc' fails
           |   31 |    if (!data) {
           |      |       ~          
           |      |       |
           |      |       (35) following 'true' branch (when 'data' is NULL)...
           |   32 |       free(ptr);
           |      |       ~~~~~~~~~  
           |      |       |
           |      |       (36) ...to here
           |      |       (37) '<unknown>' leaks here; was allocated at (18)
           |

If I'm reading things right, at the free at (37) it's freeing a buffer
containing the last pointer to the allocation at (18).  But this is on an
error-handling path which is about to call "fail", which the analyzer knows
calls "abort".

So there are at least two issues here:
(a) difficult to read: yet another '<unknown>' in the report, making it hard to
decipher what the leaked thing being reported is.  
Probably instead of '<unknown>' it should say 'out[0]'.

Ideally could show a "path of last liveness", showing that:

  "ptr" aliases "out", out points-to (buffer allocated at (9), I think), which
points-to (buffer allocated at (18))

hence the free of ptr loses the last paths of liveness of the buffer allocated
at (18).

(b) Maybe we shouldn't bother reporting leaks on paths that are about to
unconditionally exit/abort, similar to the logic for leaks at the end of
"main".

Reply via email to