analyzer: reimplement -Wanalyzer-use-of-uninitialized-value [PR95006 et al]
The initial gcc 10 era commit of the analyzer (in757bf1dff5
) had an implementation of -Wanalyzer-use-of-uninitialized-value, but was sufficiently buggy that I removed it in78b9783774
before the release of gcc 10.1 This patch reintroduces the warning, heavily rewritten, with (I hope) a less buggy implementation this time, for GCC 12. gcc/analyzer/ChangeLog: PR analyzer/95006 PR analyzer/94713 PR analyzer/94714 * analyzer.cc (maybe_reconstruct_from_def_stmt): Split out GIMPLE_ASSIGN case into... (get_diagnostic_tree_for_gassign_1): New. (get_diagnostic_tree_for_gassign): New. * analyzer.h (get_diagnostic_tree_for_gassign): New decl. * analyzer.opt (Wanalyzer-write-to-string-literal): New. * constraint-manager.cc (class svalue_purger): New. (constraint_manager::purge_state_involving): New. * constraint-manager.h (constraint_manager::purge_state_involving): New. * diagnostic-manager.cc (saved_diagnostic::supercedes_p): New. (dedupe_winners::handle_interactions): New. (diagnostic_manager::emit_saved_diagnostics): Call it. * diagnostic-manager.h (saved_diagnostic::supercedes_p): New decl. * engine.cc (impl_region_model_context::warn): Convert return type to bool. Return false if the diagnostic isn't saved. (impl_region_model_context::purge_state_involving): New. (impl_sm_context::get_state): Use NULL ctxt when querying old rvalue. (impl_sm_context::set_next_state): Use new sval when querying old state. (class dump_path_diagnostic): Move to region-model.cc (exploded_node::on_stmt): Move to on_stmt_pre and on_stmt_post. Remove call to purge_state_involving. (exploded_node::on_stmt_pre): New, based on the above. Move most of it to region_model::on_stmt_pre. (exploded_node::on_stmt_post): Likewise, moving to region_model::on_stmt_post. (class stale_jmp_buf): Fix parent class to use curiously recurring template pattern. (feasibility_state::maybe_update_for_edge): Call on_call_pre and on_call_post on gcalls. * exploded-graph.h (impl_region_model_context::warn): Return bool. (impl_region_model_context::purge_state_involving): New decl. (exploded_node::on_stmt_pre): New decl. (exploded_node::on_stmt_post): New decl. * pending-diagnostic.h (pending_diagnostic::use_of_uninit_p): New. (pending_diagnostic::supercedes_p): New. * program-state.cc (sm_state_map::get_state): Inherit state for conjured_svalue as well as initial_svalue. (sm_state_map::purge_state_involving): Also support SK_CONJURED. * region-model-impl-calls.cc (call_details::get_uncertainty): Handle m_ctxt being NULL. (call_details::get_or_create_conjured_svalue): New. (region_model::impl_call_fgets): New. (region_model::impl_call_fread): New. * region-model-manager.cc (region_model_manager::get_or_create_initial_value): Return an uninitialized poisoned value for regions that can't have initial values. * region-model-reachability.cc (reachable_regions::mark_escaped_clusters): Handle ctxt being NULL. * region-model.cc (region_to_value_map::purge_state_involving): New. (poisoned_value_diagnostic::use_of_uninit_p): New. (poisoned_value_diagnostic::emit): Handle POISON_KIND_UNINIT. (poisoned_value_diagnostic::describe_final_event): Likewise. (region_model::check_for_poison): New. (region_model::on_assignment): Call it. (class dump_path_diagnostic): Move here from engine.cc. (region_model::on_stmt_pre): New, based on exploded_node::on_stmt. (region_model::on_call_pre): Move the setting of the LHS to a conjured svalue to before the checks for specific functions. Handle "fgets", "fgets_unlocked", and "fread". (region_model::purge_state_involving): New. (region_model::handle_unrecognized_call): Handle ctxt being NULL. (region_model::get_rvalue): Call check_for_poison. (selftest::test_stack_frames): Use NULL for context when getting uninitialized rvalue. (selftest::test_alloca): Likewise. * region-model.h (region_to_value_map::purge_state_involving): New decl. (call_details::get_or_create_conjured_svalue): New decl. (region_model::on_stmt_pre): New decl. (region_model::purge_state_involving): New decl. (region_model::impl_call_fgets): New decl. (region_model::impl_call_fread): New decl. (region_model::check_for_poison): New decl. (region_model_context::warn): Return bool. (region_model_context::purge_state_involving): New. (noop_region_model_context::warn): Return bool. (noop_region_model_context::purge_state_involving): New. (test_region_model_context:: warn): Return bool. * region.cc (region::get_memory_space): New. (region::can_have_initial_svalue_p): New. (region::involves_p): New. * region.h (enum memory_space): New. (region::get_memory_space): New decl. (region::can_have_initial_svalue_p): New decl. (region::involves_p): New decl. * sm-malloc.cc (use_after_free::supercedes_p): New. * store.cc (binding_cluster::purge_state_involving): New. (store::purge_state_involving): New. * store.h (class symbolic_binding): New forward decl. (binding_key::dyn_cast_symbolic_binding): New. (symbolic_binding::dyn_cast_symbolic_binding): New. (binding_cluster::purge_state_involving): New. (store::purge_state_involving): New. * svalue.cc (svalue::can_merge_p): Reject attempts to merge poisoned svalues with other svalues, so that we identify paths in which a variable is conditionally uninitialized. (involvement_visitor::visit_conjured_svalue): New. (svalue::involves_p): Also handle SK_CONJURED. (poison_kind_to_str): Handle POISON_KIND_UNINIT. (poisoned_svalue::maybe_fold_bits_within): New. * svalue.h (enum poison_kind): Add POISON_KIND_UNINIT. (poisoned_svalue::maybe_fold_bits_within): New decl. gcc/ChangeLog: PR analyzer/95006 PR analyzer/94713 PR analyzer/94714 * doc/invoke.texi: Add -Wanalyzer-use-of-uninitialized-value. gcc/testsuite/ChangeLog: PR analyzer/95006 PR analyzer/94713 PR analyzer/94714 * g++.dg/analyzer/pr93212.C: Update location of warning. * g++.dg/analyzer/pr94011.C: Add -Wno-analyzer-use-of-uninitialized-value. * g++.dg/analyzer/pr94503.C: Likewise. * gcc.dg/analyzer/clobbers-1.c: Convert "f" from a local to a param to avoid uninitialized warning. * gcc.dg/analyzer/data-model-1.c (test_12): Add test for uninitialized value on result of alloca. (test_12a): Add expected warning. (test_12c): Likewise. (test_19): Likewise. (test_29b): Likewise. (test_29c): Likewise. (test_37): Remove xfail. (test_37a): Likewise. * gcc.dg/analyzer/data-model-20.c: Add warning about leak. * gcc.dg/analyzer/explode-2.c: Remove params; add -Wno-analyzer-too-complex, -Wno-analyzer-malloc-leak, and xfails. Initialize the locals. * gcc.dg/analyzer/explode-2a.c: Initialize the locals. Add expected leak. * gcc.dg/analyzer/fgets-1.c: New test. * gcc.dg/analyzer/fread-1.c: New test. * gcc.dg/analyzer/malloc-1.c (test_16): Add expected warning. (test_40): Likewise. * gcc.dg/analyzer/memset-CVE-2017-18549-1.c: Check for uninitialized padding. * gcc.dg/analyzer/pr93355-localealias-feasibility.c (fread): New decl. (read_alias_file): Call it. * gcc.dg/analyzer/pr94047.c: Add expected warnings. * gcc.dg/analyzer/pr94851-2.c: Likewise. * gcc.dg/analyzer/pr96841.c: Convert local to a param. * gcc.dg/analyzer/pr98628.c: Likewise. * gcc.dg/analyzer/pr99042.c: Updated expected location of leak diagnostics. * gcc.dg/analyzer/symbolic-1.c: Add expected warnings. * gcc.dg/analyzer/symbolic-7.c: Likewise. * gcc.dg/analyzer/torture/pr93649.c: Add expected warning. Skip with -fno-fat-lto-objects. * gcc.dg/analyzer/uninit-1.c: New test. * gcc.dg/analyzer/uninit-2.c: New test. * gcc.dg/analyzer/uninit-3.c: New test. * gcc.dg/analyzer/uninit-4.c: New test. * gcc.dg/analyzer/uninit-pr94713.c: New test. * gcc.dg/analyzer/uninit-pr94714.c: New test. * gcc.dg/analyzer/use-after-free-2.c: New test. * gcc.dg/analyzer/use-after-free-3.c: New test. * gcc.dg/analyzer/zlib-3.c: Add expected warning. * gcc.dg/analyzer/zlib-6.c: Convert locals to params to avoid uninitialized warnings. Remove xfail. * gcc.dg/analyzer/zlib-6a.c: New test, based on the old version of the above. * gfortran.dg/analyzer/pr97668.f: Add -Wno-analyzer-use-of-uninitialized-value and -Wno-analyzer-too-complex. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
parent
98cd4d123a
commit
33255ad3ac
57 changed files with 1232 additions and 296 deletions
|
@ -63,6 +63,51 @@ get_stmt_location (const gimple *stmt, function *fun)
|
|||
static tree
|
||||
fixup_tree_for_diagnostic_1 (tree expr, hash_set<tree> *visited);
|
||||
|
||||
/* Attemp to generate a tree for the LHS of ASSIGN_STMT.
|
||||
VISITED must be non-NULL; it is used to ensure termination. */
|
||||
|
||||
static tree
|
||||
get_diagnostic_tree_for_gassign_1 (const gassign *assign_stmt,
|
||||
hash_set<tree> *visited)
|
||||
{
|
||||
enum tree_code code = gimple_assign_rhs_code (assign_stmt);
|
||||
|
||||
/* Reverse the effect of extract_ops_from_tree during
|
||||
gimplification. */
|
||||
switch (get_gimple_rhs_class (code))
|
||||
{
|
||||
default:
|
||||
case GIMPLE_INVALID_RHS:
|
||||
gcc_unreachable ();
|
||||
case GIMPLE_TERNARY_RHS:
|
||||
case GIMPLE_BINARY_RHS:
|
||||
case GIMPLE_UNARY_RHS:
|
||||
{
|
||||
tree t = make_node (code);
|
||||
TREE_TYPE (t) = TREE_TYPE (gimple_assign_lhs (assign_stmt));
|
||||
unsigned num_rhs_args = gimple_num_ops (assign_stmt) - 1;
|
||||
for (unsigned i = 0; i < num_rhs_args; i++)
|
||||
{
|
||||
tree op = gimple_op (assign_stmt, i + 1);
|
||||
if (op)
|
||||
{
|
||||
op = fixup_tree_for_diagnostic_1 (op, visited);
|
||||
if (op == NULL_TREE)
|
||||
return NULL_TREE;
|
||||
}
|
||||
TREE_OPERAND (t, i) = op;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
case GIMPLE_SINGLE_RHS:
|
||||
{
|
||||
tree op = gimple_op (assign_stmt, 1);
|
||||
op = fixup_tree_for_diagnostic_1 (op, visited);
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Subroutine of fixup_tree_for_diagnostic_1, called on SSA names.
|
||||
Attempt to reconstruct a a tree expression for SSA_NAME
|
||||
based on its def-stmt.
|
||||
|
@ -91,45 +136,8 @@ maybe_reconstruct_from_def_stmt (tree ssa_name,
|
|||
/* Can't handle these. */
|
||||
return NULL_TREE;
|
||||
case GIMPLE_ASSIGN:
|
||||
{
|
||||
enum tree_code code = gimple_assign_rhs_code (def_stmt);
|
||||
|
||||
/* Reverse the effect of extract_ops_from_tree during
|
||||
gimplification. */
|
||||
switch (get_gimple_rhs_class (code))
|
||||
{
|
||||
default:
|
||||
case GIMPLE_INVALID_RHS:
|
||||
gcc_unreachable ();
|
||||
case GIMPLE_TERNARY_RHS:
|
||||
case GIMPLE_BINARY_RHS:
|
||||
case GIMPLE_UNARY_RHS:
|
||||
{
|
||||
tree t = make_node (code);
|
||||
TREE_TYPE (t) = TREE_TYPE (ssa_name);
|
||||
unsigned num_rhs_args = gimple_num_ops (def_stmt) - 1;
|
||||
for (unsigned i = 0; i < num_rhs_args; i++)
|
||||
{
|
||||
tree op = gimple_op (def_stmt, i + 1);
|
||||
if (op)
|
||||
{
|
||||
op = fixup_tree_for_diagnostic_1 (op, visited);
|
||||
if (op == NULL_TREE)
|
||||
return NULL_TREE;
|
||||
}
|
||||
TREE_OPERAND (t, i) = op;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
case GIMPLE_SINGLE_RHS:
|
||||
{
|
||||
tree op = gimple_op (def_stmt, 1);
|
||||
op = fixup_tree_for_diagnostic_1 (op, visited);
|
||||
return op;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
return get_diagnostic_tree_for_gassign_1
|
||||
(as_a <const gassign *> (def_stmt), visited);
|
||||
case GIMPLE_CALL:
|
||||
{
|
||||
gcall *call_stmt = as_a <gcall *> (def_stmt);
|
||||
|
@ -193,6 +201,15 @@ fixup_tree_for_diagnostic (tree expr)
|
|||
return fixup_tree_for_diagnostic_1 (expr, &visited);
|
||||
}
|
||||
|
||||
/* Attempt to generate a tree for the LHS of ASSIGN_STMT. */
|
||||
|
||||
tree
|
||||
get_diagnostic_tree_for_gassign (const gassign *assign_stmt)
|
||||
{
|
||||
hash_set<tree> visited;
|
||||
return get_diagnostic_tree_for_gassign_1 (assign_stmt, &visited);
|
||||
}
|
||||
|
||||
} // namespace ana
|
||||
|
||||
/* Helper function for checkers. Is the CALL to the given function name,
|
||||
|
|
|
@ -112,6 +112,7 @@ extern void print_quoted_type (pretty_printer *pp, tree t);
|
|||
extern int readability_comparator (const void *p1, const void *p2);
|
||||
extern int tree_cmp (const void *p1, const void *p2);
|
||||
extern tree fixup_tree_for_diagnostic (tree);
|
||||
extern tree get_diagnostic_tree_for_gassign (const gassign *);
|
||||
|
||||
/* A tree, extended with stack frame information for locals, so that
|
||||
we can distinguish between different values of locals within a potentially
|
||||
|
|
|
@ -134,6 +134,10 @@ Wanalyzer-write-to-string-literal
|
|||
Common Var(warn_analyzer_write_to_string_literal) Init(1) Warning
|
||||
Warn about code paths which attempt to write to a string literal.
|
||||
|
||||
Wanalyzer-use-of-uninitialized-value
|
||||
Common Var(warn_analyzer_use_of_uninitialized_value) Init(1) Warning
|
||||
Warn about code paths in which an uninitialized value is used.
|
||||
|
||||
Wanalyzer-too-complex
|
||||
Common Var(warn_analyzer_too_complex) Init(0) Warning
|
||||
Warn if the code is too complicated for the analyzer to fully explore.
|
||||
|
|
|
@ -1653,6 +1653,29 @@ on_liveness_change (const svalue_set &live_svalues,
|
|||
purge (p, NULL);
|
||||
}
|
||||
|
||||
class svalue_purger
|
||||
{
|
||||
public:
|
||||
svalue_purger (const svalue *sval) : m_sval (sval) {}
|
||||
|
||||
bool should_purge_p (const svalue *sval) const
|
||||
{
|
||||
return sval->involves_p (m_sval);
|
||||
}
|
||||
|
||||
private:
|
||||
const svalue *m_sval;
|
||||
};
|
||||
|
||||
/* Purge any state involving SVAL. */
|
||||
|
||||
void
|
||||
constraint_manager::purge_state_involving (const svalue *sval)
|
||||
{
|
||||
svalue_purger p (sval);
|
||||
purge (p, NULL);
|
||||
}
|
||||
|
||||
/* Comparator for use by constraint_manager::canonicalize.
|
||||
Sort a pair of equiv_class instances, using the representative
|
||||
svalue as a sort key. */
|
||||
|
|
|
@ -269,6 +269,7 @@ public:
|
|||
|
||||
void on_liveness_change (const svalue_set &live_svalues,
|
||||
const region_model *model);
|
||||
void purge_state_involving (const svalue *sval);
|
||||
|
||||
void canonicalize ();
|
||||
|
||||
|
|
|
@ -722,6 +722,18 @@ saved_diagnostic::add_duplicate (saved_diagnostic *other)
|
|||
m_duplicates.safe_push (other);
|
||||
}
|
||||
|
||||
/* Return true if this diagnostic supercedes OTHER, and that OTHER should
|
||||
therefore not be emitted. */
|
||||
|
||||
bool
|
||||
saved_diagnostic::supercedes_p (const saved_diagnostic &other) const
|
||||
{
|
||||
/* They should be at the same stmt. */
|
||||
if (m_stmt != other.m_stmt)
|
||||
return false;
|
||||
return m_d->supercedes_p (*other.m_d);
|
||||
}
|
||||
|
||||
/* State for building a checker_path from a particular exploded_path.
|
||||
In particular, this precomputes reachability information: the set of
|
||||
source enodes for which a path be found to the diagnostic enode. */
|
||||
|
@ -1021,6 +1033,38 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
/* Handle interactions between the dedupe winners, so that some
|
||||
diagnostics can supercede others (of different kinds).
|
||||
|
||||
We want use-after-free to supercede use-of-unitialized-value,
|
||||
so that if we have these at the same stmt, we don't emit
|
||||
a use-of-uninitialized, just the use-after-free. */
|
||||
|
||||
void handle_interactions (diagnostic_manager *dm)
|
||||
{
|
||||
LOG_SCOPE (dm->get_logger ());
|
||||
auto_vec<const dedupe_key *> superceded;
|
||||
for (auto outer : m_map)
|
||||
{
|
||||
const saved_diagnostic *outer_sd = outer.second;
|
||||
for (auto inner : m_map)
|
||||
{
|
||||
const saved_diagnostic *inner_sd = inner.second;
|
||||
if (inner_sd->supercedes_p (*outer_sd))
|
||||
{
|
||||
superceded.safe_push (outer.first);
|
||||
if (dm->get_logger ())
|
||||
dm->log ("sd[%i] \"%s\" superceded by sd[%i] \"%s\"",
|
||||
outer_sd->get_index (), outer_sd->m_d->get_kind (),
|
||||
inner_sd->get_index (), inner_sd->m_d->get_kind ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto iter : superceded)
|
||||
m_map.remove (iter);
|
||||
}
|
||||
|
||||
/* Emit the simplest diagnostic within each set. */
|
||||
|
||||
void emit_best (diagnostic_manager *dm,
|
||||
|
@ -1095,6 +1139,8 @@ diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
|
|||
FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
|
||||
best_candidates.add (get_logger (), &pf, sd);
|
||||
|
||||
best_candidates.handle_interactions (this);
|
||||
|
||||
/* For each dedupe-key, call emit_saved_diagnostic on the "best"
|
||||
saved_diagnostic. */
|
||||
best_candidates.emit_best (this, eg);
|
||||
|
|
|
@ -58,6 +58,8 @@ public:
|
|||
|
||||
unsigned get_index () const { return m_idx; }
|
||||
|
||||
bool supercedes_p (const saved_diagnostic &other) const;
|
||||
|
||||
//private:
|
||||
const state_machine *m_sm;
|
||||
const exploded_node *m_enode;
|
||||
|
|
|
@ -108,14 +108,29 @@ impl_region_model_context (program_state *state,
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
impl_region_model_context::warn (pending_diagnostic *d)
|
||||
{
|
||||
LOG_FUNC (get_logger ());
|
||||
if (m_stmt == NULL && m_stmt_finder == NULL)
|
||||
{
|
||||
if (get_logger ())
|
||||
get_logger ()->log ("rejecting diagnostic: no stmt");
|
||||
delete d;
|
||||
return false;
|
||||
}
|
||||
if (m_eg)
|
||||
m_eg->get_diagnostic_manager ().add_diagnostic
|
||||
(m_enode_for_diag, m_enode_for_diag->get_supernode (),
|
||||
m_stmt, m_stmt_finder, d);
|
||||
{
|
||||
m_eg->get_diagnostic_manager ().add_diagnostic
|
||||
(m_enode_for_diag, m_enode_for_diag->get_supernode (),
|
||||
m_stmt, m_stmt_finder, d);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete d;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -155,6 +170,19 @@ impl_region_model_context::get_uncertainty ()
|
|||
return m_uncertainty;
|
||||
}
|
||||
|
||||
/* Purge state involving SVAL. The region_model has already been purged,
|
||||
so we only need to purge other state in the program_state:
|
||||
the sm-state. */
|
||||
|
||||
void
|
||||
impl_region_model_context::purge_state_involving (const svalue *sval)
|
||||
{
|
||||
int i;
|
||||
sm_state_map *smap;
|
||||
FOR_EACH_VEC_ELT (m_new_state->m_checker_states, i, smap)
|
||||
smap->purge_state_involving (sval, m_ext_state);
|
||||
}
|
||||
|
||||
/* struct setjmp_record. */
|
||||
|
||||
int
|
||||
|
@ -230,16 +258,15 @@ public:
|
|||
return model->get_fndecl_for_call (call, &old_ctxt);
|
||||
}
|
||||
|
||||
state_machine::state_t get_state (const gimple *stmt,
|
||||
state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED,
|
||||
tree var)
|
||||
{
|
||||
logger * const logger = get_logger ();
|
||||
LOG_FUNC (logger);
|
||||
impl_region_model_context old_ctxt
|
||||
(m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
|
||||
NULL, stmt);
|
||||
/* Use NULL ctxt on this get_rvalue call to avoid triggering
|
||||
uninitialized value warnings. */
|
||||
const svalue *var_old_sval
|
||||
= m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
|
||||
= m_old_state->m_region_model->get_rvalue (var, NULL);
|
||||
|
||||
state_machine::state_t current
|
||||
= m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
|
||||
|
@ -263,12 +290,6 @@ public:
|
|||
{
|
||||
logger * const logger = get_logger ();
|
||||
LOG_FUNC (logger);
|
||||
impl_region_model_context old_ctxt
|
||||
(m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
|
||||
NULL, stmt);
|
||||
const svalue *var_old_sval
|
||||
= m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
|
||||
|
||||
impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
|
||||
m_old_state, m_new_state,
|
||||
NULL,
|
||||
|
@ -278,8 +299,9 @@ public:
|
|||
const svalue *origin_new_sval
|
||||
= m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
|
||||
|
||||
/* We use the new sval here to avoid issues with uninitialized values. */
|
||||
state_machine::state_t current
|
||||
= m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ());
|
||||
= m_old_smap->get_state (var_new_sval, m_eg.get_ext_state ());
|
||||
if (logger)
|
||||
logger->log ("%s: state transition of %qE: %s -> %s",
|
||||
m_sm.get_name (),
|
||||
|
@ -1160,26 +1182,6 @@ fndecl_has_gimple_body_p (tree fndecl)
|
|||
|
||||
namespace ana {
|
||||
|
||||
/* A pending_diagnostic subclass for implementing "__analyzer_dump_path". */
|
||||
|
||||
class dump_path_diagnostic
|
||||
: public pending_diagnostic_subclass<dump_path_diagnostic>
|
||||
{
|
||||
public:
|
||||
bool emit (rich_location *richloc) FINAL OVERRIDE
|
||||
{
|
||||
inform (richloc, "path");
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "dump_path_diagnostic"; }
|
||||
|
||||
bool operator== (const dump_path_diagnostic &) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/* Modify STATE in place, applying the effects of the stmt at this node's
|
||||
point. */
|
||||
|
||||
|
@ -1218,89 +1220,8 @@ exploded_node::on_stmt (exploded_graph &eg,
|
|||
bool unknown_side_effects = false;
|
||||
bool terminate_path = false;
|
||||
|
||||
switch (gimple_code (stmt))
|
||||
{
|
||||
default:
|
||||
/* No-op for now. */
|
||||
break;
|
||||
|
||||
case GIMPLE_ASSIGN:
|
||||
{
|
||||
const gassign *assign = as_a <const gassign *> (stmt);
|
||||
state->m_region_model->on_assignment (assign, &ctxt);
|
||||
}
|
||||
break;
|
||||
|
||||
case GIMPLE_ASM:
|
||||
/* No-op for now. */
|
||||
break;
|
||||
|
||||
case GIMPLE_CALL:
|
||||
{
|
||||
/* Track whether we have a gcall to a function that's not recognized by
|
||||
anything, for which we don't have a function body, or for which we
|
||||
don't know the fndecl. */
|
||||
const gcall *call = as_a <const gcall *> (stmt);
|
||||
|
||||
/* Debugging/test support. */
|
||||
if (is_special_named_call_p (call, "__analyzer_describe", 2))
|
||||
state->m_region_model->impl_call_analyzer_describe (call, &ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump" by dumping state
|
||||
to stderr. */
|
||||
state->dump (eg.get_ext_state (), true);
|
||||
}
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_capacity", 1))
|
||||
state->m_region_model->impl_call_analyzer_dump_capacity (call, &ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump_path" by queuing a
|
||||
diagnostic at this exploded_node. */
|
||||
ctxt.warn (new dump_path_diagnostic ());
|
||||
}
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_region_model",
|
||||
0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump_region_model" by dumping
|
||||
the region model's state to stderr. */
|
||||
state->m_region_model->dump (false);
|
||||
}
|
||||
else if (is_special_named_call_p (call, "__analyzer_eval", 1))
|
||||
state->m_region_model->impl_call_analyzer_eval (call, &ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_break", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_break" by triggering a
|
||||
breakpoint. */
|
||||
/* TODO: is there a good cross-platform way to do this? */
|
||||
raise (SIGINT);
|
||||
}
|
||||
else if (is_special_named_call_p (call,
|
||||
"__analyzer_dump_exploded_nodes",
|
||||
1))
|
||||
{
|
||||
/* This is handled elsewhere. */
|
||||
}
|
||||
else if (is_setjmp_call_p (call))
|
||||
state->m_region_model->on_setjmp (call, this, &ctxt);
|
||||
else if (is_longjmp_call_p (call))
|
||||
{
|
||||
on_longjmp (eg, call, state, &ctxt);
|
||||
return on_stmt_flags::terminate_path ();
|
||||
}
|
||||
else
|
||||
unknown_side_effects
|
||||
= state->m_region_model->on_call_pre (call, &ctxt, &terminate_path);
|
||||
}
|
||||
break;
|
||||
|
||||
case GIMPLE_RETURN:
|
||||
{
|
||||
const greturn *return_ = as_a <const greturn *> (stmt);
|
||||
state->m_region_model->on_return (return_, &ctxt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
on_stmt_pre (eg, stmt, state, &terminate_path,
|
||||
&unknown_side_effects, &ctxt);
|
||||
|
||||
if (terminate_path)
|
||||
return on_stmt_flags::terminate_path ();
|
||||
|
@ -1316,41 +1237,71 @@ exploded_node::on_stmt (exploded_graph &eg,
|
|||
impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
|
||||
old_smap, new_smap);
|
||||
|
||||
/* If we're at the def-stmt of an SSA name, then potentially purge
|
||||
any sm-state for svalues that involve that SSA name. This avoids
|
||||
false positives in loops, since a symbolic value referring to the
|
||||
SSA name will be referring to the previous value of that SSA name.
|
||||
For example, in:
|
||||
while ((e = hashmap_iter_next(&iter))) {
|
||||
struct oid2strbuf *e_strbuf = (struct oid2strbuf *)e;
|
||||
free (e_strbuf->value);
|
||||
}
|
||||
at the def-stmt of e_8:
|
||||
e_8 = hashmap_iter_next (&iter);
|
||||
we should purge the "freed" state of:
|
||||
INIT_VAL(CAST_REG(‘struct oid2strbuf’, (*INIT_VAL(e_8))).value)
|
||||
which is the "e_strbuf->value" value from the previous iteration,
|
||||
or we will erroneously report a double-free - the "e_8" within it
|
||||
refers to the previous value. */
|
||||
if (tree lhs = gimple_get_lhs (stmt))
|
||||
if (TREE_CODE (lhs) == SSA_NAME)
|
||||
{
|
||||
const svalue *sval
|
||||
= old_state.m_region_model->get_rvalue (lhs, &ctxt);
|
||||
new_smap->purge_state_involving (sval, eg.get_ext_state ());
|
||||
}
|
||||
|
||||
/* Allow the state_machine to handle the stmt. */
|
||||
if (sm.on_stmt (&sm_ctxt, snode, stmt))
|
||||
unknown_side_effects = false;
|
||||
}
|
||||
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
state->m_region_model->on_call_post (call, unknown_side_effects, &ctxt);
|
||||
on_stmt_post (stmt, state, unknown_side_effects, &ctxt);
|
||||
|
||||
return on_stmt_flags ();
|
||||
}
|
||||
|
||||
/* Handle the pre-sm-state part of STMT, modifying STATE in-place.
|
||||
Write true to *OUT_TERMINATE_PATH if the path should be terminated.
|
||||
Write true to *OUT_UNKNOWN_SIDE_EFFECTS if the stmt has unknown
|
||||
side effects. */
|
||||
|
||||
void
|
||||
exploded_node::on_stmt_pre (exploded_graph &eg,
|
||||
const gimple *stmt,
|
||||
program_state *state,
|
||||
bool *out_terminate_path,
|
||||
bool *out_unknown_side_effects,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
/* Handle special-case calls that require the full program_state. */
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
{
|
||||
if (is_special_named_call_p (call, "__analyzer_dump", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump" by dumping state
|
||||
to stderr. */
|
||||
state->dump (eg.get_ext_state (), true);
|
||||
return;
|
||||
}
|
||||
else if (is_setjmp_call_p (call))
|
||||
{
|
||||
state->m_region_model->on_setjmp (call, this, ctxt);
|
||||
return;
|
||||
}
|
||||
else if (is_longjmp_call_p (call))
|
||||
{
|
||||
on_longjmp (eg, call, state, ctxt);
|
||||
*out_terminate_path = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, defer to m_region_model. */
|
||||
state->m_region_model->on_stmt_pre (stmt,
|
||||
out_terminate_path,
|
||||
out_unknown_side_effects,
|
||||
ctxt);
|
||||
}
|
||||
|
||||
/* Handle the post-sm-state part of STMT, modifying STATE in-place. */
|
||||
|
||||
void
|
||||
exploded_node::on_stmt_post (const gimple *stmt,
|
||||
program_state *state,
|
||||
bool unknown_side_effects,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
state->m_region_model->on_call_post (call, unknown_side_effects, ctxt);
|
||||
}
|
||||
|
||||
/* Consider the effect of following superedge SUCC from this node.
|
||||
|
||||
Return true if it's feasible to follow the edge, or false
|
||||
|
@ -1415,7 +1366,7 @@ valid_longjmp_stack_p (const program_point &longjmp_point,
|
|||
where the enclosing function of the "setjmp" has returned (and thus
|
||||
the stack frame no longer exists). */
|
||||
|
||||
class stale_jmp_buf : public pending_diagnostic_subclass<dump_path_diagnostic>
|
||||
class stale_jmp_buf : public pending_diagnostic_subclass<stale_jmp_buf>
|
||||
{
|
||||
public:
|
||||
stale_jmp_buf (const gcall *setjmp_call, const gcall *longjmp_call,
|
||||
|
@ -3763,6 +3714,13 @@ feasibility_state::maybe_update_for_edge (logger *logger,
|
|||
|
||||
if (const gassign *assign = dyn_cast <const gassign *> (stmt))
|
||||
m_model.on_assignment (assign, NULL);
|
||||
else if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
{
|
||||
bool terminate_path;
|
||||
bool unknown_side_effects
|
||||
= m_model.on_call_pre (call, NULL, &terminate_path);
|
||||
m_model.on_call_post (call, unknown_side_effects, NULL);
|
||||
}
|
||||
else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
|
||||
m_model.on_return (return_, NULL);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class impl_region_model_context : public region_model_context
|
|||
uncertainty_t *uncertainty,
|
||||
logger *logger = NULL);
|
||||
|
||||
void warn (pending_diagnostic *d) FINAL OVERRIDE;
|
||||
bool warn (pending_diagnostic *d) FINAL OVERRIDE;
|
||||
void on_svalue_leak (const svalue *) OVERRIDE;
|
||||
void on_liveness_change (const svalue_set &live_svalues,
|
||||
const region_model *model) FINAL OVERRIDE;
|
||||
|
@ -74,6 +74,8 @@ class impl_region_model_context : public region_model_context
|
|||
|
||||
uncertainty_t *get_uncertainty () FINAL OVERRIDE;
|
||||
|
||||
void purge_state_involving (const svalue *sval) FINAL OVERRIDE;
|
||||
|
||||
exploded_graph *m_eg;
|
||||
log_user m_logger;
|
||||
exploded_node *m_enode_for_diag;
|
||||
|
@ -223,6 +225,17 @@ class exploded_node : public dnode<eg_traits>
|
|||
const gimple *stmt,
|
||||
program_state *state,
|
||||
uncertainty_t *uncertainty);
|
||||
void on_stmt_pre (exploded_graph &eg,
|
||||
const gimple *stmt,
|
||||
program_state *state,
|
||||
bool *out_terminate_path,
|
||||
bool *out_unknown_side_effects,
|
||||
region_model_context *ctxt);
|
||||
void on_stmt_post (const gimple *stmt,
|
||||
program_state *state,
|
||||
bool unknown_side_effects,
|
||||
region_model_context *ctxt);
|
||||
|
||||
bool on_edge (exploded_graph &eg,
|
||||
const superedge *succ,
|
||||
program_point *next_point,
|
||||
|
|
|
@ -154,6 +154,9 @@ class pending_diagnostic
|
|||
/* Hand-coded RTTI: get an ID for the subclass. */
|
||||
virtual const char *get_kind () const = 0;
|
||||
|
||||
/* A vfunc for identifying "use of uninitialized value". */
|
||||
virtual bool use_of_uninit_p () const { return false; }
|
||||
|
||||
/* Compare for equality with OTHER, which might be of a different
|
||||
subclass. */
|
||||
|
||||
|
@ -269,6 +272,16 @@ class pending_diagnostic
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Vfunc for determining that this pending_diagnostic supercedes OTHER,
|
||||
and that OTHER should therefore not be emitted.
|
||||
They have already been tested for being at the same stmt. */
|
||||
|
||||
virtual bool
|
||||
supercedes_p (const pending_diagnostic &other ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/* A template to make it easier to make subclasses of pending_diagnostic.
|
||||
|
|
|
@ -372,21 +372,31 @@ sm_state_map::get_state (const svalue *sval,
|
|||
INIT_VAL(foo). */
|
||||
if (m_sm.inherited_state_p ())
|
||||
if (region_model_manager *mgr = ext_state.get_model_manager ())
|
||||
if (const initial_svalue *init_sval = sval->dyn_cast_initial_svalue ())
|
||||
{
|
||||
const region *reg = init_sval->get_region ();
|
||||
/* Try recursing upwards (up to the base region for the cluster). */
|
||||
if (!reg->base_region_p ())
|
||||
if (const region *parent_reg = reg->get_parent_region ())
|
||||
{
|
||||
const svalue *parent_init_sval
|
||||
= mgr->get_or_create_initial_value (parent_reg);
|
||||
state_machine::state_t parent_state
|
||||
= get_state (parent_init_sval, ext_state);
|
||||
if (parent_state)
|
||||
return parent_state;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (const initial_svalue *init_sval = sval->dyn_cast_initial_svalue ())
|
||||
{
|
||||
const region *reg = init_sval->get_region ();
|
||||
/* Try recursing upwards (up to the base region for the
|
||||
cluster). */
|
||||
if (!reg->base_region_p ())
|
||||
if (const region *parent_reg = reg->get_parent_region ())
|
||||
{
|
||||
const svalue *parent_init_sval
|
||||
= mgr->get_or_create_initial_value (parent_reg);
|
||||
state_machine::state_t parent_state
|
||||
= get_state (parent_init_sval, ext_state);
|
||||
if (parent_state)
|
||||
return parent_state;
|
||||
}
|
||||
}
|
||||
else if (const sub_svalue *sub_sval = sval->dyn_cast_sub_svalue ())
|
||||
{
|
||||
const svalue *parent_sval = sub_sval->get_parent ();
|
||||
if (state_machine::state_t parent_state
|
||||
= get_state (parent_sval, ext_state))
|
||||
return parent_state;
|
||||
}
|
||||
}
|
||||
|
||||
return m_sm.get_default_state (sval);
|
||||
}
|
||||
|
@ -596,7 +606,8 @@ sm_state_map::purge_state_involving (const svalue *sval,
|
|||
const extrinsic_state &ext_state)
|
||||
{
|
||||
/* Currently svalue::involves_p requires this. */
|
||||
if (sval->get_kind () != SK_INITIAL)
|
||||
if (!(sval->get_kind () == SK_INITIAL
|
||||
|| sval->get_kind () == SK_CONJURED))
|
||||
return;
|
||||
|
||||
svalue_set svals_to_unset;
|
||||
|
|
|
@ -84,7 +84,10 @@ call_details::call_details (const gcall *call, region_model *model,
|
|||
uncertainty_t *
|
||||
call_details::get_uncertainty () const
|
||||
{
|
||||
return m_ctxt->get_uncertainty ();
|
||||
if (m_ctxt)
|
||||
return m_ctxt->get_uncertainty ();
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If the callsite has a left-hand-side region, set it to RESULT
|
||||
|
@ -173,6 +176,15 @@ call_details::dump (bool simple) const
|
|||
pp_flush (&pp);
|
||||
}
|
||||
|
||||
/* Get a conjured_svalue for this call for REG. */
|
||||
|
||||
const svalue *
|
||||
call_details::get_or_create_conjured_svalue (const region *reg) const
|
||||
{
|
||||
region_model_manager *mgr = m_model->get_manager ();
|
||||
return mgr->get_or_create_conjured_svalue (reg->get_type (), m_call, reg);
|
||||
}
|
||||
|
||||
/* Implementations of specific functions. */
|
||||
|
||||
/* Handle the on_call_pre part of "alloca". */
|
||||
|
@ -305,6 +317,42 @@ region_model::impl_call_error (const call_details &cd, unsigned min_args,
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "fgets" and "fgets_unlocked". */
|
||||
|
||||
void
|
||||
region_model::impl_call_fgets (const call_details &cd)
|
||||
{
|
||||
/* Ideally we would bifurcate state here between the
|
||||
error vs no error cases. */
|
||||
const svalue *ptr_sval = cd.get_arg_svalue (0);
|
||||
if (const region_svalue *ptr_to_region_sval
|
||||
= ptr_sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
const region *reg = ptr_to_region_sval->get_pointee ();
|
||||
const region *base_reg = reg->get_base_region ();
|
||||
const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
|
||||
purge_state_involving (new_sval, cd.get_ctxt ());
|
||||
set_value (base_reg, new_sval, cd.get_ctxt ());
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "fread". */
|
||||
|
||||
void
|
||||
region_model::impl_call_fread (const call_details &cd)
|
||||
{
|
||||
const svalue *ptr_sval = cd.get_arg_svalue (0);
|
||||
if (const region_svalue *ptr_to_region_sval
|
||||
= ptr_sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
const region *reg = ptr_to_region_sval->get_pointee ();
|
||||
const region *base_reg = reg->get_base_region ();
|
||||
const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
|
||||
purge_state_involving (new_sval, cd.get_ctxt ());
|
||||
set_value (base_reg, new_sval, cd.get_ctxt ());
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the on_call_post part of "free", after sm-handling.
|
||||
|
||||
If the ptr points to an underlying heap region, delete the region,
|
||||
|
|
|
@ -252,6 +252,10 @@ region_model_manager::get_or_create_unknown_svalue (tree type)
|
|||
const svalue *
|
||||
region_model_manager::get_or_create_initial_value (const region *reg)
|
||||
{
|
||||
if (!reg->can_have_initial_svalue_p ())
|
||||
return get_or_create_poisoned_svalue (POISON_KIND_UNINIT,
|
||||
reg->get_type ());
|
||||
|
||||
/* The initial value of a cast is a cast of the initial value. */
|
||||
if (const cast_region *cast_reg = reg->dyn_cast_cast_region ())
|
||||
{
|
||||
|
|
|
@ -267,7 +267,6 @@ reachable_regions::handle_parm (const svalue *sval, tree param_type)
|
|||
void
|
||||
reachable_regions::mark_escaped_clusters (region_model_context *ctxt)
|
||||
{
|
||||
gcc_assert (ctxt);
|
||||
auto_vec<const function_region *> escaped_fn_regs
|
||||
(m_mutable_base_regs.elements ());
|
||||
for (hash_set<const region *>::iterator iter = m_mutable_base_regs.begin ();
|
||||
|
@ -281,12 +280,15 @@ reachable_regions::mark_escaped_clusters (region_model_context *ctxt)
|
|||
if (const function_region *fn_reg = base_reg->dyn_cast_function_region ())
|
||||
escaped_fn_regs.quick_push (fn_reg);
|
||||
}
|
||||
/* Sort to ensure deterministic results. */
|
||||
escaped_fn_regs.qsort (region::cmp_ptr_ptr);
|
||||
unsigned i;
|
||||
const function_region *fn_reg;
|
||||
FOR_EACH_VEC_ELT (escaped_fn_regs, i, fn_reg)
|
||||
ctxt->on_escaped_function (fn_reg->get_fndecl ());
|
||||
if (ctxt)
|
||||
{
|
||||
/* Sort to ensure deterministic results. */
|
||||
escaped_fn_regs.qsort (region::cmp_ptr_ptr);
|
||||
unsigned i;
|
||||
const function_region *fn_reg;
|
||||
FOR_EACH_VEC_ELT (escaped_fn_regs, i, fn_reg)
|
||||
ctxt->on_escaped_function (fn_reg->get_fndecl ());
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump SET to PP, sorting it to avoid churn when comparing dumps. */
|
||||
|
|
|
@ -221,6 +221,23 @@ region_to_value_map::can_merge_with_p (const region_to_value_map &other,
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Purge any state involving SVAL. */
|
||||
|
||||
void
|
||||
region_to_value_map::purge_state_involving (const svalue *sval)
|
||||
{
|
||||
auto_vec<const region *> to_purge;
|
||||
for (auto iter : *this)
|
||||
{
|
||||
const region *iter_reg = iter.first;
|
||||
const svalue *iter_sval = iter.second;
|
||||
if (iter_reg->involves_p (sval) || iter_sval->involves_p (sval))
|
||||
to_purge.safe_push (iter_reg);
|
||||
}
|
||||
for (auto iter : to_purge)
|
||||
m_hash_map.remove (iter);
|
||||
}
|
||||
|
||||
/* class region_model. */
|
||||
|
||||
/* Ctor for region_model: construct an "empty" model. */
|
||||
|
@ -442,6 +459,11 @@ public:
|
|||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "poisoned_value_diagnostic"; }
|
||||
|
||||
bool use_of_uninit_p () const FINAL OVERRIDE
|
||||
{
|
||||
return m_pkind == POISON_KIND_UNINIT;
|
||||
}
|
||||
|
||||
bool operator== (const poisoned_value_diagnostic &other) const
|
||||
{
|
||||
return m_expr == other.m_expr;
|
||||
|
@ -453,6 +475,16 @@ public:
|
|||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case POISON_KIND_UNINIT:
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable". */
|
||||
return warning_meta (rich_loc, m,
|
||||
OPT_Wanalyzer_use_of_uninitialized_value,
|
||||
"use of uninitialized value %qE",
|
||||
m_expr);
|
||||
}
|
||||
break;
|
||||
case POISON_KIND_FREED:
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
|
@ -482,6 +514,9 @@ public:
|
|||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case POISON_KIND_UNINIT:
|
||||
return ev.formatted_print ("use of uninitialized value %qE here",
|
||||
m_expr);
|
||||
case POISON_KIND_FREED:
|
||||
return ev.formatted_print ("use after %<free%> of %qE here",
|
||||
m_expr);
|
||||
|
@ -782,6 +817,41 @@ region_model::get_gassign_result (const gassign *assign,
|
|||
}
|
||||
}
|
||||
|
||||
/* Check for SVAL being poisoned, adding a warning to CTXT.
|
||||
Return SVAL, or, if a warning is added, another value, to avoid
|
||||
repeatedly complaining about the same poisoned value in followup code. */
|
||||
|
||||
const svalue *
|
||||
region_model::check_for_poison (const svalue *sval,
|
||||
tree expr,
|
||||
region_model_context *ctxt) const
|
||||
{
|
||||
if (!ctxt)
|
||||
return sval;
|
||||
|
||||
if (const poisoned_svalue *poisoned_sval = sval->dyn_cast_poisoned_svalue ())
|
||||
{
|
||||
/* If we have an SSA name for a temporary, we don't want to print
|
||||
'<unknown>'.
|
||||
Poisoned values are shared by type, and so we can't reconstruct
|
||||
the tree other than via the def stmts, using
|
||||
fixup_tree_for_diagnostic. */
|
||||
tree diag_arg = fixup_tree_for_diagnostic (expr);
|
||||
enum poison_kind pkind = poisoned_sval->get_poison_kind ();
|
||||
if (ctxt->warn (new poisoned_value_diagnostic (diag_arg, pkind)))
|
||||
{
|
||||
/* We only want to report use of a poisoned value at the first
|
||||
place it gets used; return an unknown value to avoid generating
|
||||
a chain of followup warnings. */
|
||||
sval = m_mgr->get_or_create_unknown_svalue (sval->get_type ());
|
||||
}
|
||||
|
||||
return sval;
|
||||
}
|
||||
|
||||
return sval;
|
||||
}
|
||||
|
||||
/* Update this model for the ASSIGN stmt, using CTXT to report any
|
||||
diagnostics. */
|
||||
|
||||
|
@ -798,6 +868,8 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
|
|||
for some SVALUE. */
|
||||
if (const svalue *sval = get_gassign_result (assign, ctxt))
|
||||
{
|
||||
tree expr = get_diagnostic_tree_for_gassign (assign);
|
||||
check_for_poison (sval, expr, ctxt);
|
||||
set_value (lhs_reg, sval, ctxt);
|
||||
return;
|
||||
}
|
||||
|
@ -863,6 +935,109 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
|
|||
}
|
||||
}
|
||||
|
||||
/* A pending_diagnostic subclass for implementing "__analyzer_dump_path". */
|
||||
|
||||
class dump_path_diagnostic
|
||||
: public pending_diagnostic_subclass<dump_path_diagnostic>
|
||||
{
|
||||
public:
|
||||
bool emit (rich_location *richloc) FINAL OVERRIDE
|
||||
{
|
||||
inform (richloc, "path");
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "dump_path_diagnostic"; }
|
||||
|
||||
bool operator== (const dump_path_diagnostic &) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/* Handle the pre-sm-state part of STMT, modifying this object in-place.
|
||||
Write true to *OUT_TERMINATE_PATH if the path should be terminated.
|
||||
Write true to *OUT_UNKNOWN_SIDE_EFFECTS if the stmt has unknown
|
||||
side effects. */
|
||||
|
||||
void
|
||||
region_model::on_stmt_pre (const gimple *stmt,
|
||||
bool *out_terminate_path,
|
||||
bool *out_unknown_side_effects,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
switch (gimple_code (stmt))
|
||||
{
|
||||
default:
|
||||
/* No-op for now. */
|
||||
break;
|
||||
|
||||
case GIMPLE_ASSIGN:
|
||||
{
|
||||
const gassign *assign = as_a <const gassign *> (stmt);
|
||||
on_assignment (assign, ctxt);
|
||||
}
|
||||
break;
|
||||
|
||||
case GIMPLE_ASM:
|
||||
/* No-op for now. */
|
||||
break;
|
||||
|
||||
case GIMPLE_CALL:
|
||||
{
|
||||
/* Track whether we have a gcall to a function that's not recognized by
|
||||
anything, for which we don't have a function body, or for which we
|
||||
don't know the fndecl. */
|
||||
const gcall *call = as_a <const gcall *> (stmt);
|
||||
|
||||
/* Debugging/test support. */
|
||||
if (is_special_named_call_p (call, "__analyzer_describe", 2))
|
||||
impl_call_analyzer_describe (call, ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_capacity", 1))
|
||||
impl_call_analyzer_dump_capacity (call, ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump_path" by queuing a
|
||||
diagnostic at this exploded_node. */
|
||||
ctxt->warn (new dump_path_diagnostic ());
|
||||
}
|
||||
else if (is_special_named_call_p (call, "__analyzer_dump_region_model",
|
||||
0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_dump_region_model" by dumping
|
||||
the region model's state to stderr. */
|
||||
dump (false);
|
||||
}
|
||||
else if (is_special_named_call_p (call, "__analyzer_eval", 1))
|
||||
impl_call_analyzer_eval (call, ctxt);
|
||||
else if (is_special_named_call_p (call, "__analyzer_break", 0))
|
||||
{
|
||||
/* Handle the builtin "__analyzer_break" by triggering a
|
||||
breakpoint. */
|
||||
/* TODO: is there a good cross-platform way to do this? */
|
||||
raise (SIGINT);
|
||||
}
|
||||
else if (is_special_named_call_p (call,
|
||||
"__analyzer_dump_exploded_nodes",
|
||||
1))
|
||||
{
|
||||
/* This is handled elsewhere. */
|
||||
}
|
||||
else
|
||||
*out_unknown_side_effects = on_call_pre (call, ctxt,
|
||||
out_terminate_path);
|
||||
}
|
||||
break;
|
||||
|
||||
case GIMPLE_RETURN:
|
||||
{
|
||||
const greturn *return_ = as_a <const greturn *> (stmt);
|
||||
on_return (return_, ctxt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update this model for the CALL stmt, using CTXT to report any
|
||||
diagnostics - the first half.
|
||||
|
||||
|
@ -885,6 +1060,22 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
|
|||
|
||||
bool unknown_side_effects = false;
|
||||
|
||||
/* Some of the cases below update the lhs of the call based on the
|
||||
return value, but not all. Provide a default value, which may
|
||||
get overwritten below. */
|
||||
if (tree lhs = gimple_call_lhs (call))
|
||||
{
|
||||
const region *lhs_region = get_lvalue (lhs, ctxt);
|
||||
if (TREE_CODE (lhs) == SSA_NAME)
|
||||
{
|
||||
const svalue *sval
|
||||
= m_mgr->get_or_create_conjured_svalue (TREE_TYPE (lhs), call,
|
||||
lhs_region);
|
||||
purge_state_involving (sval, ctxt);
|
||||
set_value (lhs_region, sval, ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
if (gimple_call_internal_p (call))
|
||||
{
|
||||
switch (gimple_call_internal_fn (call))
|
||||
|
@ -994,6 +1185,17 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
|
|||
else
|
||||
unknown_side_effects = true;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "fgets", call, 3)
|
||||
|| is_named_call_p (callee_fndecl, "fgets_unlocked", call, 3))
|
||||
{
|
||||
impl_call_fgets (cd);
|
||||
return false;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "fread", call, 4))
|
||||
{
|
||||
impl_call_fread (cd);
|
||||
return false;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "getchar", call, 0))
|
||||
{
|
||||
/* No side-effects (tracking stream state is out-of-scope
|
||||
|
@ -1029,19 +1231,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
|
|||
else
|
||||
unknown_side_effects = true;
|
||||
|
||||
/* Some of the above cases update the lhs of the call based on the
|
||||
return value. If we get here, it hasn't been done yet, so do that
|
||||
now. */
|
||||
if (tree lhs = gimple_call_lhs (call))
|
||||
{
|
||||
const region *lhs_region = get_lvalue (lhs, ctxt);
|
||||
if (TREE_CODE (lhs) == SSA_NAME)
|
||||
{
|
||||
const svalue *sval = m_mgr->get_or_create_initial_value (lhs_region);
|
||||
set_value (lhs_region, sval, ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
return unknown_side_effects;
|
||||
}
|
||||
|
||||
|
@ -1090,6 +1279,38 @@ region_model::on_call_post (const gcall *call,
|
|||
handle_unrecognized_call (call, ctxt);
|
||||
}
|
||||
|
||||
/* Purge state involving SVAL from this region_model, using CTXT
|
||||
(if non-NULL) to purge other state in a program_state.
|
||||
|
||||
For example, if we're at the def-stmt of an SSA name, then we need to
|
||||
purge any state for svalues that involve that SSA name. This avoids
|
||||
false positives in loops, since a symbolic value referring to the
|
||||
SSA name will be referring to the previous value of that SSA name.
|
||||
|
||||
For example, in:
|
||||
while ((e = hashmap_iter_next(&iter))) {
|
||||
struct oid2strbuf *e_strbuf = (struct oid2strbuf *)e;
|
||||
free (e_strbuf->value);
|
||||
}
|
||||
at the def-stmt of e_8:
|
||||
e_8 = hashmap_iter_next (&iter);
|
||||
we should purge the "freed" state of:
|
||||
INIT_VAL(CAST_REG(‘struct oid2strbuf’, (*INIT_VAL(e_8))).value)
|
||||
which is the "e_strbuf->value" value from the previous iteration,
|
||||
or we will erroneously report a double-free - the "e_8" within it
|
||||
refers to the previous value. */
|
||||
|
||||
void
|
||||
region_model::purge_state_involving (const svalue *sval,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
m_store.purge_state_involving (sval, m_mgr);
|
||||
m_constraints->purge_state_involving (sval);
|
||||
m_dynamic_extents.purge_state_involving (sval);
|
||||
if (ctxt)
|
||||
ctxt->purge_state_involving (sval);
|
||||
}
|
||||
|
||||
/* Handle a call CALL to a function with unknown behavior.
|
||||
|
||||
Traverse the regions in this model, determining what regions are
|
||||
|
@ -1135,7 +1356,7 @@ region_model::handle_unrecognized_call (const gcall *call,
|
|||
}
|
||||
}
|
||||
|
||||
uncertainty_t *uncertainty = ctxt->get_uncertainty ();
|
||||
uncertainty_t *uncertainty = ctxt ? ctxt->get_uncertainty () : NULL;
|
||||
|
||||
/* Purge sm-state for the svalues that were reachable,
|
||||
both in non-mutable and mutable form. */
|
||||
|
@ -1144,14 +1365,16 @@ region_model::handle_unrecognized_call (const gcall *call,
|
|||
iter != reachable_regs.end_reachable_svals (); ++iter)
|
||||
{
|
||||
const svalue *sval = (*iter);
|
||||
ctxt->on_unknown_change (sval, false);
|
||||
if (ctxt)
|
||||
ctxt->on_unknown_change (sval, false);
|
||||
}
|
||||
for (svalue_set::iterator iter
|
||||
= reachable_regs.begin_mutable_svals ();
|
||||
iter != reachable_regs.end_mutable_svals (); ++iter)
|
||||
{
|
||||
const svalue *sval = (*iter);
|
||||
ctxt->on_unknown_change (sval, true);
|
||||
if (ctxt)
|
||||
ctxt->on_unknown_change (sval, true);
|
||||
if (uncertainty)
|
||||
uncertainty->on_mutable_sval_at_unknown_call (sval);
|
||||
}
|
||||
|
@ -1603,6 +1826,8 @@ region_model::get_rvalue (path_var pv, region_model_context *ctxt) const
|
|||
|
||||
assert_compat_types (result_sval->get_type (), TREE_TYPE (pv.m_tree));
|
||||
|
||||
result_sval = check_for_poison (result_sval, pv.m_tree, ctxt);
|
||||
|
||||
return result_sval;
|
||||
}
|
||||
|
||||
|
@ -4307,7 +4532,7 @@ test_stack_frames ()
|
|||
|
||||
/* Verify that p (which was pointing at the local "x" in the popped
|
||||
frame) has been poisoned. */
|
||||
const svalue *new_p_sval = model.get_rvalue (p, &ctxt);
|
||||
const svalue *new_p_sval = model.get_rvalue (p, NULL);
|
||||
ASSERT_EQ (new_p_sval->get_kind (), SK_POISONED);
|
||||
ASSERT_EQ (new_p_sval->dyn_cast_poisoned_svalue ()->get_poison_kind (),
|
||||
POISON_KIND_POPPED_STACK);
|
||||
|
@ -5397,7 +5622,7 @@ test_alloca ()
|
|||
/* Verify that the pointers to the alloca region are replaced by
|
||||
poisoned values when the frame is popped. */
|
||||
model.pop_frame (NULL, NULL, &ctxt);
|
||||
ASSERT_EQ (model.get_rvalue (p, &ctxt)->get_kind (), SK_POISONED);
|
||||
ASSERT_EQ (model.get_rvalue (p, NULL)->get_kind (), SK_POISONED);
|
||||
}
|
||||
|
||||
/* Verify that svalue::involves_p works. */
|
||||
|
|
|
@ -171,6 +171,8 @@ public:
|
|||
bool can_merge_with_p (const region_to_value_map &other,
|
||||
region_to_value_map *out) const;
|
||||
|
||||
void purge_state_involving (const svalue *sval);
|
||||
|
||||
private:
|
||||
hash_map_t m_hash_map;
|
||||
};
|
||||
|
@ -470,6 +472,8 @@ public:
|
|||
void dump_to_pp (pretty_printer *pp, bool simple) const;
|
||||
void dump (bool simple) const;
|
||||
|
||||
const svalue *get_or_create_conjured_svalue (const region *) const;
|
||||
|
||||
private:
|
||||
const gcall *m_call;
|
||||
region_model *m_model;
|
||||
|
@ -518,6 +522,12 @@ class region_model
|
|||
void canonicalize ();
|
||||
bool canonicalized_p () const;
|
||||
|
||||
void
|
||||
on_stmt_pre (const gimple *stmt,
|
||||
bool *out_terminate_path,
|
||||
bool *out_unknown_side_effects,
|
||||
region_model_context *ctxt);
|
||||
|
||||
void on_assignment (const gassign *stmt, region_model_context *ctxt);
|
||||
const svalue *get_gassign_result (const gassign *assign,
|
||||
region_model_context *ctxt);
|
||||
|
@ -527,6 +537,8 @@ class region_model
|
|||
bool unknown_side_effects,
|
||||
region_model_context *ctxt);
|
||||
|
||||
void purge_state_involving (const svalue *sval, region_model_context *ctxt);
|
||||
|
||||
/* Specific handling for on_call_pre. */
|
||||
bool impl_call_alloca (const call_details &cd);
|
||||
void impl_call_analyzer_describe (const gcall *call,
|
||||
|
@ -539,6 +551,8 @@ class region_model
|
|||
bool impl_call_calloc (const call_details &cd);
|
||||
bool impl_call_error (const call_details &cd, unsigned min_args,
|
||||
bool *out_terminate_path);
|
||||
void impl_call_fgets (const call_details &cd);
|
||||
void impl_call_fread (const call_details &cd);
|
||||
void impl_call_free (const call_details &cd);
|
||||
bool impl_call_malloc (const call_details &cd);
|
||||
void impl_call_memcpy (const call_details &cd);
|
||||
|
@ -727,6 +741,10 @@ class region_model
|
|||
bool called_from_main_p () const;
|
||||
const svalue *get_initial_value_for_global (const region *reg) const;
|
||||
|
||||
const svalue *check_for_poison (const svalue *sval,
|
||||
tree expr,
|
||||
region_model_context *ctxt) const;
|
||||
|
||||
void check_for_writable_region (const region* dest_reg,
|
||||
region_model_context *ctxt) const;
|
||||
|
||||
|
@ -757,7 +775,9 @@ class region_model
|
|||
class region_model_context
|
||||
{
|
||||
public:
|
||||
virtual void warn (pending_diagnostic *d) = 0;
|
||||
/* Hook for clients to store pending diagnostics.
|
||||
Return true if the diagnostic was stored, or false if it was deleted. */
|
||||
virtual bool warn (pending_diagnostic *d) = 0;
|
||||
|
||||
/* Hook for clients to be notified when an SVAL that was reachable
|
||||
in a previous state is no longer live, so that clients can emit warnings
|
||||
|
@ -799,6 +819,9 @@ class region_model_context
|
|||
virtual void on_escaped_function (tree fndecl) = 0;
|
||||
|
||||
virtual uncertainty_t *get_uncertainty () = 0;
|
||||
|
||||
/* Hook for clients to purge state involving SVAL. */
|
||||
virtual void purge_state_involving (const svalue *sval) = 0;
|
||||
};
|
||||
|
||||
/* A "do nothing" subclass of region_model_context. */
|
||||
|
@ -806,7 +829,7 @@ class region_model_context
|
|||
class noop_region_model_context : public region_model_context
|
||||
{
|
||||
public:
|
||||
void warn (pending_diagnostic *) OVERRIDE {}
|
||||
bool warn (pending_diagnostic *) OVERRIDE { return false; }
|
||||
void on_svalue_leak (const svalue *) OVERRIDE {}
|
||||
void on_liveness_change (const svalue_set &,
|
||||
const region_model *) OVERRIDE {}
|
||||
|
@ -829,6 +852,8 @@ public:
|
|||
void on_escaped_function (tree) OVERRIDE {}
|
||||
|
||||
uncertainty_t *get_uncertainty () OVERRIDE { return NULL; }
|
||||
|
||||
void purge_state_involving (const svalue *sval ATTRIBUTE_UNUSED) OVERRIDE {}
|
||||
};
|
||||
|
||||
/* A subclass of region_model_context for determining if operations fail
|
||||
|
@ -931,9 +956,10 @@ using namespace ::selftest;
|
|||
class test_region_model_context : public noop_region_model_context
|
||||
{
|
||||
public:
|
||||
void warn (pending_diagnostic *d) FINAL OVERRIDE
|
||||
bool warn (pending_diagnostic *d) FINAL OVERRIDE
|
||||
{
|
||||
m_diagnostics.safe_push (d);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
|
||||
|
|
|
@ -168,6 +168,109 @@ region::maybe_get_frame_region () const
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the memory space of this region. */
|
||||
|
||||
enum memory_space
|
||||
region::get_memory_space () const
|
||||
{
|
||||
const region *iter = this;
|
||||
while (iter)
|
||||
{
|
||||
switch (iter->get_kind ())
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case RK_GLOBALS:
|
||||
return MEMSPACE_GLOBALS;
|
||||
case RK_CODE:
|
||||
case RK_FUNCTION:
|
||||
case RK_LABEL:
|
||||
return MEMSPACE_CODE;
|
||||
case RK_FRAME:
|
||||
case RK_STACK:
|
||||
case RK_ALLOCA:
|
||||
return MEMSPACE_STACK;
|
||||
case RK_HEAP:
|
||||
case RK_HEAP_ALLOCATED:
|
||||
return MEMSPACE_HEAP;
|
||||
case RK_STRING:
|
||||
return MEMSPACE_READONLY_DATA;
|
||||
}
|
||||
if (iter->get_kind () == RK_CAST)
|
||||
iter = iter->dyn_cast_cast_region ()->get_original_region ();
|
||||
else
|
||||
iter = iter->get_parent_region ();
|
||||
}
|
||||
return MEMSPACE_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Subroutine for use by region_model_manager::get_or_create_initial_value.
|
||||
Return true if this region has an initial_svalue.
|
||||
Return false if attempting to use INIT_VAL(this_region) should give
|
||||
the "UNINITIALIZED" poison value. */
|
||||
|
||||
bool
|
||||
region::can_have_initial_svalue_p () const
|
||||
{
|
||||
const region *base_reg = get_base_region ();
|
||||
|
||||
/* Check for memory spaces that are uninitialized by default. */
|
||||
enum memory_space mem_space = base_reg->get_memory_space ();
|
||||
switch (mem_space)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case MEMSPACE_UNKNOWN:
|
||||
case MEMSPACE_CODE:
|
||||
case MEMSPACE_GLOBALS:
|
||||
case MEMSPACE_READONLY_DATA:
|
||||
/* Such regions have initial_svalues. */
|
||||
return true;
|
||||
|
||||
case MEMSPACE_HEAP:
|
||||
/* Heap allocations are uninitialized by default. */
|
||||
return false;
|
||||
|
||||
case MEMSPACE_STACK:
|
||||
if (tree decl = base_reg->maybe_get_decl ())
|
||||
{
|
||||
/* See the assertion in frame_region::get_region_for_local for the
|
||||
tree codes we need to handle here. */
|
||||
switch (TREE_CODE (decl))
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
|
||||
case PARM_DECL:
|
||||
/* Parameters have initial values. */
|
||||
return true;
|
||||
|
||||
case VAR_DECL:
|
||||
case RESULT_DECL:
|
||||
/* Function locals don't have initial values. */
|
||||
return false;
|
||||
|
||||
case SSA_NAME:
|
||||
{
|
||||
tree ssa_name = decl;
|
||||
/* SSA names that are the default defn of a PARM_DECL
|
||||
have initial_svalues; other SSA names don't. */
|
||||
if (SSA_NAME_IS_DEFAULT_DEF (ssa_name)
|
||||
&& SSA_NAME_VAR (ssa_name)
|
||||
&& TREE_CODE (SSA_NAME_VAR (ssa_name)) == PARM_DECL)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have an on-stack region that isn't associated with a decl
|
||||
or SSA name, then we have VLA/alloca, which is uninitialized. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this region is a decl_region, return the decl.
|
||||
Otherwise return NULL. */
|
||||
|
||||
|
@ -584,6 +687,20 @@ region::non_null_p () const
|
|||
}
|
||||
}
|
||||
|
||||
/* Return true iff this region is defined in terms of SVAL. */
|
||||
|
||||
bool
|
||||
region::involves_p (const svalue *sval) const
|
||||
{
|
||||
if (const symbolic_region *symbolic_reg = dyn_cast_symbolic_region ())
|
||||
{
|
||||
if (symbolic_reg->get_pointer ()->involves_p (sval))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Comparator for trees to impose a deterministic ordering on
|
||||
T1 and T2. */
|
||||
|
||||
|
|
|
@ -25,6 +25,18 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
namespace ana {
|
||||
|
||||
/* An enum for identifying different spaces within memory. */
|
||||
|
||||
enum memory_space
|
||||
{
|
||||
MEMSPACE_UNKNOWN,
|
||||
MEMSPACE_CODE,
|
||||
MEMSPACE_GLOBALS,
|
||||
MEMSPACE_STACK,
|
||||
MEMSPACE_HEAP,
|
||||
MEMSPACE_READONLY_DATA
|
||||
};
|
||||
|
||||
/* An enum for discriminating between the different concrete subclasses
|
||||
of region. */
|
||||
|
||||
|
@ -123,6 +135,8 @@ public:
|
|||
bool base_region_p () const;
|
||||
bool descendent_of_p (const region *elder) const;
|
||||
const frame_region *maybe_get_frame_region () const;
|
||||
enum memory_space get_memory_space () const;
|
||||
bool can_have_initial_svalue_p () const;
|
||||
|
||||
tree maybe_get_decl () const;
|
||||
|
||||
|
@ -141,6 +155,8 @@ public:
|
|||
|
||||
static int cmp_ptr_ptr (const void *, const void *);
|
||||
|
||||
bool involves_p (const svalue *sval) const;
|
||||
|
||||
region_offset get_offset () const;
|
||||
|
||||
/* Attempt to get the size of this region as a concrete number of bytes.
|
||||
|
|
|
@ -1198,6 +1198,25 @@ public:
|
|||
funcname, ev.m_expr);
|
||||
}
|
||||
|
||||
/* Implementation of pending_diagnostic::supercedes_p for
|
||||
use_after_free.
|
||||
|
||||
We want use-after-free to supercede use-of-unitialized-value,
|
||||
so that if we have these at the same stmt, we don't emit
|
||||
a use-of-uninitialized, just the use-after-free.
|
||||
(this is because we fully purge information about freed
|
||||
buffers when we free them to avoid state explosions, so
|
||||
that if they are accessed after the free, it looks like
|
||||
they are uninitialized). */
|
||||
|
||||
bool supercedes_p (const pending_diagnostic &other) const FINAL OVERRIDE
|
||||
{
|
||||
if (other.use_of_uninit_p ())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_free_event;
|
||||
const deallocator *m_deallocator;
|
||||
|
|
|
@ -1316,6 +1316,38 @@ binding_cluster::mark_region_as_unknown (store_manager *mgr,
|
|||
bind (mgr, reg, sval);
|
||||
}
|
||||
|
||||
/* Purge state involving SVAL. */
|
||||
|
||||
void
|
||||
binding_cluster::purge_state_involving (const svalue *sval,
|
||||
region_model_manager *sval_mgr)
|
||||
{
|
||||
auto_vec<const binding_key *> to_remove;
|
||||
for (auto iter : m_map)
|
||||
{
|
||||
const binding_key *iter_key = iter.first;
|
||||
if (const symbolic_binding *symbolic_key
|
||||
= iter_key->dyn_cast_symbolic_binding ())
|
||||
{
|
||||
const region *reg = symbolic_key->get_region ();
|
||||
if (reg->involves_p (sval))
|
||||
to_remove.safe_push (iter_key);
|
||||
}
|
||||
const svalue *iter_sval = iter.second;
|
||||
if (iter_sval->involves_p (sval))
|
||||
{
|
||||
const svalue *new_sval
|
||||
= sval_mgr->get_or_create_unknown_svalue (iter_sval->get_type ());
|
||||
m_map.put (iter_key, new_sval);
|
||||
}
|
||||
}
|
||||
for (auto iter : to_remove)
|
||||
{
|
||||
m_map.remove (iter);
|
||||
m_touched = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get any SVAL bound to REG within this cluster via kind KIND,
|
||||
without checking parent regions of REG. */
|
||||
|
||||
|
@ -2447,6 +2479,29 @@ store::mark_region_as_unknown (store_manager *mgr, const region *reg,
|
|||
cluster->mark_region_as_unknown (mgr, reg, uncertainty);
|
||||
}
|
||||
|
||||
/* Purge state involving SVAL. */
|
||||
|
||||
void
|
||||
store::purge_state_involving (const svalue *sval,
|
||||
region_model_manager *sval_mgr)
|
||||
{
|
||||
auto_vec <const region *> base_regs_to_purge;
|
||||
for (auto iter : m_cluster_map)
|
||||
{
|
||||
const region *base_reg = iter.first;
|
||||
if (base_reg->involves_p (sval))
|
||||
base_regs_to_purge.safe_push (base_reg);
|
||||
else
|
||||
{
|
||||
binding_cluster *cluster = iter.second;
|
||||
cluster->purge_state_involving (sval, sval_mgr);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter : base_regs_to_purge)
|
||||
purge_cluster (iter);
|
||||
}
|
||||
|
||||
/* Get the cluster for BASE_REG, or NULL (const version). */
|
||||
|
||||
const binding_cluster *
|
||||
|
|
|
@ -198,6 +198,7 @@ private:
|
|||
|
||||
class byte_range;
|
||||
class concrete_binding;
|
||||
class symbolic_binding;
|
||||
|
||||
/* Abstract base class for describing ranges of bits within a binding_map
|
||||
that can have svalues bound to them. */
|
||||
|
@ -220,6 +221,8 @@ public:
|
|||
|
||||
virtual const concrete_binding *dyn_cast_concrete_binding () const
|
||||
{ return NULL; }
|
||||
virtual const symbolic_binding *dyn_cast_symbolic_binding () const
|
||||
{ return NULL; }
|
||||
};
|
||||
|
||||
/* A concrete range of bits. */
|
||||
|
@ -420,6 +423,9 @@ public:
|
|||
|
||||
void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
|
||||
|
||||
const symbolic_binding *dyn_cast_symbolic_binding () const FINAL OVERRIDE
|
||||
{ return this; }
|
||||
|
||||
const region *get_region () const { return m_region; }
|
||||
|
||||
static int cmp_ptr_ptr (const void *, const void *);
|
||||
|
@ -563,6 +569,8 @@ public:
|
|||
void zero_fill_region (store_manager *mgr, const region *reg);
|
||||
void mark_region_as_unknown (store_manager *mgr, const region *reg,
|
||||
uncertainty_t *uncertainty);
|
||||
void purge_state_involving (const svalue *sval,
|
||||
region_model_manager *sval_mgr);
|
||||
|
||||
const svalue *get_binding (store_manager *mgr, const region *reg) const;
|
||||
const svalue *get_binding_recursive (store_manager *mgr,
|
||||
|
@ -697,6 +705,8 @@ public:
|
|||
void zero_fill_region (store_manager *mgr, const region *reg);
|
||||
void mark_region_as_unknown (store_manager *mgr, const region *reg,
|
||||
uncertainty_t *uncertainty);
|
||||
void purge_state_involving (const svalue *sval,
|
||||
region_model_manager *sval_mgr);
|
||||
|
||||
const binding_cluster *get_cluster (const region *base_reg) const;
|
||||
binding_cluster *get_cluster (const region *base_reg);
|
||||
|
|
|
@ -158,6 +158,13 @@ svalue::can_merge_p (const svalue *other,
|
|||
|| (other->get_kind () == SK_UNMERGEABLE))
|
||||
return NULL;
|
||||
|
||||
/* Reject attempts to merge poisoned svalues with other svalues
|
||||
(either non-poisoned, or other kinds of poison), so that e.g.
|
||||
we identify paths in which a variable is conditionally uninitialized. */
|
||||
if (get_kind () == SK_POISONED
|
||||
|| other->get_kind () == SK_POISONED)
|
||||
return NULL;
|
||||
|
||||
/* Reject attempts to merge NULL pointers with not-NULL-pointers. */
|
||||
if (POINTER_TYPE_P (get_type ()))
|
||||
{
|
||||
|
@ -516,6 +523,12 @@ public:
|
|||
m_found = true;
|
||||
}
|
||||
|
||||
void visit_conjured_svalue (const conjured_svalue *candidate)
|
||||
{
|
||||
if (candidate == m_needle)
|
||||
m_found = true;
|
||||
}
|
||||
|
||||
bool found_p () const { return m_found; }
|
||||
|
||||
private:
|
||||
|
@ -528,8 +541,9 @@ private:
|
|||
bool
|
||||
svalue::involves_p (const svalue *other) const
|
||||
{
|
||||
/* Currently only implemented for initial_svalue. */
|
||||
gcc_assert (other->get_kind () == SK_INITIAL);
|
||||
/* Currently only implemented for these kinds. */
|
||||
gcc_assert (other->get_kind () == SK_INITIAL
|
||||
|| other->get_kind () == SK_CONJURED);
|
||||
|
||||
involvement_visitor v (other);
|
||||
accept (&v);
|
||||
|
@ -811,6 +825,8 @@ poison_kind_to_str (enum poison_kind kind)
|
|||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case POISON_KIND_UNINIT:
|
||||
return "uninit";
|
||||
case POISON_KIND_FREED:
|
||||
return "freed";
|
||||
case POISON_KIND_POPPED_STACK:
|
||||
|
@ -847,6 +863,18 @@ poisoned_svalue::accept (visitor *v) const
|
|||
v->visit_poisoned_svalue (this);
|
||||
}
|
||||
|
||||
/* Implementation of svalue::maybe_fold_bits_within vfunc
|
||||
for poisoned_svalue. */
|
||||
|
||||
const svalue *
|
||||
poisoned_svalue::maybe_fold_bits_within (tree type,
|
||||
const bit_range &,
|
||||
region_model_manager *mgr) const
|
||||
{
|
||||
/* Bits within a poisoned value are also poisoned. */
|
||||
return mgr->get_or_create_poisoned_svalue (m_kind, type);
|
||||
}
|
||||
|
||||
/* class setjmp_svalue's implementation is in engine.cc, so that it can use
|
||||
the declaration of exploded_node. */
|
||||
|
||||
|
|
|
@ -324,6 +324,9 @@ public:
|
|||
|
||||
enum poison_kind
|
||||
{
|
||||
/* For use to describe uninitialized memory. */
|
||||
POISON_KIND_UNINIT,
|
||||
|
||||
/* For use to describe freed memory. */
|
||||
POISON_KIND_FREED,
|
||||
|
||||
|
@ -378,6 +381,11 @@ public:
|
|||
void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
|
||||
void accept (visitor *v) const FINAL OVERRIDE;
|
||||
|
||||
const svalue *
|
||||
maybe_fold_bits_within (tree type,
|
||||
const bit_range &subrange,
|
||||
region_model_manager *mgr) const FINAL OVERRIDE;
|
||||
|
||||
enum poison_kind get_poison_kind () const { return m_kind; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -9234,6 +9234,7 @@ Enabling this option effectively enables the following warnings:
|
|||
-Wanalyzer-tainted-array-index @gol
|
||||
-Wanalyzer-unsafe-call-within-signal-handler @gol
|
||||
-Wanalyzer-use-after-free @gol
|
||||
-Wanalyzer-use-of-uninitialized-value @gol
|
||||
-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
|
||||
-Wanalyzer-write-to-const @gol
|
||||
-Wanalyzer-write-to-string-literal @gol
|
||||
|
@ -9478,6 +9479,15 @@ detects an attempt to write through a pointer to a string literal.
|
|||
However, the analyzer does not prioritize detection of such paths, so
|
||||
false negatives are more likely relative to other warnings.
|
||||
|
||||
@item -Wno-analyzer-use-of-uninitialized-value
|
||||
@opindex Wanalyzer-use-of-uninitialized-value
|
||||
@opindex Wno-analyzer-use-of-uninitialized-value
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-use-of-uninitialized-value} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which an uninitialized
|
||||
value is used.
|
||||
|
||||
@end table
|
||||
|
||||
Pertinent parameters for controlling the exploration are:
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
auto lol()
|
||||
{
|
||||
int aha = 3;
|
||||
return [&aha] {
|
||||
return aha; // { dg-warning "dereferencing pointer '.*' to within stale stack frame" }
|
||||
return [&aha] { // { dg-warning "dereferencing pointer '.*' to within stale stack frame" }
|
||||
return aha;
|
||||
};
|
||||
/* TODO: may be worth special-casing the reporting of dangling
|
||||
references from lambdas, to highlight the declaration, and maybe fix
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// { dg-do compile { target c++11 } }
|
||||
// { dg-additional-options "-O1" }
|
||||
// { dg-additional-options "-O1 -Wno-analyzer-use-of-uninitialized-value" }
|
||||
|
||||
template <typename DV> DV
|
||||
vu (DV j4)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// { dg-additional-options "-Wno-analyzer-use-of-uninitialized-value" }
|
||||
|
||||
template <typename> class allocator {
|
||||
public:
|
||||
allocator(const allocator &);
|
||||
|
|
|
@ -25,9 +25,8 @@ void test_1 (void)
|
|||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
||||
|
||||
void test_2 (void)
|
||||
void test_2 (struct foo f)
|
||||
{
|
||||
struct foo f;
|
||||
f.i = 42;
|
||||
if (f.j)
|
||||
__analyzer_eval (f.j); /* { dg-warning "TRUE" } */
|
||||
|
|
|
@ -137,7 +137,7 @@ void test_11 (void)
|
|||
|
||||
/* alloca. */
|
||||
|
||||
void test_12 (void)
|
||||
int test_12 (void)
|
||||
{
|
||||
void *p = __builtin_alloca (256);
|
||||
void *q = __builtin_alloca (256);
|
||||
|
@ -145,14 +145,14 @@ void test_12 (void)
|
|||
/* alloca results should be unique. */
|
||||
__analyzer_eval (p == q); /* { dg-warning "FALSE" } */
|
||||
|
||||
// FIXME: complain about uses of poisoned values
|
||||
return *(int *)p; /* { dg-warning "use of uninitialized value '\\*\\(int \\*\\)p" } */
|
||||
}
|
||||
|
||||
/* Use of uninit value. */
|
||||
int test_12a (void)
|
||||
{
|
||||
int i;
|
||||
return i; // FIXME: do we see the return stmt?
|
||||
return i; /* { dg-warning "use of uninitialized value 'i'" } */
|
||||
}
|
||||
|
||||
void test_12b (void *p, void *q)
|
||||
|
@ -165,9 +165,11 @@ int test_12c (void)
|
|||
int i;
|
||||
int j;
|
||||
|
||||
j = i; // FIXME: should complain about this
|
||||
j = i; /* { dg-warning "use of uninitialized value 'i'" } */
|
||||
|
||||
return j;
|
||||
/* We should not emit followup warnings after the first warning about
|
||||
an uninitialized value. */
|
||||
return j; /* { dg-bogus "use of uninitialized value" } */
|
||||
}
|
||||
|
||||
struct coord
|
||||
|
@ -348,7 +350,9 @@ void test_19 (void)
|
|||
{
|
||||
int i, j;
|
||||
/* Compare two uninitialized locals. */
|
||||
__analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == j); /* { dg-warning "UNKNOWN" "unknown " } */
|
||||
/* { dg-warning "use of uninitialized value 'i'" "uninit i" { target *-*-* } .-1 } */
|
||||
/* { dg-warning "use of uninitialized value 'j'" "uninit j" { target *-*-* } .-2 } */
|
||||
}
|
||||
|
||||
void test_20 (int i, int j)
|
||||
|
@ -649,8 +653,10 @@ void test_29b (void)
|
|||
__analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'p\\\[10\\\].x'" "uninit" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'p\\\[10\\\].y'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
q = &p[7];
|
||||
|
||||
|
@ -698,8 +704,10 @@ void test_29c (int len)
|
|||
__analyzer_eval (p[9].x == 109024); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p[9].y == 109025); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[10].x == 0); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value '\\*p\\\[10\\\].x'" "uninit" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (p[10].y == 0); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value '\\*p\\\[10\\\].y'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
q = &p[7];
|
||||
|
||||
|
@ -811,7 +819,7 @@ void test_36 (int i)
|
|||
int test_37 (void)
|
||||
{
|
||||
int *ptr;
|
||||
return *ptr; /* { dg-warning "use of uninitialized value 'ptr'" "uninit-warning-removed" { xfail *-*-* } } */
|
||||
return *ptr; /* { dg-warning "use of uninitialized value 'ptr'" } */
|
||||
}
|
||||
|
||||
/* Write through uninitialized pointer. */
|
||||
|
@ -819,7 +827,7 @@ int test_37 (void)
|
|||
void test_37a (int i)
|
||||
{
|
||||
int *ptr;
|
||||
*ptr = i; /* { dg-warning "use of uninitialized value 'ptr'" "uninit-warning-removed" { xfail *-*-* } } */
|
||||
*ptr = i; /* { dg-warning "use of uninitialized value 'ptr'" } */
|
||||
}
|
||||
|
||||
// TODO: the various other ptr deref poisonings
|
||||
|
|
|
@ -17,7 +17,7 @@ test (int n) {
|
|||
for (; i >= 0; i++) {
|
||||
free(arr[i]); /* { dg-bogus "double-'free'" } */
|
||||
}
|
||||
free(arr);
|
||||
free(arr); /* { dg-warning "leak" } */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
independently, so the total combined number of states
|
||||
at any program point within the loop is NUM_VARS * NUM_STATES.
|
||||
|
||||
Set the limits high enough that we can fully explore this. */
|
||||
However, due to the way the analyzer represents heap-allocated regions
|
||||
this never terminates, eventually hitting the complexity limit
|
||||
(PR analyzer/93695). */
|
||||
|
||||
/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=200 --param analyzer-bb-explosion-factor=50" } */
|
||||
/* { dg-additional-options "-Wno-analyzer-too-complex -Wno-analyzer-malloc-leak" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
@ -12,35 +14,35 @@ extern int get (void);
|
|||
|
||||
void test (void)
|
||||
{
|
||||
void *p0, *p1, *p2, *p3;
|
||||
void *p0 = NULL, *p1 = NULL, *p2 = NULL, *p3 = NULL;
|
||||
while (get ())
|
||||
{
|
||||
switch (get ())
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
p0 = malloc (16); /* { dg-warning "leak" } */
|
||||
p0 = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
|
||||
break;
|
||||
case 1:
|
||||
free (p0); /* { dg-warning "double-'free' of 'p0'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 2:
|
||||
p1 = malloc (16); /* { dg-warning "leak" } */
|
||||
p1 = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
|
||||
break;
|
||||
case 3:
|
||||
free (p1); /* { dg-warning "double-'free' of 'p1'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 4:
|
||||
p2 = malloc (16); /* { dg-warning "leak" } */
|
||||
p2 = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
|
||||
break;
|
||||
case 5:
|
||||
free (p2); /* { dg-warning "double-'free' of 'p2'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 6:
|
||||
p3 = malloc (16); /* { dg-warning "leak" } */
|
||||
p3 = malloc (16); /* { dg-warning "leak" "" { xfail *-*-* } } */
|
||||
break;
|
||||
case 7:
|
||||
free (p3); /* { dg-warning "double-'free' of 'p3'" "" { xfail *-*-* } } */
|
||||
|
|
|
@ -8,13 +8,13 @@ extern int get (void);
|
|||
|
||||
void test (void)
|
||||
{
|
||||
void *p0, *p1, *p2, *p3;
|
||||
void *p0 = NULL, *p1 = NULL, *p2 = NULL, *p3 = NULL;
|
||||
/* Due to not purging constraints on SSA names within loops
|
||||
(PR analyzer/101068), the analyzer effectively treats the original
|
||||
explode-2.c as this code. */
|
||||
int a = get ();
|
||||
int b = get ();
|
||||
while (a)
|
||||
while (a) /* { dg-warning "leak" } */
|
||||
{
|
||||
switch (b)
|
||||
{
|
||||
|
|
31
gcc/testsuite/gcc.dg/analyzer/fgets-1.c
Normal file
31
gcc/testsuite/gcc.dg/analyzer/fgets-1.c
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* { dg-do "compile" } */
|
||||
|
||||
#define NULL ((void *) 0)
|
||||
typedef struct _IO_FILE FILE;
|
||||
|
||||
extern char *fgets(char *__restrict __s, int __n,
|
||||
FILE *__restrict __stream);
|
||||
extern char *fgets_unlocked(char *__restrict __s, int __n,
|
||||
FILE *__restrict __stream);
|
||||
|
||||
char
|
||||
test_1 (FILE *fp)
|
||||
{
|
||||
char buf[400];
|
||||
|
||||
if (fgets (buf, sizeof buf, fp) == NULL)
|
||||
return 0;
|
||||
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
char
|
||||
test_2 (FILE *fp)
|
||||
{
|
||||
char buf[400];
|
||||
|
||||
if (fgets_unlocked (buf, sizeof buf, fp) == NULL)
|
||||
return 0;
|
||||
|
||||
return buf[0];
|
||||
}
|
13
gcc/testsuite/gcc.dg/analyzer/fread-1.c
Normal file
13
gcc/testsuite/gcc.dg/analyzer/fread-1.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* { dg-additional-options "-fanalyzer-checker=taint" } */
|
||||
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
|
||||
extern size_t fread (void *, size_t, size_t, void *);
|
||||
|
||||
int
|
||||
test_1 (void *fp)
|
||||
{
|
||||
int i;
|
||||
fread (&i, sizeof (i), 1, fp);
|
||||
return i;
|
||||
}
|
|
@ -204,8 +204,7 @@ void test_16 (void)
|
|||
bar ();
|
||||
|
||||
fail:
|
||||
free (q); /* { dg-warning "free of uninitialized 'q'" "" { xfail *-*-* } } */
|
||||
/* TODO(xfail): implement uninitialized detection. */
|
||||
free (q); /* { dg-warning "use of uninitialized value 'q'" } */
|
||||
free (p);
|
||||
}
|
||||
|
||||
|
@ -459,8 +458,8 @@ int *
|
|||
test_40 (int i)
|
||||
{
|
||||
int *p = (int*)malloc(sizeof(int*));
|
||||
i = *p; /* { dg-warning "dereference of possibly-NULL 'p' \\\[CWE-690\\\]" } */
|
||||
/* TODO: (it's also uninitialized) */
|
||||
i = *p; /* { dg-warning "dereference of possibly-NULL 'p' \\\[CWE-690\\\]" "possibly-null" } */
|
||||
/* { dg-warning "use of uninitialized value '\\*p'" "uninit" { target *-*-*} .-1 } */
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ struct aac_srb_reply
|
|||
#define ST_OK 0
|
||||
#define SRB_STATUS_SUCCESS 0x01
|
||||
|
||||
extern void check_uninit (u8 v);
|
||||
|
||||
/* Adapted from drivers/scsi/aacraid/commctrl.c */
|
||||
|
||||
static int aac_send_raw_srb(/* [...snip...] */)
|
||||
|
@ -66,10 +68,8 @@ static int aac_send_raw_srb(/* [...snip...] */)
|
|||
__analyzer_eval (reply.sense_data_size == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (reply.sense_data[0] == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (reply.sense_data[AAC_SENSE_BUFFERSIZE - 1] == 0); /* { dg-warning "TRUE" } */
|
||||
/* TODO: the following should be detected as uninitialized, when
|
||||
that diagnostic is reimplemented. */
|
||||
__analyzer_eval (reply.padding[0] == 0); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (reply.padding[1] == 0); /* { dg-warning "UNKNOWN" } */
|
||||
check_uninit (reply.padding[0]); /* { dg-warning "uninitialized value" } */
|
||||
check_uninit (reply.padding[1]); /* { dg-warning "uninitialized value" } */
|
||||
}
|
||||
|
||||
static int aac_send_raw_srb_fixed(/* [...snip...] */)
|
||||
|
|
|
@ -30,6 +30,7 @@ typedef __SIZE_TYPE__ size_t;
|
|||
typedef struct _IO_FILE FILE;
|
||||
extern FILE *fopen (const char *__restrict __filename,
|
||||
const char *__restrict __modes);
|
||||
extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
extern int fclose (FILE *__stream);
|
||||
|
||||
extern int isspace (int) __attribute__((__nothrow__, __leaf__));
|
||||
|
@ -50,6 +51,12 @@ read_alias_file (const char *fname, int fname_len)
|
|||
if (fp == NULL)
|
||||
return 0;
|
||||
|
||||
if (fread (buf, sizeof buf, 1, fp) != 1)
|
||||
{
|
||||
fclose (fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cp = buf;
|
||||
|
||||
/* Ignore leading white space. */
|
||||
|
|
|
@ -13,7 +13,7 @@ void
|
|||
foo (void)
|
||||
{
|
||||
struct list l;
|
||||
tlist t = l;
|
||||
tlist t = l; /* { dg-warning "use of uninitialized value 'l'" } */
|
||||
for (;;)
|
||||
bar (&t);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ int pamark(void) {
|
|||
if (curbp->b_amark == (AMARK *)NULL)
|
||||
curbp->b_amark = p;
|
||||
else
|
||||
last->m_next = p;
|
||||
last->m_next = p; /* { dg-warning "dereference of NULL 'last'" } */
|
||||
}
|
||||
|
||||
p->m_name = (char)c; /* { dg-bogus "leak of 'p'" "bogus leak" } */
|
||||
|
|
|
@ -10,10 +10,8 @@ void
|
|||
th (int *);
|
||||
|
||||
void
|
||||
bv (__SIZE_TYPE__ ny)
|
||||
bv (__SIZE_TYPE__ ny, int ***mf)
|
||||
{
|
||||
int ***mf;
|
||||
|
||||
while (l8 ())
|
||||
{
|
||||
*mf = 0;
|
||||
|
|
|
@ -7,8 +7,7 @@ struct chanset_t {
|
|||
struct chanset_t *next;
|
||||
char dname[];
|
||||
};
|
||||
void help_subst() {
|
||||
char *writeidx;
|
||||
void help_subst(char *writeidx) {
|
||||
for (;; help_subst_chan = *help_subst_chan_0_0) {
|
||||
foo(help_subst_chan.next->dname);
|
||||
if (help_subst_chan_0_0) {
|
||||
|
|
|
@ -29,8 +29,8 @@ int test_3 (void)
|
|||
if ((p->file = fopen("test.txt", "w")) == NULL)
|
||||
return 1;
|
||||
unknown_fn ();
|
||||
return 0; /* { dg-warning "leak" } */
|
||||
}
|
||||
return 0;
|
||||
} /* { dg-warning "leak" } */
|
||||
|
||||
int test_4 (void)
|
||||
{
|
||||
|
@ -38,8 +38,8 @@ int test_4 (void)
|
|||
struct foo *p = &f;
|
||||
if ((p->file = fopen("test.txt", "w")) == NULL)
|
||||
return 1;
|
||||
return 0; /* { dg-warning "leak" } */
|
||||
}
|
||||
return 0;
|
||||
} /* { dg-warning "leak" } */
|
||||
|
||||
int test_5 (void)
|
||||
{
|
||||
|
|
|
@ -11,14 +11,16 @@ void test_1 (char a, char b, char c, char d, char e, char f,
|
|||
|
||||
__analyzer_eval (arr[2] == a); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[3] == b); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[4]); /* { dg-warning "UNKNOWN" } */ // TODO: report uninit
|
||||
__analyzer_eval (arr[4]); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'arr\\\[4\\\]'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
/* Replace one concrete binding's value with a different value. */
|
||||
arr[3] = c; /* (3) */
|
||||
__analyzer_eval (arr[2] == a); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[3] == c); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[3] == b); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (arr[4]); /* { dg-warning "UNKNOWN" } */ // TODO: report uninit
|
||||
__analyzer_eval (arr[4]); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'arr\\\[4\\\]'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
/* Symbolic binding. */
|
||||
arr[i] = d; /* (4) */
|
||||
|
|
|
@ -37,8 +37,10 @@ void test_3 (int i)
|
|||
int arr[2];
|
||||
|
||||
/* Concrete reads. */
|
||||
__analyzer_eval (arr[0] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (arr[0] == 42); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'arr\\\[0\\\]'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
/* Symbolic read. */
|
||||
__analyzer_eval (arr[i] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (arr[i] == 42); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value 'arr\\\[i\\\]'" "uninit" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
|
||||
/* { dg-additional-options "-Wno-incompatible-pointer-types -Wno-analyzer-too-complex" } */
|
||||
/* TODO: ideally we shouldn't have -Wno-analyzer-too-complex above; it
|
||||
appears to be needed due to the recursion. */
|
||||
|
@ -57,7 +58,7 @@ ts (struct dz *cx)
|
|||
{
|
||||
struct dz nt;
|
||||
|
||||
if (nt.r5)
|
||||
if (nt.r5) /* { dg-warning "use of uninitialized value 'nt.r5'" } */
|
||||
{
|
||||
m6 (cx);
|
||||
h5 (cx);
|
||||
|
|
44
gcc/testsuite/gcc.dg/analyzer/uninit-1.c
Normal file
44
gcc/testsuite/gcc.dg/analyzer/uninit-1.c
Normal file
|
@ -0,0 +1,44 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
int test_1 (void)
|
||||
{
|
||||
int i;
|
||||
return i; /* { dg-warning "use of uninitialized value 'i'" } */
|
||||
}
|
||||
|
||||
int test_2 (void)
|
||||
{
|
||||
int i;
|
||||
return i * 2; /* { dg-warning "use of uninitialized value 'i'" } */
|
||||
}
|
||||
|
||||
int test_3 (void)
|
||||
{
|
||||
static int i;
|
||||
return i;
|
||||
}
|
||||
|
||||
int test_4 (void)
|
||||
{
|
||||
int *p;
|
||||
return *p; /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
}
|
||||
|
||||
int test_5 (int flag, int *q)
|
||||
{
|
||||
int *p;
|
||||
if (flag) /* { dg-message "following 'false' branch" } */
|
||||
p = q;
|
||||
|
||||
/* There should be two enodes here,
|
||||
i.e. not merging the init vs non-init states. */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
return *p; /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
}
|
||||
|
||||
int test_6 (int i)
|
||||
{
|
||||
int arr[10];
|
||||
return arr[i]; /* { dg-warning "use of uninitialized value 'arr\\\[i\\\]'" } */
|
||||
}
|
14
gcc/testsuite/gcc.dg/analyzer/uninit-2.c
Normal file
14
gcc/testsuite/gcc.dg/analyzer/uninit-2.c
Normal file
|
@ -0,0 +1,14 @@
|
|||
typedef __SIZE_TYPE__ size_t;
|
||||
|
||||
extern size_t strlen (const char *__s)
|
||||
__attribute__ ((__nothrow__ , __leaf__))
|
||||
__attribute__ ((__pure__))
|
||||
__attribute__ ((__nonnull__ (1)));
|
||||
|
||||
extern char *read_file (const char *file);
|
||||
|
||||
size_t test_1 (const char *file)
|
||||
{
|
||||
char *str = read_file (file);
|
||||
return strlen (str);
|
||||
}
|
36
gcc/testsuite/gcc.dg/analyzer/uninit-3.c
Normal file
36
gcc/testsuite/gcc.dg/analyzer/uninit-3.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* Reduced from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c */
|
||||
|
||||
/* The original file has this licence header. */
|
||||
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (c) 2007-2011 Atheros Communications Inc.
|
||||
* Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc.
|
||||
* Copyright (c) 2016-2017 Erik Stromdahl <erik.stromdahl@gmail.com>
|
||||
*/
|
||||
|
||||
/* Adapted from include/linux/compiler_attributes.h. */
|
||||
#define __printf(a, b) __attribute__((__format__(printf, a, b)))
|
||||
|
||||
/* From drivers/net/wireless/ath/ath10k/core.h. */
|
||||
|
||||
struct ath10k;
|
||||
|
||||
/* From drivers/net/wireless/ath/ath10k/debug.h. */
|
||||
|
||||
enum ath10k_debug_mask {
|
||||
/* [...other values removed...] */
|
||||
ATH10K_DBG_USB_BULK = 0x00080000,
|
||||
};
|
||||
|
||||
extern unsigned int ath10k_debug_mask;
|
||||
|
||||
__printf(3, 4) void __ath10k_dbg(struct ath10k *ar,
|
||||
enum ath10k_debug_mask mask,
|
||||
const char *fmt, ...);
|
||||
|
||||
static void ath10k_usb_hif_tx_sg(struct ath10k *ar)
|
||||
{
|
||||
if (ath10k_debug_mask & ATH10K_DBG_USB_BULK)
|
||||
__ath10k_dbg(ar, ATH10K_DBG_USB_BULK, "usb bulk transmit failed: %d\n", 42);
|
||||
}
|
39
gcc/testsuite/gcc.dg/analyzer/uninit-4.c
Normal file
39
gcc/testsuite/gcc.dg/analyzer/uninit-4.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* Example of interprocedural detection of an uninitialized field
|
||||
in a heap-allocated struct. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
struct foo
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int k;
|
||||
};
|
||||
|
||||
struct foo *__attribute__((noinline))
|
||||
alloc_foo (int a, int b)
|
||||
{
|
||||
struct foo *p = malloc (sizeof (struct foo));
|
||||
if (!p)
|
||||
return NULL;
|
||||
p->i = a;
|
||||
p->k = b;
|
||||
return p;
|
||||
}
|
||||
|
||||
void test (int x, int y, int z)
|
||||
{
|
||||
struct foo *p = alloc_foo (x, z);
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
__analyzer_eval (p->i == x); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (p->j == y); /* { dg-warning "UNKNOWN" "unknown" } */
|
||||
/* { dg-warning "use of uninitialized value '\\*p\\.j'" "uninit" { target *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (p->k == z); /* { dg-warning "TRUE" } */
|
||||
|
||||
free (p);
|
||||
}
|
11
gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c
Normal file
11
gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
void f1 (int *);
|
||||
void f2 (int);
|
||||
|
||||
int foo (void)
|
||||
{
|
||||
int *p;
|
||||
|
||||
f1 (p); /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
f2 (p[0]); /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
return 0;
|
||||
}
|
12
gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c
Normal file
12
gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
int *p;
|
||||
int i;
|
||||
|
||||
p = &i; /* { dg-bogus "uninitialized" } */
|
||||
printf ("%d\n", p[0]); /* { dg-warning "use of uninitialized value '\\*p'" } */
|
||||
|
||||
return 0;
|
||||
}
|
8
gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c
Normal file
8
gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c
Normal file
|
@ -0,0 +1,8 @@
|
|||
int test (void)
|
||||
{
|
||||
int *ptr = (int *)__builtin_malloc (sizeof (int));
|
||||
*ptr = 42; /* { dg-warning "dereference of possibly-NULL 'ptr'" } */
|
||||
__builtin_free (ptr);
|
||||
|
||||
return *ptr; /* { dg-warning "use after 'free' of 'ptr'" "use-after-free" } */
|
||||
}
|
12
gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c
Normal file
12
gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
void test_1 (int x, int y, int *out)
|
||||
{
|
||||
int *ptr = (int *)malloc (sizeof (int));
|
||||
if (!ptr)
|
||||
return;
|
||||
*ptr = 19;
|
||||
|
||||
free (ptr);
|
||||
*out = *ptr; /* { dg-warning "use after 'free' of 'ptr'" } */
|
||||
}
|
|
@ -179,7 +179,7 @@ static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e,
|
|||
|
||||
f = 1 << (k - w);
|
||||
for (j = i >> w; j < z; j += f)
|
||||
q[j] = r;
|
||||
q[j] = r; /* { dg-warning "use of uninitialized value 'r.base'" } */
|
||||
|
||||
mask = (1 << w) - 1;
|
||||
while ((i & mask) != x[h]) {
|
||||
|
|
|
@ -16,15 +16,8 @@ typedef struct inflate_blocks_state {
|
|||
|
||||
extern int inflate_flush(inflate_blocks_statef *, z_stream *, int);
|
||||
|
||||
int inflate_blocks(inflate_blocks_statef *s, z_stream *z, int r) {
|
||||
uInt t;
|
||||
uLong b;
|
||||
uInt k;
|
||||
Byte *p;
|
||||
uInt n;
|
||||
Byte *q;
|
||||
uInt m;
|
||||
|
||||
int inflate_blocks(inflate_blocks_statef *s, z_stream *z, int r,
|
||||
uLong b, uInt k, Byte *p, uInt n, Byte *q, uInt m) {
|
||||
while (k < (3)) {
|
||||
{
|
||||
if (n)
|
||||
|
@ -41,7 +34,7 @@ int inflate_blocks(inflate_blocks_statef *s, z_stream *z, int r) {
|
|||
return inflate_flush(s, z, r);
|
||||
}
|
||||
};
|
||||
b |= ((uLong)(n--, *p++)) << k; /* { dg-warning "use of uninitialized value" "uninit-warning-removed" { xfail *-*-* } } */
|
||||
b |= ((uLong)(n--, *p++)) << k;
|
||||
k += 8;
|
||||
}
|
||||
}
|
||||
|
|
47
gcc/testsuite/gcc.dg/analyzer/zlib-6a.c
Normal file
47
gcc/testsuite/gcc.dg/analyzer/zlib-6a.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
typedef unsigned char Byte;
|
||||
typedef unsigned int uInt;
|
||||
typedef unsigned long uLong;
|
||||
|
||||
typedef struct z_stream_s {
|
||||
Byte *next_in;
|
||||
uInt avail_in;
|
||||
uLong total_in;
|
||||
} z_stream;
|
||||
|
||||
typedef struct inflate_blocks_state {
|
||||
uInt bitk;
|
||||
uLong bitb;
|
||||
Byte *write;
|
||||
} inflate_blocks_statef;
|
||||
|
||||
extern int inflate_flush(inflate_blocks_statef *, z_stream *, int);
|
||||
|
||||
int inflate_blocks(inflate_blocks_statef *s, z_stream *z, int r) {
|
||||
uInt t;
|
||||
uLong b;
|
||||
uInt k;
|
||||
Byte *p;
|
||||
uInt n;
|
||||
Byte *q;
|
||||
uInt m;
|
||||
|
||||
while (k < (3)) { /* { dg-warning "use of uninitialized value 'k'" } */
|
||||
{
|
||||
if (n) /* { dg-warning "use of uninitialized value 'n'" } */
|
||||
r = 0;
|
||||
else {
|
||||
{
|
||||
s->bitb = b; /* { dg-warning "use of uninitialized value 'b'" } */
|
||||
s->bitk = k; /* { dg-warning "use of uninitialized value 'k'" } */
|
||||
z->avail_in = n; /* { dg-warning "use of uninitialized value 'n'" } */
|
||||
z->total_in += p - z->next_in; /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
z->next_in = p; /* { dg-warning "use of uninitialized value 'p'" } */
|
||||
s->write = q; /* { dg-warning "use of uninitialized value 'q'" } */
|
||||
}
|
||||
return inflate_flush(s, z, r);
|
||||
}
|
||||
};
|
||||
b |= ((uLong)(n--, *p++)) << k; /* { dg-warning "use of uninitialized value" } */
|
||||
k += 8; /* { dg-warning "use of uninitialized value 'k'" } */
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
c { dg-additional-options "-std=legacy" }
|
||||
c { dg-additional-options "-std=legacy -Wno-analyzer-use-of-uninitialized-value -Wno-analyzer-too-complex" }
|
||||
|
||||
SUBROUTINE PPADD (A, C, BH)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue