https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101386
Bug ID: 101386 Summary: Analysis of attribute malloc's deallocator function is not capable of abstraction Product: gcc Version: 11.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: analyzer Assignee: dmalcolm at gcc dot gnu.org Reporter: morrison.levi at gmail dot com Target Milestone: --- Consider the program string_allocator.c below. It creates reference counted strings. The analyzer will trigger `-Wanalyzer-mismatching-deallocation`, but it is incorrect. ``` #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct string_s { _Atomic size_t refcount; size_t len; char data[]; // flexible array member }; void string_delete(struct string_s *string) { if (string && --string->refcount == 0) { free(string); } } __attribute__((malloc, malloc(string_delete))) struct string_s * string_alloc(size_t len) { struct string_s *obj = calloc(1, offsetof(struct string_s, data) + len + 1); if (obj) { obj->refcount = 1; obj->len = len; } return obj; } int main() { const char *hello_world = "Hello, World!"; size_t size = strlen(hello_world); struct string_s *str = string_alloc(size); if (str) { memcpy(str->data + 0, hello_world, str->len); printf("%s\n", str->data + 0); } string_delete(str); return 0; } ``` When compiling with: $ gcc -g -O2 -std=c11 -Wall -Wextra -fanalyzer -fsanitize=address string_allocator.c -o string_allocator I get the warning: string_allocator.c: In function 'main': string_allocator.c:37:3: warning: 'str' should have been deallocated with 'free' but was deallocated with 'string_delete' [CWE-762] [-Wanalyzer-mismatching-deallocation] 37 | string_delete(str); | ^~~~~~~~~~~~~~~~~~ 'main': events 1-2 | | 29 | int main() { | | ^~~~ | | | | | (1) entry to 'main' |...... | 32 | struct string_s *str = string_alloc(size); | | ~~~~~~~~~~~~~~~~~~ | | | | | (2) calling 'string_alloc' from 'main' | +--> 'string_alloc': events 3-7 | | 20 | string_alloc(size_t len) { | | ^~~~~~~~~~~~ | | | | | (3) entry to 'string_alloc' | 21 | struct string_s *obj = calloc(1, offsetof(struct string_s, data) + len + 1); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (4) allocated here (expects deallocation with 'free') | 22 | if (obj) { | | ~ | | | | | (5) assuming 'obj' is non-NULL | | (6) following 'true' branch (when 'obj' is non-NULL)... | 23 | obj->refcount = 1; | | ~~~ | | | | | (7) ...to here | <------+ | 'main': events 8-11 | | 32 | struct string_s *str = string_alloc(size); | | ^~~~~~~~~~~~~~~~~~ | | | | | (8) returning to 'main' from 'string_alloc' | 33 | if (str) { | | ~ | | | | | (9) following 'true' branch (when 'str' is non-NULL)... | 34 | memcpy(str->data + 0, hello_world, str->len); | | ~~~~~~ | | | | | (10) ...to here |...... | 37 | string_delete(str); | | ~~~~~~~~~~~~~~~~~~ | | | | | (11) deallocated with 'string_delete' here; allocation at (4) expects deallocation with 'free' | Note that I also get this warning, which either I don't understand or it is bogus: string_allocator.c: In function 'string_alloc': string_allocator.c:26:10: warning: leak of 'str' [CWE-401] [-Wanalyzer-malloc-leak] 26 | return obj; | ^~~ 'main': events 1-3 | | 29 | int main() { | | ^~~~ | | | | | (1) entry to 'main' |...... | 32 | struct string_s *str = string_alloc(size); | | ~~~~~~~~~~~~~~~~~~ | | | | | (2) allocated here | | (3) calling 'string_alloc' from 'main' | +--> 'string_alloc': events 4-7 | | 20 | string_alloc(size_t len) { | | ^~~~~~~~~~~~ | | | | | (4) entry to 'string_alloc' | 21 | struct string_s *obj = calloc(1, offsetof(struct string_s, data) + len + 1); | 22 | if (obj) { | | ~ | | | | | (5) following 'false' branch (when 'obj' is NULL)... |...... | 26 | return obj; | | ~~~~~~ ~~~ | | | | | | | (7) 'str' leaks here; was allocated at (2) | | (6) ...to here |