* [committed] analyzer: new warning: -Wanalyzer-deref-before-check [PR99671]
@ 2022-11-10 18:37 David Malcolm
0 siblings, 0 replies; only message in thread
From: David Malcolm @ 2022-11-10 18:37 UTC (permalink / raw)
To: gcc-patches; +Cc: David Malcolm
This patch implements a new -Wanalyzer-deref-before-check within
-fanalyzer. It complains about code paths in which a pointer is checked
for NULL after it has already been dereferenced.
For example, for the testcase in PR 77432 the diagnostic emits:
deref-before-check-1.c: In function 'test_from_pr77432':
deref-before-check-1.c:6:8: warning: check of 'a' for NULL after already dereferencing it [-Wanalyzer-deref-before-check]
6 | if (a)
| ^
'test_from_pr77432': events 1-2
|
| 5 | int b = *a;
| | ^
| | |
| | (1) pointer 'a' is dereferenced here
| 6 | if (a)
| | ~
| | |
| | (2) pointer 'a' is checked for NULL here but it was already dereferenced at (1)
|
and in PR 77425 we had an instance of this hidden behind a
macro, which the diagnostic complains about as follows:
deref-before-check-pr77425.c: In function 'get_odr_type':
deref-before-check-pr77425.c:35:10: warning: check of 'odr_types_ptr' for NULL after already dereferencing it [-Wanalyzer-deref-before-check]
35 | if (odr_types_ptr)
| ^
'get_odr_type': events 1-3
|
| 27 | if (cond)
| | ^
| | |
| | (1) following 'false' branch...
|......
| 31 | else if (other_cond)
| | ~~~~~~~~~~~
| | ||
| | |(2) ...to here
| | (3) following 'true' branch...
|
'get_odr_type': event 4
|
| 11 | #define odr_types (*odr_types_ptr)
| | ~^~~~~~~~~~~~~~~
| | |
| | (4) ...to here
deref-before-check-pr77425.c:33:7: note: in expansion of macro 'odr_types'
| 33 | odr_types[val->id] = 0;
| | ^~~~~~~~~
|
'get_odr_type': event 5
|
| 11 | #define odr_types (*odr_types_ptr)
| | ~^~~~~~~~~~~~~~~
| | |
| | (5) pointer 'odr_types_ptr' is dereferenced here
deref-before-check-pr77425.c:33:7: note: in expansion of macro 'odr_types'
| 33 | odr_types[val->id] = 0;
| | ^~~~~~~~~
|
'get_odr_type': event 6
|
| 35 | if (odr_types_ptr)
| | ^
| | |
| | (6) pointer 'odr_types_ptr' is checked for NULL here but it was already dereferenced at (5)
|
Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-3884-g5c6546ca7d8cab.
gcc/analyzer/ChangeLog:
PR analyzer/99671
* analyzer.opt (Wanalyzer-deref-before-check): New warning.
* diagnostic-manager.cc
(null_assignment_sm_context::set_next_state): Only add state
change events for transition to "null" state.
(null_assignment_sm_context::is_transition_to_null): New.
* engine.cc (impl_region_model_context::on_pop_frame): New.
* exploded-graph.h (impl_region_model_context::on_pop_frame): New
decl.
* program-state.cc (sm_state_map::clear_any_state): New.
(sm_state_map::can_merge_with_p): New.
(program_state::can_merge_with_p): Replace requirement that
sm-states be equal in favor of an attempt to merge them.
* program-state.h (sm_state_map::clear_any_state): New decl.
(sm_state_map::can_merge_with_p): New decl.
* region-model.cc (region_model::eval_condition): Make const.
(region_model::pop_frame): Call ctxt->on_pop_frame.
* region-model.h (region_model::eval_condition): Make const.
(region_model_context::on_pop_frame): New vfunc.
(noop_region_model_context::on_pop_frame): New.
(region_model_context_decorator::on_pop_frame): New.
* sm-malloc.cc (enum resource_state): Add RS_ASSUMED_NON_NULL.
(allocation_state::dump_to_pp): Drop "final".
(struct assumed_non_null_state): New subclass.
(malloc_state_machine::m_assumed_non_null): New.
(assumed_non_null_p): New.
(class deref_before_check): New.
(assumed_non_null_state::dump_to_pp): New.
(malloc_state_machine::get_or_create_assumed_non_null_state_for_frame):
New.
(malloc_state_machine::maybe_assume_non_null): New.
(malloc_state_machine::on_stmt): Transition from start state to
"assumed-non-null" state for pointers passed to
__attribute__((nonnull)) arguments, and for pointers explicitly
dereferenced. Call maybe_complain_about_deref_before_check for
pointers explicitly compared against NULL.
(malloc_state_machine::maybe_complain_about_deref_before_check):
New.
(malloc_state_machine::on_deallocator_call): Also transition
"assumed-non-null" states to "freed".
(malloc_state_machine::on_pop_frame): New.
(malloc_state_machine::maybe_get_merged_states_nonequal): New.
* sm-malloc.dot: Update for changes to sm-malloc.cc.
* sm.h (state_machine::on_pop_frame): New.
(state_machine::maybe_get_merged_state): New.
(state_machine::maybe_get_merged_states_nonequal): New.
gcc/ChangeLog:
* doc/gcc/gcc-command-options/options-that-control-static-analysis.rst:
Add -Wanalyzer-deref-before-check.
gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/deref-before-check-1.c: New test.
* gcc.dg/analyzer/deref-before-check-2.c: New test.
* gcc.dg/analyzer/deref-before-check-pr77425.c: New test.
* gcc.dg/analyzer/malloc-1.c (test_51): New test.
gcc/ChangeLog:
PR analyzer/99671
* tristate.h (tristate::is_unknown): New.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
gcc/analyzer/analyzer.opt | 4 +
gcc/analyzer/diagnostic-manager.cc | 12 +
gcc/analyzer/engine.cc | 16 +
gcc/analyzer/exploded-graph.h | 2 +
gcc/analyzer/program-state.cc | 83 ++++-
gcc/analyzer/program-state.h | 6 +
gcc/analyzer/region-model.cc | 9 +-
gcc/analyzer/region-model.h | 11 +-
gcc/analyzer/sm-malloc.cc | 314 +++++++++++++++++-
gcc/analyzer/sm-malloc.dot | 7 +
gcc/analyzer/sm.h | 31 ++
.../options-that-control-static-analysis.rst | 29 ++
.../gcc.dg/analyzer/deref-before-check-1.c | 169 ++++++++++
.../gcc.dg/analyzer/deref-before-check-2.c | 130 ++++++++
.../analyzer/deref-before-check-pr77425.c | 43 +++
gcc/testsuite/gcc.dg/analyzer/malloc-1.c | 9 +
gcc/tristate.h | 1 +
17 files changed, 860 insertions(+), 16 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/analyzer/deref-before-check-1.c
create mode 100644 gcc/testsuite/gcc.dg/analyzer/deref-before-check-2.c
create mode 100644 gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr77425.c
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index dbab3b82deb..58ad3262ebf 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -58,6 +58,10 @@ Wanalyzer-allocation-size
Common Var(warn_analyzer_allocation_size) Init(1) Warning
Warn about code paths in which a pointer to a buffer is assigned to an incompatible type.
+Wanalyzer-deref-before-check
+Common Var(warn_analyzer_deref_before_check) Init(1) Warning
+Warn about code paths in which a pointer is checked for NULL after it has already been dereferenced.
+
Wanalyzer-double-fclose
Common Var(warn_analyzer_double_fclose) Init(1) Warning
Warn about code paths in which a stdio FILE can be closed more than once.
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 74cc7369d77..f82dd3a434a 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1739,9 +1739,12 @@ struct null_assignment_sm_context : public sm_context
state_machine::state_t from = get_state (stmt, var);
if (from != m_sm.get_start_state ())
return;
+ if (!is_transition_to_null (to))
+ return;
const svalue *var_new_sval
= m_new_state->m_region_model->get_rvalue (var, NULL);
+
const supernode *supernode = m_point->get_supernode ();
int stack_depth = m_point->get_stack_depth ();
@@ -1764,6 +1767,8 @@ struct null_assignment_sm_context : public sm_context
state_machine::state_t from = get_state (stmt, sval);
if (from != m_sm.get_start_state ())
return;
+ if (!is_transition_to_null (to))
+ return;
const supernode *supernode = m_point->get_supernode ();
int stack_depth = m_point->get_stack_depth ();
@@ -1834,6 +1839,13 @@ struct null_assignment_sm_context : public sm_context
return m_new_state;
}
+ /* We only care about transitions to the "null" state
+ within sm-malloc. Special-case this. */
+ static bool is_transition_to_null (state_machine::state_t s)
+ {
+ return !strcmp (s->get_name (), "null");
+ }
+
const program_state *m_old_state;
const program_state *m_new_state;
const gimple *m_stmt;
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 9c32afc6c71..fc90b49d041 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -921,6 +921,22 @@ impl_region_model_context::on_bounded_ranges (const svalue &sval,
}
}
+/* Implementation of region_model_context::on_pop_frame vfunc.
+ Notify all state machines about the frame being popped, which
+ could lead to states being discarded. */
+
+void
+impl_region_model_context::on_pop_frame (const frame_region *frame_reg)
+{
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = m_ext_state.get_sm (sm_idx);
+ sm.on_pop_frame (smap, frame_reg);
+ }
+}
+
/* Implementation of region_model_context::on_phi vfunc.
Notify all state machines about the phi, which could lead to
state transitions. */
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 8e3c160189c..de927e6c1b7 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -77,6 +77,8 @@ class impl_region_model_context : public region_model_context
void on_bounded_ranges (const svalue &sval,
const bounded_ranges &ranges) final override;
+ void on_pop_frame (const frame_region *frame_reg) final override;
+
void on_unknown_change (const svalue *sval, bool is_mutable) final override;
void on_phi (const gphi *phi, tree rhs) final override;
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index d00fd5ebe0b..a0585f5ceef 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -518,6 +518,14 @@ sm_state_map::impl_set_state (const svalue *sval,
return true;
}
+/* Clear any state for SVAL from this state map. */
+
+void
+sm_state_map::clear_any_state (const svalue *sval)
+{
+ m_map.remove (sval);
+}
+
/* Set the "global" state within this state map to STATE. */
void
@@ -719,6 +727,67 @@ sm_state_map::canonicalize_svalue (const svalue *sval,
return sval;
}
+/* Attempt to merge this state map with OTHER, writing the result
+ into *OUT.
+ Return true if the merger was possible, false otherwise.
+
+ Normally, only identical state maps can be merged, so that
+ differences between state maps lead to different enodes
+
+ However some state machines may support merging states to
+ allow for discarding of less important states, and thus avoid
+ blow-up of the exploded graph. */
+
+bool
+sm_state_map::can_merge_with_p (const sm_state_map &other,
+ const state_machine &sm,
+ const extrinsic_state &ext_state,
+ sm_state_map **out) const
+{
+ /* If identical, then they merge trivially, with a copy. */
+ if (*this == other)
+ {
+ delete *out;
+ *out = clone ();
+ return true;
+ }
+
+ delete *out;
+ *out = new sm_state_map (sm);
+
+ /* Otherwise, attempt to merge element by element. */
+
+ /* Try to merge global state. */
+ if (state_machine::state_t merged_global_state
+ = sm.maybe_get_merged_state (get_global_state (),
+ other.get_global_state ()))
+ (*out)->set_global_state (merged_global_state);
+ else
+ return false;
+
+ /* Try to merge state each svalue's state (for the union
+ of svalues represented by each smap).
+ Ignore the origin information. */
+ hash_set<const svalue *> svals;
+ for (auto kv : *this)
+ svals.add (kv.first);
+ for (auto kv : other)
+ svals.add (kv.first);
+ for (auto sval : svals)
+ {
+ state_machine::state_t this_state = get_state (sval, ext_state);
+ state_machine::state_t other_state = other.get_state (sval, ext_state);
+ if (state_machine::state_t merged_state
+ = sm.maybe_get_merged_state (this_state, other_state))
+ (*out)->impl_set_state (sval, merged_state, NULL, ext_state);
+ else
+ return false;
+ }
+
+ /* Successfully merged all elements. */
+ return true;
+}
+
/* class program_state. */
/* program_state's ctor. */
@@ -1283,11 +1352,14 @@ program_state::can_merge_with_p (const program_state &other,
gcc_assert (out);
gcc_assert (m_region_model);
- /* Early reject if there are sm-differences between the states. */
+ /* Attempt to merge the sm-states. */
int i;
sm_state_map *smap;
FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
- if (*m_checker_states[i] != *other.m_checker_states[i])
+ if (!m_checker_states[i]->can_merge_with_p (*other.m_checker_states[i],
+ ext_state.get_sm (i),
+ ext_state,
+ &out->m_checker_states[i]))
return false;
/* Attempt to merge the region_models. */
@@ -1298,13 +1370,6 @@ program_state::can_merge_with_p (const program_state &other,
this, &other))
return false;
- /* Copy m_checker_states to OUT. */
- FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
- {
- delete smap;
- out->m_checker_states[i] = m_checker_states[i]->clone ();
- }
-
out->m_region_model->canonicalize ();
return true;
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
index ad40578c703..79643278ee8 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -145,6 +145,7 @@ public:
state_machine::state_t state,
const svalue *origin,
const extrinsic_state &ext_state);
+ void clear_any_state (const svalue *sval);
void set_global_state (state_machine::state_t state);
state_machine::state_t get_global_state () const;
@@ -174,6 +175,11 @@ public:
bool replay_call_summary (call_summary_replay &r,
const sm_state_map &summary);
+ bool can_merge_with_p (const sm_state_map &other,
+ const state_machine &sm,
+ const extrinsic_state &ext_state,
+ sm_state_map **out) const;
+
private:
const state_machine &m_sm;
map_t m_map;
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 5ffad64a9c5..b91434d7db4 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -4690,7 +4690,7 @@ tristate
region_model::eval_condition (tree lhs,
enum tree_code op,
tree rhs,
- region_model_context *ctxt)
+ region_model_context *ctxt) const
{
/* For now, make no attempt to model constraints on floating-point
values. */
@@ -5415,8 +5415,13 @@ region_model::pop_frame (tree result_lvalue,
{
gcc_assert (m_current_frame);
- /* Evaluate the result, within the callee frame. */
const frame_region *frame_reg = m_current_frame;
+
+ /* Notify state machines. */
+ if (ctxt)
+ ctxt->on_pop_frame (frame_reg);
+
+ /* Evaluate the result, within the callee frame. */
tree fndecl = m_current_frame->get_function ()->decl;
tree result = DECL_RESULT (fndecl);
const svalue *retval = NULL;
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 70c808f4973..50790596726 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -458,7 +458,7 @@ class region_model
tristate eval_condition (tree lhs,
enum tree_code op,
tree rhs,
- region_model_context *ctxt);
+ region_model_context *ctxt) const;
bool add_constraint (tree lhs, enum tree_code op, tree rhs,
region_model_context *ctxt);
bool add_constraint (tree lhs, enum tree_code op, tree rhs,
@@ -708,6 +708,9 @@ class region_model_context
virtual void on_bounded_ranges (const svalue &sval,
const bounded_ranges &ranges) = 0;
+ /* Hook for clients to be notified when a frame is popped from the stack. */
+ virtual void on_pop_frame (const frame_region *) = 0;
+
/* Hooks for clients to be notified when an unknown change happens
to SVAL (in response to a call to an unknown function). */
virtual void on_unknown_change (const svalue *sval, bool is_mutable) = 0;
@@ -789,6 +792,7 @@ public:
const bounded_ranges &) override
{
}
+ void on_pop_frame (const frame_region *) override {}
void on_unknown_change (const svalue *sval ATTRIBUTE_UNUSED,
bool is_mutable ATTRIBUTE_UNUSED) override
{
@@ -886,6 +890,11 @@ class region_model_context_decorator : public region_model_context
m_inner->on_bounded_ranges (sval, ranges);
}
+ void on_pop_frame (const frame_region *frame_reg) override
+ {
+ m_inner->on_pop_frame (frame_reg);
+ }
+
void on_unknown_change (const svalue *sval, bool is_mutable) override
{
m_inner->on_unknown_change (sval, is_mutable);
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index d050ef83eeb..dd10356155c 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -87,6 +87,9 @@ enum resource_state
/* The start state. */
RS_START,
+ /* State for a pointer that's been unconditionally dereferenced. */
+ RS_ASSUMED_NON_NULL,
+
/* State for a pointer that's known to be NULL. */
RS_NULL,
@@ -126,7 +129,7 @@ struct allocation_state : public state_machine::state
m_deallocator (deallocator)
{}
- void dump_to_pp (pretty_printer *pp) const final override;
+ void dump_to_pp (pretty_printer *pp) const override;
const allocation_state *get_nonnull () const;
@@ -135,6 +138,25 @@ struct allocation_state : public state_machine::state
const deallocator *m_deallocator;
};
+/* Custom state subclass, for the "assumed-non-null" state
+ where the assumption happens in a particular frame. */
+
+struct assumed_non_null_state : public allocation_state
+{
+ assumed_non_null_state (const char *name, unsigned id,
+ const frame_region *frame)
+ : allocation_state (name, id, RS_ASSUMED_NON_NULL,
+ NULL, NULL),
+ m_frame (frame)
+ {
+ gcc_assert (m_frame);
+ }
+
+ void dump_to_pp (pretty_printer *pp) const final override;
+
+ const frame_region *m_frame;
+};
+
/* An enum for choosing which wording to use in various diagnostics
when describing deallocations. */
@@ -384,14 +406,25 @@ public:
enum tree_code op,
const svalue *rhs) const final override;
+ void on_pop_frame (sm_state_map *smap,
+ const frame_region *) const final override;
+
bool can_purge_p (state_t s) const final override;
std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
bool reset_when_passed_to_unknown_fn_p (state_t s,
bool is_mutable) const final override;
+ state_t
+ maybe_get_merged_states_nonequal (state_t state_a,
+ state_t state_b) const final override;
+
static bool unaffected_by_call_p (tree fndecl);
+ void maybe_assume_non_null (sm_context *sm_ctxt,
+ tree ptr,
+ const gimple *stmt) const;
+
void on_realloc_with_move (region_model *model,
sm_state_map *smap,
const svalue *old_ptr_sval,
@@ -406,6 +439,10 @@ public:
/* States that are independent of api. */
+ /* States for a pointer that's been unconditionally dereferenced
+ in a particular stack frame. */
+ hash_map<const frame_region *, state_t> m_assumed_non_null;
+
/* State for a pointer that's known to be NULL. */
state_t m_null;
@@ -425,6 +462,16 @@ private:
const deallocator *
get_or_create_deallocator (tree deallocator_fndecl);
+ state_t
+ get_or_create_assumed_non_null_state_for_frame (const frame_region *frame);
+
+ void
+ maybe_complain_about_deref_before_check (sm_context *sm_ctxt,
+ const supernode *node,
+ const gimple *stmt,
+ const assumed_non_null_state *,
+ tree ptr) const;
+
void on_allocator_call (sm_context *sm_ctxt,
const gcall *call,
const deallocator_set *deallocators,
@@ -678,6 +725,14 @@ freed_p (state_machine::state_t state)
return get_rs (state) == RS_FREED;
}
+/* Return true if STATE is a value that has been assumed to be non-NULL. */
+
+static bool
+assumed_non_null_p (state_machine::state_t state)
+{
+ return get_rs (state) == RS_ASSUMED_NON_NULL;
+}
+
/* Class for diagnostics relating to malloc_state_machine. */
class malloc_diagnostic : public pending_diagnostic
@@ -1428,6 +1483,80 @@ private:
const char *m_funcname;
};
+/* Concrete pending_diagnostic subclass for -Wanalyzer-deref-before-check. */
+
+class deref_before_check : public malloc_diagnostic
+{
+public:
+ deref_before_check (const malloc_state_machine &sm, tree arg)
+ : malloc_diagnostic (sm, arg)
+ {}
+
+ const char *get_kind () const final override { return "deref_before_check"; }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_deref_before_check;
+ }
+
+ bool emit (rich_location *rich_loc) final override
+ {
+ if (m_arg)
+ return warning_at (rich_loc, get_controlling_option (),
+ "check of %qE for NULL after already"
+ " dereferencing it",
+ m_arg);
+ else
+ return warning_at (rich_loc, get_controlling_option (),
+ "check of pointer for NULL after already"
+ " dereferencing it");
+ }
+
+ label_text describe_state_change (const evdesc::state_change &change)
+ final override
+ {
+ if (change.m_old_state == m_sm.get_start_state ()
+ && assumed_non_null_p (change.m_new_state))
+ {
+ m_first_deref_event = change.m_event_id;
+ if (m_arg)
+ return change.formatted_print ("pointer %qE is dereferenced here",
+ m_arg);
+ else
+ return label_text::borrow ("pointer is dereferenced here");
+ }
+ return malloc_diagnostic::describe_state_change (change);
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) final override
+ {
+ if (m_first_deref_event.known_p ())
+ {
+ if (m_arg)
+ return ev.formatted_print ("pointer %qE is checked for NULL here but"
+ " it was already dereferenced at %@",
+ m_arg, &m_first_deref_event);
+ else
+ return ev.formatted_print ("pointer is checked for NULL here but"
+ " it was already dereferenced at %@",
+ &m_first_deref_event);
+ }
+ else
+ {
+ if (m_arg)
+ return ev.formatted_print ("pointer %qE is checked for NULL here but"
+ " it was already dereferenced",
+ m_arg);
+ else
+ return ev.formatted_print ("pointer is checked for NULL here but"
+ " it was already dereferenced");
+ }
+ }
+
+private:
+ diagnostic_event_id_t m_first_deref_event;
+};
+
/* struct allocation_state : public state_machine::state. */
/* Implementation of state_machine::state::dump_to_pp vfunc
@@ -1456,6 +1585,17 @@ allocation_state::get_nonnull () const
return as_a_allocation_state (m_deallocators->m_nonnull);
}
+/* struct assumed_non_null_state : public allocation_state. */
+
+void
+assumed_non_null_state::dump_to_pp (pretty_printer *pp) const
+{
+ allocation_state::dump_to_pp (pp);
+ pp_string (pp, " (in ");
+ m_frame->dump_to_pp (pp, true);
+ pp_character (pp, ')');
+}
+
/* malloc_state_machine's ctor. */
malloc_state_machine::malloc_state_machine (logger *logger)
@@ -1597,6 +1737,22 @@ malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl)
return d;
}
+/* Get the "assumed-non-null" state for assumptions made within FRAME,
+ creating it if necessary. */
+
+state_machine::state_t
+malloc_state_machine::
+get_or_create_assumed_non_null_state_for_frame (const frame_region *frame)
+{
+ if (state_t *slot = m_assumed_non_null.get (frame))
+ return *slot;
+ state_machine::state *new_state
+ = new assumed_non_null_state ("assumed-non-null", alloc_state_id (), frame);
+ add_custom_state (new_state);
+ m_assumed_non_null.put (frame, new_state);
+ return new_state;
+}
+
/* Try to identify the function declaration either by name or as a known malloc
builtin. */
@@ -1629,6 +1785,33 @@ known_allocator_p (const_tree fndecl, const gcall *call)
return false;
}
+/* If PTR's nullness is not known, transition it to the "assumed-non-null"
+ state for the current frame. */
+
+void
+malloc_state_machine::maybe_assume_non_null (sm_context *sm_ctxt,
+ tree ptr,
+ const gimple *stmt) const
+{
+ const region_model *old_model = sm_ctxt->get_old_region_model ();
+ if (!old_model)
+ return;
+
+ tree null_ptr_cst = build_int_cst (TREE_TYPE (ptr), 0);
+ tristate known_non_null
+ = old_model->eval_condition (ptr, NE_EXPR, null_ptr_cst, NULL);
+ if (known_non_null.is_unknown ())
+ {
+ /* Cast away const-ness for cache-like operations. */
+ malloc_state_machine *mut_this
+ = const_cast <malloc_state_machine *> (this);
+ state_t next_state
+ = mut_this->get_or_create_assumed_non_null_state_for_frame
+ (old_model->get_current_frame ());
+ sm_ctxt->set_next_state (stmt, ptr, next_state);
+ }
+}
+
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
bool
@@ -1743,6 +1926,8 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
(*this, diag_arg, callee_fndecl, i));
sm_ctxt->set_next_state (stmt, arg, m_stop);
}
+ else if (state == m_start)
+ maybe_assume_non_null (sm_ctxt, arg, stmt);
}
}
BITMAP_FREE (nonnull_args);
@@ -1760,6 +1945,36 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
}
}
+ /* Look for pointers explicitly being compared against zero
+ that are in state assumed_non_null i.e. we already defererenced
+ them.
+ We have to do this check here, rather than in on_condition
+ because we add a constraint that the pointer is non-null when
+ dereferencing it, and this makes the apply_constraints_for_gcond
+ find known-true and known-false conditions; on_condition is only
+ called when adding new constraints. */
+ if (const gcond *cond_stmt = dyn_cast <const gcond *> (stmt))
+ {
+ enum tree_code op = gimple_cond_code (cond_stmt);
+ if (op == EQ_EXPR || op == NE_EXPR)
+ {
+ tree lhs = gimple_cond_lhs (cond_stmt);
+ tree rhs = gimple_cond_rhs (cond_stmt);
+ if (any_pointer_p (lhs)
+ && any_pointer_p (rhs)
+ && zerop (rhs))
+ {
+ state_t state = sm_ctxt->get_state (stmt, lhs);
+ if (assumed_non_null_p (state))
+ maybe_complain_about_deref_before_check
+ (sm_ctxt, node,
+ stmt,
+ (const assumed_non_null_state *)state,
+ lhs);
+ }
+ }
+ }
+
if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
if (any_pointer_p (lhs))
on_zero_assignment (sm_ctxt, stmt,lhs);
@@ -1778,7 +1993,9 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
tree arg = TREE_OPERAND (op, 0);
state_t state = sm_ctxt->get_state (stmt, arg);
- if (unchecked_p (state))
+ if (state == m_start)
+ maybe_assume_non_null (sm_ctxt, arg, stmt);
+ else if (unchecked_p (state))
{
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
sm_ctxt->warn (node, stmt, arg,
@@ -1808,6 +2025,53 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
return false;
}
+/* Given a check against null of PTR in assumed-non-null state STATE,
+ potentially add a deref_before_check warning to SM_CTXT. */
+
+void
+malloc_state_machine::
+maybe_complain_about_deref_before_check (sm_context *sm_ctxt,
+ const supernode *node,
+ const gimple *stmt,
+ const assumed_non_null_state *state,
+ tree ptr) const
+{
+ const region_model *model = sm_ctxt->get_old_region_model ();
+ if (!model)
+ return;
+
+ /* Don't complain if the current frame (where the check is occurring) is
+ deeper than the frame in which the "not null" assumption was made.
+ This suppress false positives for cases like:
+
+ void foo (struct s *p)
+ {
+ int val = s->some_field; // deref here
+ shared_helper (p);
+ }
+
+ where "shared_helper" has:
+
+ void shared_helper (struct s *p)
+ {
+ if (!p) // check here
+ return;
+ // etc
+ }
+
+ since the check in "shared_helper" is OK. */
+ const frame_region *checked_in_frame = model->get_current_frame ();
+ const frame_region *assumed_nonnull_in_frame = state->m_frame;
+ if (checked_in_frame->get_index () > assumed_nonnull_in_frame->get_index ())
+ return;
+
+ tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr);
+ sm_ctxt->warn
+ (node, stmt, ptr,
+ make_unique<deref_before_check> (*this, diag_ptr));
+ sm_ctxt->set_next_state (stmt, ptr, m_stop);
+}
+
/* Handle a call to an allocator.
RETURNS_NONNULL is true if CALL is to a fndecl known to have
__attribute__((returns_nonnull)). */
@@ -1870,8 +2134,8 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
state_t state = sm_ctxt->get_state (call, arg);
- /* start/unchecked/nonnull -> freed. */
- if (state == m_start)
+ /* start/assumed_non_null/unchecked/nonnull -> freed. */
+ if (state == m_start || assumed_non_null_p (state))
sm_ctxt->set_next_state (call, arg, d->m_freed);
else if (unchecked_p (state) || nonnull_p (state))
{
@@ -2016,6 +2280,31 @@ malloc_state_machine::on_condition (sm_context *sm_ctxt,
}
}
+/* Implementation of state_machine::on_pop_frame vfunc for malloc_state_machine.
+ Clear any "assumed-non-null" state where the assumption happened in
+ FRAME_REG. */
+
+void
+malloc_state_machine::on_pop_frame (sm_state_map *smap,
+ const frame_region *frame_reg) const
+{
+ hash_set<const svalue *> svals_to_clear;
+ for (auto kv : *smap)
+ {
+ const svalue *sval = kv.first;
+ state_t state = kv.second.m_state;
+ if (assumed_non_null_p (state))
+ {
+ const assumed_non_null_state *assumed_state
+ = (const assumed_non_null_state *)state;
+ if (frame_reg == assumed_state->m_frame)
+ svals_to_clear.add (sval);
+ }
+ }
+ for (auto sval : svals_to_clear)
+ smap->clear_any_state (sval);
+}
+
/* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine.
Don't allow purging of pointers in state 'unchecked' or 'nonnull'
(to avoid false leak reports). */
@@ -2053,6 +2342,23 @@ malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
return is_mutable;
}
+/* Implementation of state_machine::maybe_get_merged_states_nonequal vfunc
+ for malloc_state_machine.
+
+ Support discarding "assumed-non-null" states when merging with
+ start state. */
+
+state_machine::state_t
+malloc_state_machine::maybe_get_merged_states_nonequal (state_t state_a,
+ state_t state_b) const
+{
+ if (assumed_non_null_p (state_a) && state_b == m_start)
+ return m_start;
+ if (state_a == m_start && assumed_non_null_p (state_b))
+ return m_start;
+ return NULL;
+}
+
/* Return true if calls to FNDECL are known to not affect this sm-state. */
bool
diff --git a/gcc/analyzer/sm-malloc.dot b/gcc/analyzer/sm-malloc.dot
index c8f4f9c06ba..cb1c54aa351 100644
--- a/gcc/analyzer/sm-malloc.dot
+++ b/gcc/analyzer/sm-malloc.dot
@@ -32,6 +32,9 @@ digraph "malloc" {
It could be a pointer to heap-allocated memory, or could be NULL. */
unchecked;
+ /* State for a pointer that's been unconditionally dereferenced. */
+ assumed_non_null;
+
/* State for a pointer that's known to be NULL. */
null;
@@ -58,6 +61,7 @@ digraph "malloc" {
/* On "free". */
start -> freed [label="on 'free(X);'"];
+ assumed_non_null -> freed [label="on 'free(X);'"];
unchecked -> freed [label="on 'free(X);'"];
nonnull -> freed [label="on 'free(X);'"];
freed -> stop [label="on 'free(X);':\n Warn('double-free')"];
@@ -66,6 +70,7 @@ digraph "malloc" {
/* Handle "__attribute__((nonnull))". */
unchecked -> nonnull [label="on 'FN(X)' with __attribute__((nonnull)):\nWarn('possible NULL arg')"];
null -> stop [label="on 'FN(X)' with __attribute__((nonnull)):\nWarn('NULL arg')"];
+ start -> assumed_non_null [label="on 'FN(X)' with __attribute__((nonnull))"];
/* is_zero_assignment. */
start -> null [label="on 'X = 0;'"];
@@ -76,6 +81,7 @@ digraph "malloc" {
start -> non_heap [label="on 'X = &EXPR;'"];
/* Handle dereferences. */
+ start -> assumed_non_null [label="on '*X'"];
unchecked -> nonnull [label="on '*X':\nWarn('possible NULL deref')"];
null -> stop [label="on '*X':\nWarn('NULL deref')"];
freed -> stop [label="on '*X':\nWarn('use after free')"];
@@ -83,6 +89,7 @@ digraph "malloc" {
/* on_condition. */
unchecked -> nonnull [label="on 'X != 0'"];
unchecked -> null [label="on 'X == 0'"];
+ assumed_non_null -> stop [label="on 'if (X)':\nWarn('deref-before-check')"];
unchecked -> stop [label="on leak:\nWarn('leak')"];
nonnull -> stop [label="on leak:\nWarn('leak')"];
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 0171474e4b9..085a3a1514b 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -117,6 +117,12 @@ public:
{
}
+ virtual void
+ on_pop_frame (sm_state_map *smap ATTRIBUTE_UNUSED,
+ const frame_region *frame_reg ATTRIBUTE_UNUSED) const
+ {
+ }
+
/* Return true if it safe to discard the given state (to help
when simplifying state objects).
States that need leak detection should return false. */
@@ -139,6 +145,31 @@ public:
return is_mutable;
}
+ /* Attempt to get a state for the merger of STATE_A and STATE_B,
+ or return NULL if merging shouldn't occur, so that differences
+ between sm-state will lead to separate exploded nodes.
+
+ Most state machines will only merge equal states, but can
+ override maybe_get_merged_states_nonequal to support mergers
+ of certain non-equal states. */
+ state_t maybe_get_merged_state (state_t state_a,
+ state_t state_b) const
+ {
+ if (state_a == state_b)
+ return state_a;
+ return maybe_get_merged_states_nonequal (state_a, state_b);
+ }
+
+ /* Base implementation of hook for maybe_get_merged_state on non-equal
+ states. */
+ virtual state_t
+ maybe_get_merged_states_nonequal (state_t state_a ATTRIBUTE_UNUSED,
+ state_t state_b ATTRIBUTE_UNUSED) const
+ {
+ /* By default, non-equal sm states should inhibit merger of enodes. */
+ return NULL;
+ }
+
void validate (state_t s) const;
void dump_to_pp (pretty_printer *pp) const;
diff --git a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
index c0e06ea5b2d..09bf049036f 100644
--- a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
+++ b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
@@ -19,6 +19,7 @@ Options That Control Static Analysis
Enabling this option effectively enables the following warnings:
:option:`-Wanalyzer-allocation-size` |gol|
+ :option:`-Wanalyzer-deref-before-check` |gol|
:option:`-Wanalyzer-double-fclose` |gol|
:option:`-Wanalyzer-double-free` |gol|
:option:`-Wanalyzer-exposure-through-output-file` |gol|
@@ -88,6 +89,33 @@ Options That Control Static Analysis
Default setting; overrides :option:`-Wno-analyzer-allocation-size`.
+.. option:: -Wno-analyzer-deref-before-check
+
+ This warning requires :option:`-fanalyzer`, which enables it; use
+ :option:`-Wno-analyzer-deref-before-check`
+ to disable it.
+
+ This diagnostic warns for paths through the code in which a pointer
+ is checked for ``NULL`` *after* it has already been
+ dereferenced, suggesting that the pointer could have been NULL.
+ Such cases suggest that the check for NULL is either redundant,
+ or that it needs to be moved to before the pointer is dereferenced.
+
+ This diagnostic also considers values passed to a function argument
+ marked with ``__attribute__((nonnull))`` as requiring a non-NULL
+ value, and thus will complain if such values are checked for ``NULL``
+ after returning from such a function call.
+
+ This diagnostic is unlikely to be reported when any level of optimization
+ is enabled, as GCC's optimization logic will typically consider such
+ checks for NULL as being redundant, and optimize them away before the
+ analyzer "sees" them. Hence optimization should be disabled when
+ attempting to trigger this diagnostic.
+
+.. option:: -Wanalyzer-deref-before-check
+
+ Default setting; overrides :option:`-Wno-analyzer-deref-before-check`.
+
.. option:: -Wno-analyzer-double-fclose
This warning requires :option:`-fanalyzer`, which enables it; use
@@ -825,6 +853,7 @@ The following options control the analyzer.
Currently, :option:`-fanalyzer-checker=taint` disables the
following warnings from :option:`-fanalyzer` :
+ :option:`-Wanalyzer-deref-before-check` |gol|
:option:`-Wanalyzer-double-fclose` |gol|
:option:`-Wanalyzer-double-free` |gol|
:option:`-Wanalyzer-exposure-through-output-file` |gol|
diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-1.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-1.c
new file mode 100644
index 00000000000..e1c7a00b97c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-1.c
@@ -0,0 +1,169 @@
+#define NULL ((void *)0)
+
+int test_from_pr77432 (int *a)
+{
+ int b = *a; /* { dg-message "pointer 'a' is dereferenced here" } */
+ if (a) /* { dg-warning "check of 'a' for NULL after already dereferencing it \\\[-Wanalyzer-deref-before-check\\\]" "warning" } */
+ /* { dg-message "pointer 'a' is checked for NULL here but it was already dereferenced at \\(1\\)" "final event" { target *-*-* } .-1 } */
+ return b;
+ return 0;
+}
+
+int test_1a (int *p, int x)
+{
+ *p = x; /* { dg-message "pointer 'p' is dereferenced here" } */
+
+ if (p) /* { dg-warning "check of 'p' for NULL after already dereferencing it \\\[-Wanalyzer-deref-before-check\\\]" "warning" } */
+ /* { dg-message "pointer 'p' is checked for NULL here but it was already dereferenced at \\(1\\)" "final event" { target *-*-* } .-1 } */
+ return 1;
+ else
+ return 0;
+}
+
+int test_1b (int *p, int *q)
+{
+ *q = *p; /* { dg-message "8: pointer 'p' is dereferenced here" } */
+
+ if (p) /* { dg-warning "check of 'p' for NULL after already dereferencing it \\\[-Wanalyzer-deref-before-check\\\]" "warning" } */
+ /* { dg-message "pointer 'p' is checked for NULL here but it was already dereferenced at \\(1\\)" "final event" { target *-*-* } .-1 } */
+ return 1;
+ else
+ return 0;
+}
+
+struct s2
+{
+ int x;
+ int y;
+};
+
+int test_2a (struct s2 *p)
+{
+ int sum = p->x + p->y; /* { dg-message "pointer 'p' is dereferenced here" } */
+ if (!p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ __builtin_abort ();
+ return sum;
+}
+
+int test_2b (struct s2 *p)
+{
+ if (!p)
+ __builtin_abort ();
+ int sum = p->x + p->y;
+ return sum;
+}
+
+struct s3
+{
+ int flag;
+};
+
+extern void err (const char *);
+
+void test_3 (struct s3 *p)
+{
+ if (p->flag) /* { dg-message "pointer 'p' is dereferenced here" } */
+ err ("p->flag");
+ if (!p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ err ("p was NULL");
+}
+
+struct s4
+{
+ struct s4 *m_next;
+ int m_val;
+};
+
+int test_4 (struct s4 *p)
+{
+ if (p->m_next->m_val > 0) /* { dg-message "pointer '\\*p.m_next' is dereferenced here" } */
+ return -1;
+ if (!p->m_next) /* { dg-warning "check of '\\*p.m_next' for NULL after already dereferencing it" } */
+ return -2;
+ return p->m_next->m_val;
+}
+
+struct s5
+{
+ const char *str;
+ int val;
+};
+
+int test_5 (struct s5 *p)
+{
+ __builtin_printf ("%s: %i\n", p->str, p->val); /* { dg-message "pointer 'p' is dereferenced here" } */
+ if (p != NULL) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ return p->val;
+ return -1;
+}
+
+static int __attribute__((noinline))
+__analyzer_check_ptr (int *p)
+{
+ if (p)
+ return *p;
+ else
+ return 42;
+}
+
+int test_calling_check_ptr_after_deref_1 (int *q)
+{
+ int v = *q; /* { dg-bogus "dereferenced here" } */
+ v += __analyzer_check_ptr (q);
+ return v;
+}
+
+int test_calling_check_ptr_after_deref_2 (int *q)
+{
+ int v = *q; /* { dg-bogus "dereferenced here" } */
+ v += __analyzer_check_ptr (q);
+ *q = 17;
+ return v;
+}
+
+int test_calling_check_ptr_after_deref_3 (int *q)
+{
+ int v = *q; /* { dg-message "pointer 'q' is dereferenced here" } */
+ v += __analyzer_check_ptr (q);
+ if (q) /* { dg-warning "check of 'q' for NULL after already dereferencing it" } */
+ *q = 17;
+ return v;
+}
+
+static int __attribute__((noinline))
+__analyzer_deref_ptr (int *p)
+{
+ return *p;
+}
+
+int test_calling_check_ptr_after_calling_deref_1 (int *q)
+{
+ int v = __analyzer_deref_ptr (q);
+ v += __analyzer_check_ptr (q);
+ return v;
+}
+
+int test_calling_check_ptr_after_calling_deref_2 (int *q)
+{
+ int v = __analyzer_deref_ptr (q);
+ v += __analyzer_check_ptr (q);
+ *q = 17;
+ return v;
+}
+
+int test_calling_check_ptr_after_calling_deref_3 (int *q)
+{
+ int v = __analyzer_deref_ptr (q);
+ v += __analyzer_check_ptr (q);
+ if (q)
+ *q = 17;
+ return v;
+}
+
+int test_checking_ptr_after_calling_deref (int *q)
+{
+ int v = __analyzer_deref_ptr (q);
+ if (q)
+ return 0;
+ return v;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-2.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-2.c
new file mode 100644
index 00000000000..c0409c42d89
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-2.c
@@ -0,0 +1,130 @@
+#include <stdio.h>
+
+struct st
+{
+ char *str;
+ int i;
+};
+
+int test_1 (struct st *p)
+{
+ fprintf (stderr, "str: %s\n", p->str); /* { dg-message "pointer 'p' is dereferenced here" } */
+ if (!p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ return -1;
+ return p->i;
+}
+
+int test_2 (int flag_a, int flag_b, struct st *p)
+{
+ if (flag_a)
+ {
+ int j = p->i; /* { dg-message "pointer 'p' is dereferenced here" } */
+ if (flag_b && p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ return 1;
+ return j;
+ }
+ return 2;
+}
+
+int test_3 (struct st *a, struct st *b)
+{
+ if (!a)
+ return b->i;
+ if (!b)
+ return a->i;
+ return 0;
+}
+
+int test_4 (struct st *p)
+{
+ int *q = &p->i;
+ if (!p)
+ return -1;
+ return *q;
+}
+
+void test_check_after_strlen (const char *str)
+{
+ size_t len_a = __builtin_strlen (str); /* { dg-message "pointer 'str' is dereferenced here" } */
+ size_t len_b = str ? __builtin_strlen (str) : 0; /* { dg-warning "check of 'str' for NULL after already dereferencing it" } */
+}
+
+void test_6 (struct st *a, struct st *b)
+{
+ int diff = a->i - b->i; /* { dg-message "pointer 'b' is dereferenced here" } */
+
+ /* ... */
+
+ if (b) /* { dg-warning "check of 'b' for NULL after already dereferencing it" } */
+ fprintf (stderr, "str: %s\n", b->str);
+}
+
+void test_check_after_strcmp (const char *s1, const char *s2)
+{
+ if (!__builtin_strcmp (s1, s2)) /* { dg-message "pointer 's1' is dereferenced here" } */
+ return;
+
+ /* ... */
+
+ if (s1) /* { dg-warning "check of 's1' for NULL after already dereferencing it" } */
+ return;
+}
+
+void test_more_than_one_deref (struct st *p)
+{
+ char *str = p->str; /* { dg-message "pointer 'p' is dereferenced here" } */
+ int i = p->i;
+
+ /* ... */
+
+ if (p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ return;
+
+ /* ... */
+}
+
+void test_deref_under_another_name (struct st *p)
+{
+ struct st *q = p;
+ int i = q->i; /* { dg-message "pointer 'p' is dereferenced here" } */
+
+ /* ... */
+
+ if (p) /* { dg-warning "check of 'p' for NULL after already dereferencing it" } */
+ return;
+
+ /* ... */
+}
+
+void test_check_after_memcpy_src (struct st *dst, struct st *src)
+{
+ __builtin_memcpy (dst, src, sizeof (struct st)); /* { dg-message "pointer 'src' is dereferenced here" } */
+
+ /* ... */
+
+ if (!src) /* { dg-warning "check of 'src' for NULL after already dereferencing it" } */
+ return;
+
+ /* ... */
+}
+
+void test_check_after_memcpy_dst (struct st *dst, struct st *src)
+{
+ __builtin_memcpy (dst, src, sizeof (struct st)); /* { dg-message "pointer 'dst' is dereferenced here" } */
+
+ /* ... */
+
+ if (!dst) /* { dg-warning "check of 'dst' for NULL after already dereferencing it" } */
+ return;
+
+ /* ... */
+}
+
+void test_merger (int *p, int flag)
+{
+ int x = *p;
+ if (flag)
+ __builtin_free (p);
+ if (!flag)
+ __builtin_free (p); /* { dg-bogus "double-'free'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr77425.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr77425.c
new file mode 100644
index 00000000000..1ceea97b422
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr77425.c
@@ -0,0 +1,43 @@
+/* Fixed in r7-2945-g61f46d0e6dd568.
+ Simplified from gcc/ipa-devirt.c. */
+
+#define NULL ((void *)0)
+typedef struct odr_type_d {
+ /* .... */
+ int id;
+ /* .... */
+} *odr_type;
+static odr_type **odr_types_ptr;
+#define odr_types (*odr_types_ptr) /* { dg-message "pointer 'odr_types_ptr' is dereferenced here" } */
+
+int cond, other_cond;
+
+odr_type some_logic ();
+
+odr_type
+get_odr_type (/* ... */)
+{
+ /* .... */
+ odr_type val = NULL;
+ /* .... */
+
+ val = some_logic ();
+
+ /* .... */
+ if (cond)
+ {
+ /* .... */
+ }
+ else if (other_cond)
+ {
+ odr_types[val->id] = 0; /* { dg-message "in expansion of macro 'odr_types'" } */
+ /* .... */
+ if (odr_types_ptr) /* { dg-warning "check of 'odr_types_ptr' for NULL after already dereferencing it" } */
+ {
+ /* .... */
+ val->id = 42;
+ }
+ /* .... */
+ }
+ return val;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
index d69a60530eb..6b5590a433a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c
@@ -625,5 +625,14 @@ void test_50c (void)
free (&&my_label); /* { dg-warning "'free' of '&my_label' which points to memory not on the heap \\\[CWE-590\\\]" } */
}
+/* Double free after unconditional dereference. */
+
+int test_51 (int *p)
+{
+ int result = *p;
+ free (p); /* { dg-message "first 'free' here" } */
+ free (p); /* { dg-warning "double-'free' of 'p'" } */
+ return result;
+}
/* { dg-prune-output "\\\[-Wfree-nonheap-object" } */
diff --git a/gcc/tristate.h b/gcc/tristate.h
index 8476b9ab370..bccfa8bf523 100644
--- a/gcc/tristate.h
+++ b/gcc/tristate.h
@@ -38,6 +38,7 @@ class tristate {
const char *as_string () const;
bool is_known () const { return m_value != TS_UNKNOWN; }
+ bool is_unknown () const { return m_value == TS_UNKNOWN; }
bool is_true () const { return m_value == TS_TRUE; }
bool is_false () const { return m_value == TS_FALSE; }
--
2.26.3
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2022-11-10 18:37 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-10 18:37 [committed] analyzer: new warning: -Wanalyzer-deref-before-check [PR99671] David Malcolm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).