From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2209) id B51623858424; Thu, 15 Jul 2021 19:09:15 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B51623858424 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="utf-8" From: David Malcolm To: gcc-cvs@gcc.gnu.org Subject: [gcc r12-2337] analyzer: reimplement -Wanalyzer-use-of-uninitialized-value [PR95006 et al] X-Act-Checkin: gcc X-Git-Author: David Malcolm X-Git-Refname: refs/heads/master X-Git-Oldrev: 98cd4d123aa14598b1f0d54c22663c8200a96d9c X-Git-Newrev: 33255ad3ac14e3953750fe0f2d82b901c2852ff6 Message-Id: <20210715190915.B51623858424@sourceware.org> Date: Thu, 15 Jul 2021 19:09:15 +0000 (GMT) X-BeenThere: gcc-cvs@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-cvs mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 15 Jul 2021 19:09:15 -0000 https://gcc.gnu.org/g:33255ad3ac14e3953750fe0f2d82b901c2852ff6 commit r12-2337-g33255ad3ac14e3953750fe0f2d82b901c2852ff6 Author: David Malcolm Date: Thu Jul 15 15:07:07 2021 -0400 analyzer: reimplement -Wanalyzer-use-of-uninitialized-value [PR95006 et al] 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 Diff: --- gcc/analyzer/analyzer.cc | 95 +++++--- gcc/analyzer/analyzer.h | 1 + gcc/analyzer/analyzer.opt | 4 + gcc/analyzer/constraint-manager.cc | 23 ++ gcc/analyzer/constraint-manager.h | 1 + gcc/analyzer/diagnostic-manager.cc | 46 ++++ gcc/analyzer/diagnostic-manager.h | 2 + gcc/analyzer/engine.cc | 250 ++++++++------------ gcc/analyzer/exploded-graph.h | 15 +- gcc/analyzer/pending-diagnostic.h | 13 + gcc/analyzer/program-state.cc | 43 ++-- gcc/analyzer/region-model-impl-calls.cc | 50 +++- gcc/analyzer/region-model-manager.cc | 4 + gcc/analyzer/region-model-reachability.cc | 16 +- gcc/analyzer/region-model.cc | 261 +++++++++++++++++++-- gcc/analyzer/region-model.h | 32 ++- gcc/analyzer/region.cc | 117 +++++++++ gcc/analyzer/region.h | 16 ++ gcc/analyzer/sm-malloc.cc | 19 ++ gcc/analyzer/store.cc | 55 +++++ gcc/analyzer/store.h | 10 + gcc/analyzer/svalue.cc | 32 ++- gcc/analyzer/svalue.h | 8 + gcc/doc/invoke.texi | 10 + gcc/testsuite/g++.dg/analyzer/pr93212.C | 4 +- gcc/testsuite/g++.dg/analyzer/pr94011.C | 2 +- gcc/testsuite/g++.dg/analyzer/pr94503.C | 2 + gcc/testsuite/gcc.dg/analyzer/clobbers-1.c | 3 +- gcc/testsuite/gcc.dg/analyzer/data-model-1.c | 32 ++- gcc/testsuite/gcc.dg/analyzer/data-model-20.c | 2 +- gcc/testsuite/gcc.dg/analyzer/explode-2.c | 16 +- gcc/testsuite/gcc.dg/analyzer/explode-2a.c | 4 +- gcc/testsuite/gcc.dg/analyzer/fgets-1.c | 31 +++ gcc/testsuite/gcc.dg/analyzer/fread-1.c | 13 + gcc/testsuite/gcc.dg/analyzer/malloc-1.c | 7 +- .../gcc.dg/analyzer/memset-CVE-2017-18549-1.c | 8 +- .../analyzer/pr93355-localealias-feasibility.c | 7 + gcc/testsuite/gcc.dg/analyzer/pr94047.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr94851-2.c | 2 +- gcc/testsuite/gcc.dg/analyzer/pr96841.c | 4 +- gcc/testsuite/gcc.dg/analyzer/pr98628.c | 3 +- gcc/testsuite/gcc.dg/analyzer/pr99042.c | 8 +- gcc/testsuite/gcc.dg/analyzer/symbolic-1.c | 6 +- gcc/testsuite/gcc.dg/analyzer/symbolic-7.c | 6 +- gcc/testsuite/gcc.dg/analyzer/torture/pr93649.c | 3 +- gcc/testsuite/gcc.dg/analyzer/uninit-1.c | 44 ++++ gcc/testsuite/gcc.dg/analyzer/uninit-2.c | 14 ++ gcc/testsuite/gcc.dg/analyzer/uninit-3.c | 36 +++ gcc/testsuite/gcc.dg/analyzer/uninit-4.c | 39 +++ gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c | 11 + gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c | 12 + gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c | 8 + gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c | 12 + gcc/testsuite/gcc.dg/analyzer/zlib-3.c | 2 +- gcc/testsuite/gcc.dg/analyzer/zlib-6.c | 13 +- gcc/testsuite/gcc.dg/analyzer/zlib-6a.c | 47 ++++ gcc/testsuite/gfortran.dg/analyzer/pr97668.f | 2 +- 57 files changed, 1232 insertions(+), 296 deletions(-) diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index a8ee1a1a2dc..ddace9a0c32 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -63,6 +63,51 @@ get_stmt_location (const gimple *stmt, function *fun) static tree fixup_tree_for_diagnostic_1 (tree expr, hash_set *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 *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 (def_stmt), visited); case GIMPLE_CALL: { gcall *call_stmt = as_a (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 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, diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 02830e474bc..d42bee7eb0d 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -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 diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index 7b77ae8a73d..6ddb6e3abb3 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -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. diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index 51cf52258a9..5b5a9dec0a9 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -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. */ diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h index 3173610a723..2bb3215e630 100644 --- a/gcc/analyzer/constraint-manager.h +++ b/gcc/analyzer/constraint-manager.h @@ -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 (); diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index d005facc20b..631fef6ad78 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -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 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); diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h index fc8ac2650c2..ad2eb4dfe65 100644 --- a/gcc/analyzer/diagnostic-manager.h +++ b/gcc/analyzer/diagnostic-manager.h @@ -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; diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index dc07a79e185..7662a7f7bab 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -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 -{ -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 (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 (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 (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 (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 (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 (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 +class stale_jmp_buf : public pending_diagnostic_subclass { 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 (stmt)) m_model.on_assignment (assign, NULL); + else if (const gcall *call = dyn_cast (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 (stmt)) m_model.on_return (return_, NULL); } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 1d8b73da7c4..8f48d8a286c 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -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 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, diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index 571fc1b56b9..48e2b3ec171 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -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. diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 6d60c0449ce..23cfcb032c6 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -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; diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 466d397ec49..4be6550f07f 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -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, diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 55acb90da73..7a52a64bbb3 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -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 ()) { diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc index e165cda014f..1f65307e394 100644 --- a/gcc/analyzer/region-model-reachability.cc +++ b/gcc/analyzer/region-model-reachability.cc @@ -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 escaped_fn_regs (m_mutable_base_regs.elements ()); for (hash_set::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. */ diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index acbbd112543..3fe2cce229b 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -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 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 % 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 + ''. + 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 +{ +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 (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 (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 (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. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index cf5232dfab8..71f6b3ee11e 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -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 (); } diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 46337179162..6cccb0f48f2 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -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. */ diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index 353d5c47b3c..a17e73c30c9 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -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. diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 40e64b3630f..9707a6863ce 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -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; diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index a65c7415b1b..0042a207ba6 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -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 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 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 * diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index 2ac2923723d..bc586947174 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -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); diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 70c23f016f9..22da769f095 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -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. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 5552fcf4c99..54b97f8617f 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -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: diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 62e165f8d1e..b16176ea560 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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: diff --git a/gcc/testsuite/g++.dg/analyzer/pr93212.C b/gcc/testsuite/g++.dg/analyzer/pr93212.C index 1029e8d547b..41507e2b837 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr93212.C +++ b/gcc/testsuite/g++.dg/analyzer/pr93212.C @@ -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 diff --git a/gcc/testsuite/g++.dg/analyzer/pr94011.C b/gcc/testsuite/g++.dg/analyzer/pr94011.C index 2642aa40c1b..81c0acd3954 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr94011.C +++ b/gcc/testsuite/g++.dg/analyzer/pr94011.C @@ -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 DV vu (DV j4) diff --git a/gcc/testsuite/g++.dg/analyzer/pr94503.C b/gcc/testsuite/g++.dg/analyzer/pr94503.C index 9432ac40764..ecf7121367d 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr94503.C +++ b/gcc/testsuite/g++.dg/analyzer/pr94503.C @@ -1,3 +1,5 @@ +// { dg-additional-options "-Wno-analyzer-use-of-uninitialized-value" } + template class allocator { public: allocator(const allocator &); diff --git a/gcc/testsuite/gcc.dg/analyzer/clobbers-1.c b/gcc/testsuite/gcc.dg/analyzer/clobbers-1.c index 824dbd42127..6400f845f9e 100644 --- a/gcc/testsuite/gcc.dg/analyzer/clobbers-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/clobbers-1.c @@ -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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c index 34932da7bbc..908d99981a6 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c @@ -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 diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-20.c b/gcc/testsuite/gcc.dg/analyzer/data-model-20.c index 8fdbb6b3df6..ff65883dac1 100644 --- a/gcc/testsuite/gcc.dg/analyzer/data-model-20.c +++ b/gcc/testsuite/gcc.dg/analyzer/data-model-20.c @@ -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; } } diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-2.c b/gcc/testsuite/gcc.dg/analyzer/explode-2.c index 70d8fecae8f..3b987e10a4a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/explode-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/explode-2.c @@ -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 @@ -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 *-*-* } } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-2a.c b/gcc/testsuite/gcc.dg/analyzer/explode-2a.c index 126407f3dd7..f60354cae1b 100644 --- a/gcc/testsuite/gcc.dg/analyzer/explode-2a.c +++ b/gcc/testsuite/gcc.dg/analyzer/explode-2a.c @@ -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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/fgets-1.c b/gcc/testsuite/gcc.dg/analyzer/fgets-1.c new file mode 100644 index 00000000000..e93d24c9de8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fgets-1.c @@ -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]; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fread-1.c b/gcc/testsuite/gcc.dg/analyzer/fread-1.c new file mode 100644 index 00000000000..593cb7f4aa0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fread-1.c @@ -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; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c index 448b8558ffe..df2fc9ce243 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c @@ -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; } diff --git a/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c b/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c index 9dd11390c4d..de9b5e3a1d0 100644 --- a/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/memset-CVE-2017-18549-1.c @@ -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...] */) diff --git a/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c index 1a34d05174a..c7b49d28cbe 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr93355-localealias-feasibility.c @@ -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. */ diff --git a/gcc/testsuite/gcc.dg/analyzer/pr94047.c b/gcc/testsuite/gcc.dg/analyzer/pr94047.c index 5107ec03fc0..d13da3eb044 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr94047.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr94047.c @@ -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); } diff --git a/gcc/testsuite/gcc.dg/analyzer/pr94851-2.c b/gcc/testsuite/gcc.dg/analyzer/pr94851-2.c index 60947216b7f..b837451b27a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr94851-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr94851-2.c @@ -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" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/pr96841.c b/gcc/testsuite/gcc.dg/analyzer/pr96841.c index 85466617054..c76658288b7 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr96841.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr96841.c @@ -10,10 +10,8 @@ void th (int *); void -bv (__SIZE_TYPE__ ny) +bv (__SIZE_TYPE__ ny, int ***mf) { - int ***mf; - while (l8 ()) { *mf = 0; diff --git a/gcc/testsuite/gcc.dg/analyzer/pr98628.c b/gcc/testsuite/gcc.dg/analyzer/pr98628.c index e2fa778472c..fa0ca961c46 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr98628.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr98628.c @@ -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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99042.c b/gcc/testsuite/gcc.dg/analyzer/pr99042.c index c3d124f3e31..f28a9deba34 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr99042.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr99042.c @@ -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) { diff --git a/gcc/testsuite/gcc.dg/analyzer/symbolic-1.c b/gcc/testsuite/gcc.dg/analyzer/symbolic-1.c index feab9ce3a2d..0eba646bdbd 100644 --- a/gcc/testsuite/gcc.dg/analyzer/symbolic-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/symbolic-1.c @@ -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) */ diff --git a/gcc/testsuite/gcc.dg/analyzer/symbolic-7.c b/gcc/testsuite/gcc.dg/analyzer/symbolic-7.c index 4f013677f86..665e0b645d3 100644 --- a/gcc/testsuite/gcc.dg/analyzer/symbolic-7.c +++ b/gcc/testsuite/gcc.dg/analyzer/symbolic-7.c @@ -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 } */ } diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/pr93649.c b/gcc/testsuite/gcc.dg/analyzer/torture/pr93649.c index 9d929395bb4..314c8f3521b 100644 --- a/gcc/testsuite/gcc.dg/analyzer/torture/pr93649.c +++ b/gcc/testsuite/gcc.dg/analyzer/torture/pr93649.c @@ -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); diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-1.c b/gcc/testsuite/gcc.dg/analyzer/uninit-1.c new file mode 100644 index 00000000000..8fcdcd6ad43 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-1.c @@ -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\\\]'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-2.c b/gcc/testsuite/gcc.dg/analyzer/uninit-2.c new file mode 100644 index 00000000000..0b0b8b60abd --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-2.c @@ -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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-3.c b/gcc/testsuite/gcc.dg/analyzer/uninit-3.c new file mode 100644 index 00000000000..fa33e0aa136 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-3.c @@ -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 + */ + +/* 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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-4.c b/gcc/testsuite/gcc.dg/analyzer/uninit-4.c new file mode 100644 index 00000000000..791b11106c9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-4.c @@ -0,0 +1,39 @@ +/* Example of interprocedural detection of an uninitialized field + in a heap-allocated struct. */ + +#include +#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); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c b/gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c new file mode 100644 index 00000000000..cc337dcb9b0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-pr94713.c @@ -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; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c b/gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c new file mode 100644 index 00000000000..df07f986a5e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/uninit-pr94714.c @@ -0,0 +1,12 @@ +#include + +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; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c b/gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c new file mode 100644 index 00000000000..fc138ee8215 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/use-after-free-2.c @@ -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" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c b/gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c new file mode 100644 index 00000000000..b19fd3de49f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/use-after-free-3.c @@ -0,0 +1,12 @@ +#include + +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'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-3.c b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c index 5faada16f38..57f5dcd8bfe 100644 --- a/gcc/testsuite/gcc.dg/analyzer/zlib-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/zlib-3.c @@ -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]) { diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-6.c b/gcc/testsuite/gcc.dg/analyzer/zlib-6.c index 0d814c0c096..c8e06c61313 100644 --- a/gcc/testsuite/gcc.dg/analyzer/zlib-6.c +++ b/gcc/testsuite/gcc.dg/analyzer/zlib-6.c @@ -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; } } diff --git a/gcc/testsuite/gcc.dg/analyzer/zlib-6a.c b/gcc/testsuite/gcc.dg/analyzer/zlib-6a.c new file mode 100644 index 00000000000..9676e0b3845 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/zlib-6a.c @@ -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'" } */ + } +} diff --git a/gcc/testsuite/gfortran.dg/analyzer/pr97668.f b/gcc/testsuite/gfortran.dg/analyzer/pr97668.f index 568c891cdc4..abb6bb212cc 100644 --- a/gcc/testsuite/gfortran.dg/analyzer/pr97668.f +++ b/gcc/testsuite/gfortran.dg/analyzer/pr97668.f @@ -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)