https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117662
Bug ID: 117662 Summary: Weird -fanalyzer behavior with code split across multiple files Product: gcc Version: 15.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: analyzer Assignee: dmalcolm at gcc dot gnu.org Reporter: frantisek at sumsal dot cz Target Milestone: --- Hey, As Fedora started recently utilizing gcc's static analyzer through OpenScanHub, I started going through results for the systemd package, and would like to eventually get rid of as much findings as possible. There's one false positive that appears in multiple cases, and I managed to somewhat minimize it to the following dummy code: $ cat log.c #include <stdio.h> #include "log.h" #define foo_log_oom(msg) foo_log(ENOMEM, msg) int foo_log(int error, const char *msg) { fprintf(stderr, "%s\n", msg); return -error; } $ cat log.h #pragma once #include <errno.h> #define foo_log_oom(msg) foo_log(ENOMEM, msg) int foo_log(int error, const char *msg); $ cat main.c #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "log.h" int foo_new(size_t size, char **ret) { char *p = NULL; assert(ret); p = malloc(size); if (!p) return foo_log_oom("foo_new() failed"); *ret = p; return 0; } int main(void) { char *p = NULL; int r; r = foo_new(1024, &p); if (r < 0) return 1; strcpy(p, "foo"); printf("%s\n", p); free(p); return 0; } This is a very minimal example that resembles the bare minimum of the logging infra that systemd usesm, but it reproduces the issue. In its current form it generates a warning: $ /opt/gcc-latest/bin/gcc -o main main.c log.c -fanalyzer main.c: In function ‘main’: main.c:30:5: warning: use of NULL where non-null expected [CWE-476] [-Wanalyzer-null-argument] 30 | strcpy(p, "foo"); | ^~~~~~~~~~~~~~~~ ‘main’: events 1-3 │ │ 22 | int main(void) { │ | ^~~~ │ | | │ | (1) entry to ‘main’ │ 23 | char *p = NULL; │ | ~ │ | | │ | (2) ‘p’ is NULL │...... │ 26 | r = foo_new(1024, &p); │ | ~~~~~~~~~~~~~~~~~ │ | | │ | (3) calling ‘foo_new’ from ‘main’ │ └──> ‘foo_new’: events 4-5 │ │ 8 | int foo_new(size_t size, char **ret) { │ | ^~~~~~~ │ | | │ | (4) entry to ‘foo_new’ │ 9 | char *p = NULL; │ | ~ │ | | │ | (5) ‘p’ is NULL │ ‘foo_new’: event 6 │ │ 11 | assert(ret); │ | ^~~~~~ │ | | │ | (6) following ‘true’ branch (when ‘ret’ is non-NULL)... ─>─┐ │ | │ │ ‘foo_new’: events 7-8 │ │ | │ │ |┌───────────────────────────────────────────────────────────────┘ │ 13 |│ p = malloc(size); │ |│ ^~~~~~~~~~~~ │ |│ | │ |└───────>(7) ...to here │ 14 | if (!p) │ | ~ │ | | │ | (8) following ‘true’ branch (when ‘p’ is NULL)... ─>─┐ │ | │ │ ‘foo_new’: event 9 │ │log.h:5:26: │ | │ │ |┌────────────────────────────────────────────────────────────┘ │ 5 |│#define foo_log_oom(msg) foo_log(ENOMEM, msg) │ |│ ^~~~~~~~~~~~~~~~~~~~ │ |│ | │ |└────────────────────────>(9) ...to here main.c:15:16: note: in expansion of macro ‘foo_log_oom’ │ 15 | return foo_log_oom("foo_new() failed"); │ | ^~~~~~~~~~~ │ <──────┘ │ ‘main’: events 10-16 │ │ 26 | r = foo_new(1024, &p); │ | ^~~~~~~~~~~~~~~~~ │ | | │ | (10) returning to ‘main’ from ‘foo_new’ │ 27 | if (r < 0) │ | ~ │ | | │ | (11) following ‘false’ branch (when ‘r >= 0’)... ─>─┐ │ | │ │...... │ | │ │ |┌───────────────────────────────────────────────────────────┘ │ 30 |│ strcpy(p, "foo"); │ |│ ~~~~~~~~~~~~~~~~ │ |│ | │ |└───>(12) ...to here │ | (13) ‘p’ is NULL │ | (16) ⚠ argument 1 (‘p’) NULL where non-null expected │ 31 | printf("%s\n", p); │ | ~~~~~~~~~~~~~~~~~ │ | | │ | (14) ‘p’ is NULL │ 32 | │ 33 | free(p); │ | ~~~~~~~ │ | | │ | (15) ‘p’ is NULL │ <built-in>: note: argument 1 of ‘__builtin_memcpy’ must be non-null But this is incorrect, since the foo_log_oom() function returns -ENOMEM, so the if (r < 0) condition can't follow the false branch. Interestingly enough, if I shove all the code into a single file, the warning disappears: $ cat main.c log.[ch] | /opt/gcc-latest/bin/gcc -o main -fanalyzer -x c - <stdin>:47:9: warning: ‘#pragma once’ in main file [-Wpragma-once-outside-header] (this is a quick and dirty example, hence the #pragma warning, but the outcome is the same even if the code is _properly_ integrated into the main source file, i.e. there's no -Wanalyzer-null-argument warning). Is this expected?