I recently learned about GCC's new -fanalyzer feature, and decided to try it out on some of our codebase. The results were quite interesting, but there's one function where i suspect a false positive.
This is the function in question, for which GCC reported a malloc-leak (see here):
char** va_to_argv(va_list args, int32_t* argc){ va_list a; char* arg; int32_t n; int32_t l; int32_t sz = 0; int32_t cnt = 0; va_copy(a, args); while ((arg = va_arg(a, char*)) != NULL) { sz += strlen(arg) + 1;++cnt; } va_end(a); struct s { char* argv[cnt + 1]; char data[sz]; }; struct s* tmp = calloc(1, sizeof(*tmp)); for (n=0, l=0; n<cnt; ++n) { tmp->argv[n] = &tmp->data[l]; strcpy(tmp->argv[n], va_arg(args, char*)); l += strlen(tmp->argv[n]) + 1; } tmp->argv[cnt++] = NULL; if (argc) { *argc = cnt; } return &(tmp->argv[0]);}
It is supposed to convert a va_list
(containing only char*
, with NULL
as the last argument) to the equivalent argc/argv representation. To my knowledge, there have never been any issues involving this particular function, so i was quite surprised when GCC reported tmp
being leaked at the return statement.
Yes, the result of calloc()
is stored in tmp, and the function doesn't return tmp
directly, so at first glance the warning makes sense. However, the value returned is a pointer to the first element of tmp
, and the address of the first element of a struct is the same as the address of the struct itself (so free(&(tmp->argv[0]))
should be legal).
ISO/IEC 9899, Section 6.7.2.1
A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
Unfortunately, this code relies on a GCC-specific extension (VLA in struct) which isn't supported by clang. Otherwise i would have liked to cross-check what clang's static analyzer has to say about that.