Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
128 views
in Technique[技术] by (71.8m points)

c - gcc -fanalyzer complains about double free, I don't understand why

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It seems to be an -fanalyzer issue.

I desk checked your code and found no leak.

My version of gcc doesn't seem to support -fanalyzer, so I couldn't test that.

I downloaded and built your program and ran it under valgrind. I did this for your original main and one I created with a more extensive diagnostic.

They both reported no errors of any kind.


Note that you're allocating too much space (wasteful, but harmless):

l->foos = xrealloc(l->foos,sizeof(foo) * l->foos_len);

Should be:

l->foos = xrealloc(l->foos,sizeof(foo *) * l->foos_len);

Or, better yet:

l->foos = xrealloc(l->foos,sizeof(*l->foos) * l->foos_len);

Here's my version:

#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(void)
{
    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()
{
    char name[100];

    foos_list *l = new_foos_list();

    for (int idx = 0;  idx < 30;  ++idx) {
        foo *f = new_foo();

        sprintf(name,"N%d",idx);
        f->name = strdup(name);

        size_t count = l->foos_len++;
        l->foos = xrealloc(l->foos,sizeof(*l->foos) * (count + 1));
        l->foos[count] = f;
    }

    int totlen = 0;
    for (int idx = 0;  idx < l->foos_len;  ++idx) {
        foo *f = l->foos[idx];
        totlen += printf(" %s",f->name);
        if (totlen > 70) {
            printf("
");
            totlen = 0;
        }
    }
    printf("
");

    free_foos_list(l);

    return 0;
}

Here's the my version's output:

 N0 N1 N2 N3 N4 N5 N6 N7 N8 N9 N10 N11 N12 N13 N14 N15 N16 N17 N18 N19 N20
 N21 N22 N23 N24 N25 N26 N27 N28 N29

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...