https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106119
Bug ID: 106119 Summary: Bogus use-after-free warning triggered by optimizer Product: gcc Version: 12.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: tom.cosgrove at arm dot com Target Milestone: --- There are a number of bug reports related to -Wuse-after-free - it's not clear to me if our particular issue has already been captured, so please do close as duplicate if it has (it seems that the optimiser is moving code around, so could potentially be a duplicate of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104215). Assuming not a duplicate, this should probably be linked to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104075. Our self-test code checks to see if two consecutive calloc() calls return the same value, by storing the pointer value in a uintptr_t integer and comparing against this value later. When we compile with optimisation (we don't see the problem with -O0) and with an if-statement later in the code, we get a spurious use-after-free warning. We don't get this warning at -O0, or if we remove the if-statement. We have also confirmed that if we reorder the assignment to the uintptr_t and the call to free() we consistently DO get the use-after-free warning (as expected) regardless of optimisation level. gcc-12 $ uname -a Linux e120653 5.17.15-1-MANJARO #1 SMP PREEMPT Wed Jun 15 07:09:31 UTC 2022 x86_64 GNU/Linux gcc-12 $ gcc --version gcc (GCC) 12.1.0 Copyright (C) 2022 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. gcc-12 $ cat foo.c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void calloc_self_test( __attribute__ ((unused)) int verbose ) { void *buffer1 = calloc( 1, 1 ); uintptr_t old_buffer1 = (uintptr_t) buffer1; free( buffer1 ); buffer1 = calloc( 1, 1 ); int same = ( old_buffer1 == (uintptr_t) buffer1 ); if( verbose ) printf( " CALLOC(1 again): passed (%s address)\n", same ? "same" : "different" ); free( buffer1 ); } No warning at -O0: gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c foo.c gcc-12 $ But unexpected warning at -O2: gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c foo.c foo.c: In function ‘calloc_self_test’: foo.c:8:15: warning: pointer ‘buffer1’ may be used after ‘free’ [-Wuse-after-free] 8 | uintptr_t old_buffer1 = (uintptr_t) buffer1; | ^~~~~~~~~~~ foo.c:9:5: note: call to ‘free’ here 9 | free( buffer1 ); | ^~~~~~~~~~~~~~~ Removing the if-statement quashes the warning: gcc-12 $ perl -ni -e 'print unless /if.*verbose/' foo.c gcc-12 $ cat foo.c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void calloc_self_test( __attribute__ ((unused)) int verbose ) { void *buffer1 = calloc( 1, 1 ); uintptr_t old_buffer1 = (uintptr_t) buffer1; free( buffer1 ); buffer1 = calloc( 1, 1 ); int same = ( old_buffer1 == (uintptr_t) buffer1 ); printf( " CALLOC(1 again): passed (%s address)\n", same ? "same" : "different" ); free( buffer1 ); } gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c foo.c gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o foo -c foo.c gcc-12 $ Checking that we always get the error when we swap the assignment and free() statements: gcc-12 $ cat bar.c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void calloc_self_test( __attribute__ ((unused)) int verbose ) { void *buffer1 = calloc( 1, 1 ); free( buffer1 ); uintptr_t old_buffer1 = (uintptr_t) buffer1; buffer1 = calloc( 1, 1 ); int same = ( old_buffer1 == (uintptr_t) buffer1 ); if( verbose ) printf( " CALLOC(1 again): passed (%s address)\n", same ? "same" : "different" ); free( buffer1 ); } gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c bar.c bar.c: In function ‘calloc_self_test’: bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free] 9 | uintptr_t old_buffer1 = (uintptr_t) buffer1; | ^~~~~~~~~~~ bar.c:8:5: note: call to ‘free’ here 8 | free( buffer1 ); | ^~~~~~~~~~~~~~~ gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c bar.c bar.c: In function ‘calloc_self_test’: bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free] 9 | uintptr_t old_buffer1 = (uintptr_t) buffer1; | ^~~~~~~~~~~ bar.c:8:5: note: call to ‘free’ here 8 | free( buffer1 ); and we still get the warning (as we would hope) without the if-statement: gcc-12 $ perl -ni -e 'print unless /if.*verbose/' bar.c gcc-12 $ cat bar.c #include <stdint.h> #include <stdio.h> #include <stdlib.h> void calloc_self_test( __attribute__ ((unused)) int verbose ) { void *buffer1 = calloc( 1, 1 ); free( buffer1 ); uintptr_t old_buffer1 = (uintptr_t) buffer1; buffer1 = calloc( 1, 1 ); int same = ( old_buffer1 == (uintptr_t) buffer1 ); printf( " CALLOC(1 again): passed (%s address)\n", same ? "same" : "different" ); free( buffer1 ); } gcc-12 $ gcc -O0 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c bar.c bar.c: In function ‘calloc_self_test’: bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free] 9 | uintptr_t old_buffer1 = (uintptr_t) buffer1; | ^~~~~~~~~~~ bar.c:8:5: note: call to ‘free’ here 8 | free( buffer1 ); | ^~~~~~~~~~~~~~~ gcc-12 $ gcc -O2 -Wall -Wextra -Wformat=2 -Wno-format-nonliteral -o bar -c bar.c bar.c: In function ‘calloc_self_test’: bar.c:9:15: warning: pointer ‘buffer1’ used after ‘free’ [-Wuse-after-free] 9 | uintptr_t old_buffer1 = (uintptr_t) buffer1; | ^~~~~~~~~~~ bar.c:8:5: note: call to ‘free’ here 8 | free( buffer1 ); | ^~~~~~~~~~~~~~~