The initial gcc 10 era commit of the analyzer (in
757bf1dff5e8cee34c0a75d06140ca972bfecfa7) had an implementation of
-Wanalyzer-use-of-uninitialized-value, but was sufficiently buggy
that I removed it in
78b9783774bfd3540f38f5b1e3c7fc9f719653d7 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>
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.
/* 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);
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,
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
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.
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. */
void on_liveness_change (const svalue_set &live_svalues,
const region_model *model);
+ void purge_state_involving (const svalue *sval);
void canonicalize ();
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. */
}
}
+ /* 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,
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);
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;
{
}
-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
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
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 ());
{
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,
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 (),
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. */
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 ();
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
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,
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);
}
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;
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;
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,
/* 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. */
{
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.
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);
}
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;
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
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". */
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,
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 ())
{
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 ();
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. */
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. */
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;
{
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;
{
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);
}
}
+/* 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. */
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;
}
}
}
+/* 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.
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))
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
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;
}
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
}
}
- 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. */
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);
}
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;
}
/* 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);
/* 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. */
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;
};
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;
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);
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,
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);
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;
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
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. */
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 {}
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
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 (); }
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. */
}
}
+/* 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. */
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. */
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;
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.
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;
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. */
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 *
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. */
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. */
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 *);
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,
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);
|| (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 ()))
{
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:
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);
{
default:
gcc_unreachable ();
+ case POISON_KIND_UNINIT:
+ return "uninit";
case POISON_KIND_FREED:
return "freed";
case POISON_KIND_POPPED_STACK:
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. */
enum poison_kind
{
+ /* For use to describe uninitialized memory. */
+ POISON_KIND_UNINIT,
+
/* For use to describe freed memory. */
POISON_KIND_FREED,
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:
-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
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:
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
// { 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)
+// { dg-additional-options "-Wno-analyzer-use-of-uninitialized-value" }
+
template <typename> class allocator {
public:
allocator(const allocator &);
__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" } */
/* alloca. */
-void test_12 (void)
+int test_12 (void)
{
void *p = __builtin_alloca (256);
void *q = __builtin_alloca (256);
/* 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)
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
{
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)
__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];
__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];
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. */
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
for (; i >= 0; i++) {
free(arr[i]); /* { dg-bogus "double-'free'" } */
}
- free(arr);
+ free(arr); /* { dg-warning "leak" } */
return NULL;
}
}
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>
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 *-*-* } } */
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)
{
--- /dev/null
+/* { 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];
+}
--- /dev/null
+/* { 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;
+}
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);
}
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;
}
#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...] */)
__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...] */)
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__));
if (fp == NULL)
return 0;
+ if (fread (buf, sizeof buf, 1, fp) != 1)
+ {
+ fclose (fp);
+ return 0;
+ }
+
cp = buf;
/* Ignore leading white space. */
foo (void)
{
struct list l;
- tlist t = l;
+ tlist t = l; /* { dg-warning "use of uninitialized value 'l'" } */
for (;;)
bar (&t);
}
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" } */
th (int *);
void
-bv (__SIZE_TYPE__ ny)
+bv (__SIZE_TYPE__ ny, int ***mf)
{
- int ***mf;
-
while (l8 ())
{
*mf = 0;
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) {
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)
{
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)
{
__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) */
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 } */
}
+/* { 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. */
{
struct dz nt;
- if (nt.r5)
+ if (nt.r5) /* { dg-warning "use of uninitialized value 'nt.r5'" } */
{
m6 (cx);
h5 (cx);
--- /dev/null
+#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\\\]'" } */
+}
--- /dev/null
+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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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" } */
+}
--- /dev/null
+#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'" } */
+}
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]) {
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)
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;
}
}
--- /dev/null
+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'" } */
+ }
+}
-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)