Skip to content

Commit 0d6f7b1

Browse files
committed
analyzer: use dominator info in -Wanalyzer-deref-before-check [PR108455]
My integration testing [1] of -fanalyzer in GCC 13 is showing a lot of diagnostics from the new -Wanalyzer-deref-before-check warning on real-world C projects, and most of these seem to be false positives. This patch updates the warning to make it much less likely to fire: - only intraprocedural cases are now reported - reject cases in which there are control flow paths to the check that didn't come through the dereference, by looking at BB dominator information. This fixes a false positive seen in git-2.39.0's pack-revindex.c: load_revindex_from_disk (PR analyzer/108455), in which a shared "cleanup:" section checks "data" for NULL, and depending on how much of the function is executed "data" might or might not have already been dereferenced. The counts of -Wanalyzer-deref-before-check diagnostics in [1] before/after this patch show this improvement: Known false positives: 6 -> 0 (-6) Known true positives: 1 -> 1 Unclassified positives: 123 -> 63 (-60) [1] https://github.com/davidmalcolm/gcc-analyzer-integration-tests gcc/analyzer/ChangeLog: PR analyzer/108455 * analyzer.h (class checker_event): New forward decl. (class state_change_event): Indent. (class warning_event): New forward decl. * checker-event.cc (state_change_event::state_change_event): Add "enode" param. (warning_event::get_desc): Update for new param of evdesc::final_event ctor. * checker-event.h (state_change_event::state_change_event): Add "enode" param. (state_change_event::get_exploded_node): New accessor. (state_change_event::m_enode): New field. (warning_event::warning_event): New "enode" param. (warning_event::get_exploded_node): New accessor. (warning_event::m_enode): New field. * diagnostic-manager.cc (state_change_event_creator::on_global_state_change): Pass src_node to state_change_event ctor. (state_change_event_creator::on_state_change): Likewise. (null_assignment_sm_context::set_next_state): Pass NULL for new param of state_change_event ctor. * infinite-recursion.cc (infinite_recursion_diagnostic::add_final_event): Update for new param of warning_event ctor. * pending-diagnostic.cc (pending_diagnostic::add_final_event): Pass enode to warning_event ctor. * pending-diagnostic.h (evdesc::final_event): Add reference to warning_event. * sm-malloc.cc: Include "analyzer/checker-event.h" and "analyzer/exploded-graph.h". (deref_before_check::deref_before_check): Initialize new fields. (deref_before_check::emit): Reject warnings in which we were unable to determine the enodes of the dereference and the check. Reject warnings interprocedural warnings. Reject warnings in which the dereference doesn't dominate the check. (deref_before_check::describe_state_change): Set m_deref_enode. (deref_before_check::describe_final_event): Set m_check_enode. (deref_before_check::m_deref_enode): New field. (deref_before_check::m_check_enode): New field. gcc/testsuite/ChangeLog: PR analyzer/108455 * gcc.dg/analyzer/deref-before-check-1.c: Add test coverage involving dominance. * gcc.dg/analyzer/deref-before-check-pr108455-1.c: New test. * gcc.dg/analyzer/deref-before-check-pr108455-git-pack-revindex.c: New test. Signed-off-by: David Malcolm <[email protected]>
1 parent 117848f commit 0d6f7b1

11 files changed

+272
-13
lines changed

gcc/analyzer/analyzer.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ class bounded_ranges_manager;
9393
class pending_diagnostic;
9494
class pending_note;
9595
struct event_loc_info;
96-
class state_change_event;
96+
class checker_event;
97+
class state_change_event;
98+
class warning_event;
9799
class checker_path;
98100
class extrinsic_state;
99101
class sm_state_map;

gcc/analyzer/checker-event.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,15 +410,17 @@ state_change_event::state_change_event (const supernode *node,
410410
state_machine::state_t from,
411411
state_machine::state_t to,
412412
const svalue *origin,
413-
const program_state &dst_state)
413+
const program_state &dst_state,
414+
const exploded_node *enode)
414415
: checker_event (EK_STATE_CHANGE,
415416
event_loc_info (stmt->location,
416417
node->m_fun->decl,
417418
stack_depth)),
418419
m_node (node), m_stmt (stmt), m_sm (sm),
419420
m_sval (sval), m_from (from), m_to (to),
420421
m_origin (origin),
421-
m_dst_state (dst_state)
422+
m_dst_state (dst_state),
423+
m_enode (enode)
422424
{
423425
}
424426

@@ -1159,7 +1161,7 @@ warning_event::get_desc (bool can_colorize) const
11591161
tree var = fixup_tree_for_diagnostic (m_var);
11601162
label_text ev_desc
11611163
= m_pending_diagnostic->describe_final_event
1162-
(evdesc::final_event (can_colorize, var, m_state));
1164+
(evdesc::final_event (can_colorize, var, m_state, *this));
11631165
if (ev_desc.get ())
11641166
{
11651167
if (m_sm && flag_analyzer_verbose_state_changes)

gcc/analyzer/checker-event.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,8 @@ class state_change_event : public checker_event
357357
state_machine::state_t from,
358358
state_machine::state_t to,
359359
const svalue *origin,
360-
const program_state &dst_state);
360+
const program_state &dst_state,
361+
const exploded_node *enode);
361362

362363
label_text get_desc (bool can_colorize) const final override;
363364
meaning get_meaning () const override;
@@ -367,6 +368,8 @@ class state_change_event : public checker_event
367368
return m_dst_state.get_current_function ();
368369
}
369370

371+
const exploded_node *get_exploded_node () const { return m_enode; }
372+
370373
const supernode *m_node;
371374
const gimple *m_stmt;
372375
const state_machine &m_sm;
@@ -375,6 +378,7 @@ class state_change_event : public checker_event
375378
state_machine::state_t m_to;
376379
const svalue *m_origin;
377380
program_state m_dst_state;
381+
const exploded_node *m_enode;
378382
};
379383

380384
/* Subclass of checker_event; parent class for subclasses that relate to
@@ -668,17 +672,22 @@ class warning_event : public checker_event
668672
{
669673
public:
670674
warning_event (const event_loc_info &loc_info,
675+
const exploded_node *enode,
671676
const state_machine *sm,
672677
tree var, state_machine::state_t state)
673678
: checker_event (EK_WARNING, loc_info),
679+
m_enode (enode),
674680
m_sm (sm), m_var (var), m_state (state)
675681
{
676682
}
677683

678684
label_text get_desc (bool can_colorize) const final override;
679685
meaning get_meaning () const override;
680686

687+
const exploded_node *get_exploded_node () const { return m_enode; }
688+
681689
private:
690+
const exploded_node *m_enode;
682691
const state_machine *m_sm;
683692
tree m_var;
684693
state_machine::state_t m_state;

gcc/analyzer/diagnostic-manager.cc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,7 +1572,8 @@ class state_change_event_creator : public state_change_visitor
15721572
src_sm_val,
15731573
dst_sm_val,
15741574
NULL,
1575-
dst_state));
1575+
dst_state,
1576+
src_node));
15761577
return false;
15771578
}
15781579

@@ -1616,7 +1617,8 @@ class state_change_event_creator : public state_change_visitor
16161617
src_sm_val,
16171618
dst_sm_val,
16181619
dst_origin_sval,
1619-
dst_state));
1620+
dst_state,
1621+
src_node));
16201622
return false;
16211623
}
16221624

@@ -1760,7 +1762,8 @@ struct null_assignment_sm_context : public sm_context
17601762
var_new_sval,
17611763
from, to,
17621764
NULL,
1763-
*m_new_state));
1765+
*m_new_state,
1766+
NULL));
17641767
}
17651768

17661769
void set_next_state (const gimple *stmt,
@@ -1785,7 +1788,8 @@ struct null_assignment_sm_context : public sm_context
17851788
sval,
17861789
from, to,
17871790
NULL,
1788-
*m_new_state));
1791+
*m_new_state,
1792+
NULL));
17891793
}
17901794

17911795
void warn (const supernode *, const gimple *,

gcc/analyzer/infinite-recursion.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class infinite_recursion_diagnostic
182182
/* Customize the location where the warning_event appears, putting
183183
it at the topmost entrypoint to the function. */
184184
void add_final_event (const state_machine *,
185-
const exploded_node *,
185+
const exploded_node *enode,
186186
const gimple *,
187187
tree,
188188
state_machine::state_t,
@@ -195,6 +195,7 @@ class infinite_recursion_diagnostic
195195
()->get_start_location (),
196196
m_callee_fndecl,
197197
m_new_entry_enode->get_stack_depth ()),
198+
enode,
198199
NULL, NULL, NULL));
199200
}
200201

gcc/analyzer/pending-diagnostic.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ pending_diagnostic::add_final_event (const state_machine *sm,
232232
(event_loc_info (get_stmt_location (stmt, enode->get_function ()),
233233
enode->get_function ()->decl,
234234
enode->get_stack_depth ()),
235+
enode,
235236
sm, var, state));
236237
}
237238

gcc/analyzer/pending-diagnostic.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,15 @@ struct return_of_state : public event_desc
131131
struct final_event : public event_desc
132132
{
133133
final_event (bool colorize,
134-
tree expr, state_machine::state_t state)
134+
tree expr, state_machine::state_t state,
135+
const warning_event &event)
135136
: event_desc (colorize),
136-
m_expr (expr), m_state (state)
137+
m_expr (expr), m_state (state), m_event (event)
137138
{}
138139

139140
tree m_expr;
140141
state_machine::state_t m_state;
142+
const warning_event &m_event;
141143
};
142144

143145
} /* end of namespace evdesc */

gcc/analyzer/sm-malloc.cc

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ along with GCC; see the file COPYING3. If not see
4545
#include "attribs.h"
4646
#include "analyzer/function-set.h"
4747
#include "analyzer/program-state.h"
48+
#include "analyzer/checker-event.h"
49+
#include "analyzer/exploded-graph.h"
4850

4951
#if ENABLE_ANALYZER
5052

@@ -1490,7 +1492,9 @@ class deref_before_check : public malloc_diagnostic
14901492
{
14911493
public:
14921494
deref_before_check (const malloc_state_machine &sm, tree arg)
1493-
: malloc_diagnostic (sm, arg)
1495+
: malloc_diagnostic (sm, arg),
1496+
m_deref_enode (NULL),
1497+
m_check_enode (NULL)
14941498
{}
14951499

14961500
const char *get_kind () const final override { return "deref_before_check"; }
@@ -1502,6 +1506,31 @@ class deref_before_check : public malloc_diagnostic
15021506

15031507
bool emit (rich_location *rich_loc) final override
15041508
{
1509+
/* Don't emit the warning if we can't show where the deref
1510+
and the check occur. */
1511+
if (!m_deref_enode)
1512+
return false;
1513+
if (!m_check_enode)
1514+
return false;
1515+
/* Only emit the warning for intraprocedural cases. */
1516+
if (m_deref_enode->get_function () != m_check_enode->get_function ())
1517+
return false;
1518+
if (&m_deref_enode->get_point ().get_call_string ()
1519+
!= &m_check_enode->get_point ().get_call_string ())
1520+
return false;
1521+
1522+
/* Reject the warning if the deref's BB doesn't dominate that
1523+
of the check, so that we don't warn e.g. for shared cleanup
1524+
code that checks a pointer for NULL, when that code is sometimes
1525+
used before a deref and sometimes after.
1526+
Using the dominance code requires setting cfun. */
1527+
auto_cfun sentinel (m_deref_enode->get_function ());
1528+
calculate_dominance_info (CDI_DOMINATORS);
1529+
if (!dominated_by_p (CDI_DOMINATORS,
1530+
m_check_enode->get_supernode ()->m_bb,
1531+
m_deref_enode->get_supernode ()->m_bb))
1532+
return false;
1533+
15051534
if (m_arg)
15061535
return warning_at (rich_loc, get_controlling_option (),
15071536
"check of %qE for NULL after already"
@@ -1520,6 +1549,7 @@ class deref_before_check : public malloc_diagnostic
15201549
&& assumed_non_null_p (change.m_new_state))
15211550
{
15221551
m_first_deref_event = change.m_event_id;
1552+
m_deref_enode = change.m_event.get_exploded_node ();
15231553
if (m_arg)
15241554
return change.formatted_print ("pointer %qE is dereferenced here",
15251555
m_arg);
@@ -1531,6 +1561,7 @@ class deref_before_check : public malloc_diagnostic
15311561

15321562
label_text describe_final_event (const evdesc::final_event &ev) final override
15331563
{
1564+
m_check_enode = ev.m_event.get_exploded_node ();
15341565
if (m_first_deref_event.known_p ())
15351566
{
15361567
if (m_arg)
@@ -1556,6 +1587,8 @@ class deref_before_check : public malloc_diagnostic
15561587

15571588
private:
15581589
diagnostic_event_id_t m_first_deref_event;
1590+
const exploded_node *m_deref_enode;
1591+
const exploded_node *m_check_enode;
15591592
};
15601593

15611594
/* struct allocation_state : public state_machine::state. */

gcc/testsuite/gcc.dg/analyzer/deref-before-check-1.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,39 @@ int test_checking_ptr_after_calling_deref (int *q)
167167
return 0;
168168
return v;
169169
}
170+
171+
extern void foo ();
172+
extern void bar ();
173+
extern void baz ();
174+
175+
int test_cfg_diamond_1 (int *p, int flag)
176+
{
177+
int x;
178+
x = *p; /* { dg-message "pointer 'p' is dereferenced here" } */
179+
if (flag)
180+
foo ();
181+
else
182+
bar ();
183+
if (p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
184+
{
185+
baz ();
186+
}
187+
return x;
188+
}
189+
190+
int test_cfg_diamond_2 (int *p, int flag)
191+
{
192+
int x = 0;
193+
if (flag)
194+
foo ();
195+
else
196+
{
197+
x = *p;
198+
bar ();
199+
}
200+
if (p) /* { dg-bogus "check of 'p' for NULL after already dereferencing it" } */
201+
{
202+
baz ();
203+
}
204+
return x;
205+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
extern int could_fail_1 (void);
2+
extern void *could_fail_2 (int);
3+
extern void cleanup (void *);
4+
5+
struct header {
6+
int signature;
7+
};
8+
9+
int test_1 (void) {
10+
int fd, ret = 0;
11+
void *data = ((void *)0);
12+
struct header *hdr;
13+
14+
fd = could_fail_1 ();
15+
16+
if (fd < 0) {
17+
ret = -1;
18+
goto cleanup;
19+
}
20+
21+
data = could_fail_2 (fd);
22+
hdr = data;
23+
24+
if (hdr->signature != 42) {
25+
ret = -2;
26+
goto cleanup;
27+
}
28+
29+
cleanup:
30+
if (ret) {
31+
if (data) /* { dg-bogus "check of 'data' for NULL after already dereferencing it" } */
32+
cleanup (data);
33+
}
34+
35+
return ret;
36+
}

0 commit comments

Comments
 (0)