Edit: this is actually a known bug.
original question
I would love some help on this, because I just don't get it. Hopefully it's just something I don't understand.
I have a few functions to initialize and free memory of two types : a singular foo
type and a plural foos_list
type. Here is a full program reduced to the essential:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void *
xalloc (size_t len)
{
void *mem = malloc (len);
if (!mem)
{
fprintf (stderr, "can't allocated memory");
exit (1);
}
return mem;
}
void *
xrealloc (void *mem, size_t msize)
{
mem = realloc (mem, msize);
if (!mem)
{
fprintf (stderr, "can't allocate %zu bytes of memory
", msize);
exit (1);
}
return mem;
}
typedef struct {
char *name;
} foo;
typedef struct {
foo **foos;
size_t foos_len;
} foos_list;
foo *
new_foo ()
{
foo *f = xalloc (sizeof (foo));
f->name = NULL;
return f;
}
void
free_foo (foo *f)
{
if (!f) return;
if (f->name) free (f->name);
free (f);
}
foos_list *
new_foos_list ()
{
foos_list *l = xalloc (sizeof (foos_list));
l->foos = NULL;
l->foos_len = 0;
return l;
}
void
free_foos_list (foos_list *l)
{
if (!l) return;
if (l->foos)
{
for (size_t i = 0; i < l->foos_len; i++)
free_foo (l->foos[i]);
free (l->foos);
}
free (l);
}
int main()
{
}
If I try to compile this, gcc throws an error of analyzer-use-after-free and one of analyzer-double-free.
$ gcc -Wall -Werror -fanalyzer -o test main.c
main.c: In function ‘free_foo’:
main.c:56:8: error: use after ‘free’ of ‘f’ [CWE-416] [-Werror=analyzer-use-after-free]
56 | if (f->name) free (f->name);
| ~^~~~~~
‘free_foos_list’: events 1-8
|
| 72 | free_foos_list (foos_list *l)
| | ^~~~~~~~~~~~~~
| | |
| | (1) entry to ‘free_foos_list’
| 73 | {
| 74 | if (!l) return;
| | ~
| | |
| | (2) following ‘false’ branch (when ‘l’ is non-NULL)...
| 75 |
| 76 | if (l->foos)
| | ~~~~~~~~
| | | |
| | | (3) ...to here
| | (4) following ‘true’ branch...
| 77 | {
| 78 | for (size_t i = 0; i < l->foos_len; i++)
| | ~~~ ~
| | | |
| | | (5) ...to here
| | (6) following ‘true’ branch...
| 79 | free_foo (l->foos[i]);
| | ~~~~~~~~~~~~~~~~~~~~~
| | | |
| | | (7) ...to here
| | (8) calling ‘free_foo’ from ‘free_foos_list’
|
+--> ‘free_foo’: events 9-10
|
| 53 | free_foo (foo *f)
| | ^~~~~~~~
| | |
| | (9) entry to ‘free_foo’
|......
| 58 | free (f);
| | ~~~~~~~~
| | |
| | (10) freed here
|
<------+
|
‘free_foos_list’: events 11-14
|
| 78 | for (size_t i = 0; i < l->foos_len; i++)
| | ~~~
| | |
| | (12) following ‘true’ branch...
| 79 | free_foo (l->foos[i]);
| | ^~~~~~~~~~~~~~~~~~~~~
| | | |
| | | (13) ...to here
| | (11) returning to ‘free_foos_list’ from ‘free_foo’
| | (14) calling ‘free_foo’ from ‘free_foos_list’
|
+--> ‘free_foo’: events 15-20
|
| 53 | free_foo (foo *f)
| | ^~~~~~~~
| | |
| | (15) entry to ‘free_foo’
| 54 | {
| 55 | if (!f) return;
| | ~
| | |
| | (16) following ‘false’ branch (when ‘f’ is non-NULL)...
| 56 | if (f->name) free (f->name);
| | ~~~~~~~~ ~~~~~~~
| | | | |
| | | | (19) ...to here
| | | (17) ...to here
| | (18) following ‘true’ branch...
| 57 |
| 58 | free (f);
| | ~~~~~~~~
| | |
| | (20) freed here
|
<------+
|
‘free_foos_list’: events 21-25
|
| 78 | for (size_t i = 0; i < l->foos_len; i++)
| | ~~~
| | |
| | (22) following ‘true’ branch...
| 79 | free_foo (l->foos[i]);
| | ^~~~~~~~~~~~~~~~~~~~~
| | | |
| | | (23) ...to here
| | | (24) freed here
| | (21) returning to ‘free_foos_list’ from ‘free_foo’
| | (25) calling ‘free_foo’ from ‘free_foos_list’
|
+--> ‘free_foo’: events 26-29
|
| 53 | free_foo (foo *f)
| | ^~~~~~~~
| | |
| | (26) entry to ‘free_foo’
| 54 | {
| 55 | if (!f) return;
| | ~
| | |
| | (27) following ‘false’ branch (when ‘f’ is non-NULL)...
| 56 | if (f->name) free (f->name);
| | ~~~~~~~
| | |
| | (28) ...to here
| | (29) use after ‘free’ of ‘f’; freed at (24)
|
main.c:56:16: error: double-‘free’ of ‘<unknown>’ [CWE-415] [-Werror=analyzer-double-free]
56 | if (f->name) free (f->name);
| ^~~~~~~~~~~~~~
‘free_foos_list’: events 1-10
|
| 72 | free_foos_list (foos_list *l)
| | ^~~~~~~~~~~~~~
| | |
| | (1) entry to ‘free_foos_list’
| 73 | {
| 74 | if (!l) return;
| | ~
| | |
| | (2) following ‘false’ branch (when ‘l’ is non-NULL)...
| 75 |
| 76 | if (l->foos)
| | ~~~~~~~~
| | | |
| | | (3) ...to here
| | (4) following ‘true’ branch...
| 77 | {
| 78 | for (size_t i = 0; i < l->foos_len; i++)
| | ~~~ ~
| | | |
| | | (5) ...to here
| | (6) following ‘true’ branch...
| | (8) following ‘true’ branch...
| 79 | free_foo (l->foos[i]);
| | ~~~~~~~~~~~~~~~~~~~~~
| | | |
| | | (7) ...to here
| | | (9) ...to here
| | (10) calling ‘free_foo’ from ‘free_foos_list’
|
+--> ‘free_foo’: events 11-15
|
| 53 | free_foo (foo *f)
| | ^~~~~~~~
| | |
| | (11) entry to ‘free_foo’
| 54 | {
| 55 | if (!f) return;
| | ~
| | |
| | (12) following ‘false’ branch (when ‘f’ is non-NULL)...
| 56 | if (f->name) free (f->name);
| | ~~~~~~~~ ~~~~~~~
| | | | |
| | | | (15) ...to here
| | | (13) ...to here
| | (14) following ‘true’ branch...
|
<------+
|
‘free_foos_list’: events 16-19
|
| 78 | for (size_t i = 0; i < l->foos_len; i++)
| | ~~~
| | |
| | (17) following ‘true’ branch...
| 79 | free_foo (l->foos[i]);
| | ^~~~~~~~~~~~~~~~~~~~~
| | | |
| | | (18) ...to here
| | (16) returning to ‘free_foos_list’ from ‘free_foo’
| | (19) calling ‘free_foo’ from ‘free_foos_list’
|
+--> ‘free_foo’: events 20-25
|
| 53 | free_foo (foo *f)
| | ^~~~~~~~
| | |
| | (20) entry to ‘free_foo’
| 54 | {
| 55 | if (!f) return;
| | ~
| | |
| | (21) following ‘false’ branch (when ‘f’ is non-NULL)...
| 56 | if (f->name) free (f->name);
| | ~~~~~~~~ ~~~~~~~~~~~~~~
| | | | | |
| | | | | (24) ...to here
| | | | (25) second ‘free’ here
| | | (22) ...to here
| | (23) following ‘true’ branch...
|
cc1: all warnings being treated as errors
If I understand correctly, it complains that I use f->name
after freeing f
, except that when I follow its instructions, the foo freed is l->foos[0]
and the one the analyzer think is already freed is l->foos[1]
(and it is not freed yet).
What puzzles me even more is that if I actually use those functions, the errors disappear. Here is a main function using them, and if I replace the main function with it in the previous file, it compiles just fine:
int main()
{
foos_list *l = new_foos_list ();
foo *f = new_foo ();
foo *f2 = new_foo ();
l->foos_len++;
l->foos = xrealloc (l->foos, sizeof(foo) * l->foos_len);
l->foos[0] = f;
l->foos_len++;
l->foos = xrealloc (l->foos, sizeof(foo) * l->foos_len);
l->foos[1] = f2;
free_foos_list (l);
}
My problem is that of course, I don't want to allocate and free foos, I hit that problem in a more complexe program where the functions are used in a different file they are declared, and the same fanalyzer errors are triggered.
So my question is : is this a fanalyzer bug (and if so, is there a workaround), or is it an error on my side (and how to fix it)? Thanks.
question from:
https://stackoverflow.com/questions/65923956/gcc-fanalyzer-complains-about-double-free-i-dont-understand-why