From 21d09cb732dac5d980ac628eb3aca75c821028a2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 19 Mar 2021 09:01:57 -0400 Subject: analyzer: mark epath_finder with DISABLE_COPY_AND_ASSIGN [PR99614] cppcheck warns that class epath_finder does dynamic memory allocation, but is missing a copy constructor and operator=. This class isn't meant to be copied or assigned, so mark it with DISABLE_COPY_AND_ASSIGN. gcc/analyzer/ChangeLog: PR analyzer/99614 * diagnostic-manager.cc (class epath_finder): Add DISABLE_COPY_AND_ASSIGN. --- gcc/analyzer/diagnostic-manager.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 1a3535c..a376755 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -95,6 +95,8 @@ public: feasibility_problem **out_problem); private: + DISABLE_COPY_AND_ASSIGN(epath_finder); + exploded_path *explore_feasible_paths (const exploded_node *target_enode, const char *desc, unsigned diag_idx); bool process_worklist_item (feasible_worklist *worklist, -- cgit v1.1 From 5f256a70a05fcfc5a1caf56678ceb12b4f87f781 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sat, 20 Mar 2021 00:16:24 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 58b0fbb..70b1be5 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,9 @@ +2021-03-19 David Malcolm + + PR analyzer/99614 + * diagnostic-manager.cc (class epath_finder): Add + DISABLE_COPY_AND_ASSIGN. + 2021-03-15 Martin Liska * sm-file.cc (get_file_using_fns): Add missing comma in initializer. -- cgit v1.1 From 71fc4655ab86ab66b40165b2cb49c1395ca82a9a Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 24 Mar 2021 20:47:57 -0400 Subject: analyzer; reset sm-state for SSA names at def-stmts [PR93695,PR99044,PR99716] Various false positives from -fanalyzer involve SSA names in loops, where sm-state associated with an SSA name from one iteration is erroneously reused in a subsequent iteration. For example, PR analyzer/99716 describes a false "double 'fclose' of FILE 'fp'" on: for (i = 0; i < 2; ++i) { FILE *fp = fopen ("/tmp/test", "w"); fprintf (fp, "hello"); fclose (fp); } where the gimple of the loop body is: fp_7 = fopen ("/tmp/test", "w"); __builtin_fwrite ("hello", 1, 5, fp_7); fclose (fp_7); i_10 = i_1 + 1; where fp_7 transitions to "closed" at the fclose, but is not reset at the subsequent fopen, leading to the false positive when the fclose is re-reached. The fix is to reset sm-state for svalues that involve an SSA name at the SSA name's def-stmt, since the def-stmt effectively changes the meaning of those related svalues. gcc/analyzer/ChangeLog: PR analyzer/93695 PR analyzer/99044 PR analyzer/99716 * engine.cc (exploded_node::on_stmt): Clear sm-state involving an SSA name at the def-stmt of that SSA name. * program-state.cc (sm_state_map::purge_state_involving): New. * program-state.h (sm_state_map::purge_state_involving): New decl. * region-model.cc (selftest::test_involves_p): New. (selftest::analyzer_region_model_cc_tests): Call it. * svalue.cc (class involvement_visitor): New class (svalue::involves_p): New. * svalue.h (svalue::involves_p): New decl. gcc/testsuite/ChangeLog: PR analyzer/93695 PR analyzer/99044 PR analyzer/99716 * gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c: Remove xfail. * gcc.dg/analyzer/pr93695-1.c: New test. * gcc.dg/analyzer/pr99044-1.c: New test. * gcc.dg/analyzer/pr99044-2.c: New test. * gcc.dg/analyzer/pr99716-1.c: New test. * gcc.dg/analyzer/pr99716-2.c: New test. * gcc.dg/analyzer/pr99716-3.c: New test. --- gcc/analyzer/engine.cc | 25 +++++++++++++++++++++++++ gcc/analyzer/program-state.cc | 30 ++++++++++++++++++++++++++++++ gcc/analyzer/program-state.h | 3 +++ gcc/analyzer/region-model.cc | 32 ++++++++++++++++++++++++++++++++ gcc/analyzer/svalue.cc | 34 ++++++++++++++++++++++++++++++++++ gcc/analyzer/svalue.h | 2 ++ 6 files changed, 126 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 5792c14..d7866b5 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1244,6 +1244,31 @@ exploded_node::on_stmt (exploded_graph &eg, sm_state_map *new_smap = state->m_checker_states[sm_idx]; 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; diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index e427fff..fcea5ce 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -586,6 +586,36 @@ sm_state_map::on_unknown_change (const svalue *sval, impl_set_state (*iter, (state_machine::state_t)0, NULL, ext_state); } +/* Purge state for things involving SVAL. + For use when SVAL changes meaning, at the def_stmt on an SSA_NAME. */ + +void +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) + return; + + svalue_set svals_to_unset; + + for (map_t::iterator iter = m_map.begin (); + iter != m_map.end (); + ++iter) + { + const svalue *key = (*iter).first; + entry_t e = (*iter).second; + if (!m_sm.can_purge_p (e.m_state)) + continue; + if (key->involves_p (sval)) + svals_to_unset.add (key); + } + + for (svalue_set::iterator iter = svals_to_unset.begin (); + iter != svals_to_unset.end (); ++iter) + impl_set_state (*iter, (state_machine::state_t)0, NULL, ext_state); +} + /* Comparator for imposing an order on sm_state_map instances. */ int diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index d72945d..54fdb5b 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -157,6 +157,9 @@ public: bool is_mutable, const extrinsic_state &ext_state); + void purge_state_involving (const svalue *sval, + const extrinsic_state &ext_state); + iterator_t begin () const { return m_map.begin (); } iterator_t end () const { return m_map.end (); } size_t elements () const { return m_map.elements (); } diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 96ed549..fb5dc39 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -5114,6 +5114,37 @@ test_alloca () ASSERT_EQ (model.get_rvalue (p, &ctxt)->get_kind (), SK_POISONED); } +/* Verify that svalue::involves_p works. */ + +static void +test_involves_p () +{ + region_model_manager mgr; + tree int_star = build_pointer_type (integer_type_node); + tree p = build_global_decl ("p", int_star); + tree q = build_global_decl ("q", int_star); + + test_region_model_context ctxt; + region_model model (&mgr); + const svalue *p_init = model.get_rvalue (p, &ctxt); + const svalue *q_init = model.get_rvalue (q, &ctxt); + + ASSERT_TRUE (p_init->involves_p (p_init)); + ASSERT_FALSE (p_init->involves_p (q_init)); + + const region *star_p_reg = mgr.get_symbolic_region (p_init); + const region *star_q_reg = mgr.get_symbolic_region (q_init); + + const svalue *init_star_p = mgr.get_or_create_initial_value (star_p_reg); + const svalue *init_star_q = mgr.get_or_create_initial_value (star_q_reg); + + ASSERT_TRUE (init_star_p->involves_p (p_init)); + ASSERT_FALSE (p_init->involves_p (init_star_p)); + ASSERT_FALSE (init_star_p->involves_p (q_init)); + ASSERT_TRUE (init_star_q->involves_p (q_init)); + ASSERT_FALSE (init_star_q->involves_p (p_init)); +} + /* Run all of the selftests within this file. */ void @@ -5150,6 +5181,7 @@ analyzer_region_model_cc_tests () test_POINTER_PLUS_EXPR_then_MEM_REF (); test_malloc (); test_alloca (); + test_involves_p (); } } // namespace selftest diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index d6305a3..897e84e 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -481,6 +481,40 @@ svalue::cmp_ptr_ptr (const void *p1, const void *p2) return cmp_ptr (sval1, sval2); } +/* Subclass of visitor for use in implementing svalue::involves_p. */ + +class involvement_visitor : public visitor +{ +public: + involvement_visitor (const svalue *needle) + : m_needle (needle), m_found (false) {} + + void visit_initial_svalue (const initial_svalue *candidate) + { + if (candidate == m_needle) + m_found = true; + } + + bool found_p () const { return m_found; } + +private: + const svalue *m_needle; + bool m_found; +}; + +/* Return true iff this svalue is defined in terms of OTHER. */ + +bool +svalue::involves_p (const svalue *other) const +{ + /* Currently only implemented for initial_svalue. */ + gcc_assert (other->get_kind () == SK_INITIAL); + + involvement_visitor v (other); + accept (&v); + return v.found_p (); +} + /* class region_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for region_svalue. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 672a89c..7fe0ba3 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -136,6 +136,8 @@ public: static int cmp_ptr (const svalue *, const svalue *); static int cmp_ptr_ptr (const void *, const void *); + bool involves_p (const svalue *other) const; + protected: svalue (complexity c, tree type) : m_complexity (c), m_type (type) -- cgit v1.1 From 4493b1c1ad7e2b2a60ad70563b81f51173115471 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Fri, 26 Mar 2021 00:16:25 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 70b1be5..7084246 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,18 @@ +2021-03-25 David Malcolm + + PR analyzer/93695 + PR analyzer/99044 + PR analyzer/99716 + * engine.cc (exploded_node::on_stmt): Clear sm-state involving + an SSA name at the def-stmt of that SSA name. + * program-state.cc (sm_state_map::purge_state_involving): New. + * program-state.h (sm_state_map::purge_state_involving): New decl. + * region-model.cc (selftest::test_involves_p): New. + (selftest::analyzer_region_model_cc_tests): Call it. + * svalue.cc (class involvement_visitor): New class + (svalue::involves_p): New. + * svalue.h (svalue::involves_p): New decl. + 2021-03-19 David Malcolm PR analyzer/99614 -- cgit v1.1 From 0f9aa35c79a0fe195d5076375b5794246cf44819 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 26 Mar 2021 13:26:15 -0400 Subject: analyzer: only call get_diagnostic_tree when it's needed impl_sm_context::get_diagnostic_tree could be expensive, and I find myself needing to put a breakpoint on it to debug PR analyzer/99771, so only call it if we're about to use the result. gcc/analyzer/ChangeLog: * sm-file.cc (fileptr_state_machine::on_stmt): Only call get_diagnostic_tree if the result will be used. * sm-malloc.cc (malloc_state_machine::on_stmt): Likewise. (malloc_state_machine::on_deallocator_call): Likewise. (malloc_state_machine::on_realloc_call): Likewise. (malloc_state_machine::on_realloc_call): Likewise. * sm-sensitive.cc (sensitive_state_machine::warn_for_any_exposure): Likewise. * sm-taint.cc (taint_state_machine::on_stmt): Likewise. --- gcc/analyzer/sm-file.cc | 2 +- gcc/analyzer/sm-malloc.cc | 10 +++++++--- gcc/analyzer/sm-sensitive.cc | 8 +++++--- gcc/analyzer/sm-taint.cc | 4 +++- 4 files changed, 16 insertions(+), 8 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index 7a81c8f..d64c313 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -344,7 +344,6 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt, if (is_named_call_p (callee_fndecl, "fclose", call, 1)) { tree arg = gimple_call_arg (call, 0); - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed); @@ -356,6 +355,7 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt, if (sm_ctxt->get_state (stmt, arg) == m_closed) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new double_fclose (*this, diag_arg)); sm_ctxt->set_next_state (stmt, arg, m_stop); diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index ef250c8..ae03b06 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1674,11 +1674,11 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, if (TREE_CODE (op) == MEM_REF) { tree arg = TREE_OPERAND (op, 0); - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); state_t state = sm_ctxt->get_state (stmt, arg); if (unchecked_p (state)) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new possible_null_deref (*this, diag_arg)); const allocation_state *astate = as_a_allocation_state (state); @@ -1686,12 +1686,14 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, } else if (state == m_null) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new null_deref (*this, diag_arg)); sm_ctxt->set_next_state (stmt, arg, m_stop); } else if (freed_p (state)) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); const allocation_state *astate = as_a_allocation_state (state); sm_ctxt->warn (node, stmt, arg, new use_after_free (*this, diag_arg, @@ -1738,7 +1740,6 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, if (argno >= gimple_call_num_args (call)) return; tree arg = gimple_call_arg (call, argno); - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); state_t state = sm_ctxt->get_state (call, arg); @@ -1752,6 +1753,7 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, if (!astate->m_deallocators->contains_p (d)) { /* Wrong allocator. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); pending_diagnostic *pd = new mismatching_deallocation (*this, diag_arg, astate->m_deallocators, @@ -1766,6 +1768,7 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, else if (state == d->m_freed) { /* freed -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, call, arg, new double_free (*this, diag_arg, d->m_name)); sm_ctxt->set_next_state (call, arg, m_stop); @@ -1773,6 +1776,7 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, else if (state == m_non_heap) { /* non-heap -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, call, arg, new free_of_non_heap (*this, diag_arg, d->m_name)); @@ -1806,7 +1810,6 @@ malloc_state_machine::on_realloc_call (sm_context *sm_ctxt, const gcall *call) const { tree ptr = gimple_call_arg (call, 0); - tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr); state_t state = sm_ctxt->get_state (call, ptr); @@ -1818,6 +1821,7 @@ malloc_state_machine::on_realloc_call (sm_context *sm_ctxt, if (astate->m_deallocators != &m_free) { /* Wrong allocator. */ + tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr); pending_diagnostic *pd = new mismatching_deallocation (*this, diag_ptr, astate->m_deallocators, diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc index 95172f0..9703f7e 100644 --- a/gcc/analyzer/sm-sensitive.cc +++ b/gcc/analyzer/sm-sensitive.cc @@ -174,10 +174,12 @@ sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt, const gimple *stmt, tree arg) const { - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); if (sm_ctxt->get_state (stmt, arg) == m_sensitive) - sm_ctxt->warn (node, stmt, arg, - new exposure_through_output_file (*this, diag_arg)); + { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, stmt, arg, + new exposure_through_output_file (*this, diag_arg)); + } } /* Implementation of state_machine::on_stmt vfunc for diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc index 2b2792e..e2460f9 100644 --- a/gcc/analyzer/sm-taint.cc +++ b/gcc/analyzer/sm-taint.cc @@ -227,7 +227,6 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt, if (op == ARRAY_REF) { tree arg = TREE_OPERAND (rhs1, 1); - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); /* Unsigned types have an implicit lower bound. */ bool is_unsigned = false; @@ -239,6 +238,7 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt, if (state == m_tainted) { /* Complain about missing bounds. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); pending_diagnostic *d = new tainted_array_index (*this, diag_arg, is_unsigned @@ -249,6 +249,7 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt, else if (state == m_has_lb) { /* Complain about missing upper bound. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new tainted_array_index (*this, diag_arg, BOUNDS_LOWER)); @@ -259,6 +260,7 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt, /* Complain about missing lower bound. */ if (!is_unsigned) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new tainted_array_index (*this, diag_arg, BOUNDS_UPPER)); -- cgit v1.1 From d0b7c821754e2b16e9e84d877082105799adf238 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 26 Mar 2021 18:54:18 -0400 Subject: analyzer: remove old decl of region::dump_to_pp This was made redundant in the GCC 11 rewrite of state (808f4dfeb3a95f50f15e71148e5c1067f90a126d). gcc/analyzer/ChangeLog: * region.h (region::dump_to_pp): Remove old decl. --- gcc/analyzer/region.h | 5 ----- 1 file changed, 5 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index ea24b38..175a82a 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -128,11 +128,6 @@ public: pretty_printer *pp) const; label_text get_desc (bool simple=true) const; - void dump_to_pp (const region_model &model, - pretty_printer *pp, - const char *prefix, - bool is_last_child) const; - virtual void dump_to_pp (pretty_printer *pp, bool simple) const = 0; void dump (bool simple) const; -- cgit v1.1 From 08d2edae5d84209c0dcf327a13d4f6b4eacdb1ac Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 31 Mar 2021 00:16:31 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 7084246..730039a 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,19 @@ +2021-03-30 David Malcolm + + * region.h (region::dump_to_pp): Remove old decl. + +2021-03-30 David Malcolm + + * sm-file.cc (fileptr_state_machine::on_stmt): Only call + get_diagnostic_tree if the result will be used. + * sm-malloc.cc (malloc_state_machine::on_stmt): Likewise. + (malloc_state_machine::on_deallocator_call): Likewise. + (malloc_state_machine::on_realloc_call): Likewise. + (malloc_state_machine::on_realloc_call): Likewise. + * sm-sensitive.cc + (sensitive_state_machine::warn_for_any_exposure): Likewise. + * sm-taint.cc (taint_state_machine::on_stmt): Likewise. + 2021-03-25 David Malcolm PR analyzer/93695 -- cgit v1.1 From e4bb1bd60a9fd1bed36092a990aa5fed5d45bfa6 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 29 Mar 2021 16:13:32 -0400 Subject: analyzer: avoid printing '' for SSA names [PR99771] We don't want to print '' in our diagnostics, but PR analyzer/99771 lists various cases where -fanalyzer does, due to using the SSA_NAME for a temporary when determining the best tree to use. This can happen in two ways: (a) ...when a better expression than the SSA_NAME could be built, but finding it requires traversing the relationships in the region_model in a graph-like way, rather than by considering individual svalues and regions. (b) ...when the only remaining user of the underlying svalue is the SSA_NAME, typically due to the diagnostic referring to a temporary. I've been experimenting with fixing (a), but don't have a good fix yet. In the meantime, this patch addresses (b) by detecting if we have the SSA_NAME for a temporary, and, for the cases where it's possible, reconstructing a tree by walking the def-stmts. This fixes various cases of (b) and ameliorates some cases of (a). gcc/analyzer/ChangeLog: PR analyzer/99771 * analyzer.cc (maybe_reconstruct_from_def_stmt): New. (fixup_tree_for_diagnostic_1): New. (fixup_tree_for_diagnostic): New. * analyzer.h (fixup_tree_for_diagnostic): New decl. * checker-path.cc (call_event::get_desc): Call fixup_tree_for_diagnostic and use it for the call_with_state call. (warning_event::get_desc): Likewise for the final_event and make_label_text calls. * engine.cc (impl_region_model_context::on_state_leak): Likewise for the on_leak and add_diagnostic calls. * region-model.cc (region_model::get_representative_tree): Likewise for the result. gcc/testsuite/ChangeLog: PR analyzer/99771 * gcc.dg/analyzer/data-model-10.c: Update expected output. * gcc.dg/analyzer/malloc-ipa-13.c: Likewise. * gcc.dg/analyzer/malloc-ipa-13a.c: New test. * gcc.dg/analyzer/pr99771-1.c: New test. --- gcc/analyzer/analyzer.cc | 128 +++++++++++++++++++++++++++++++++++++++++++ gcc/analyzer/analyzer.h | 1 + gcc/analyzer/checker-path.cc | 10 ++-- gcc/analyzer/engine.cc | 5 +- gcc/analyzer/region-model.cc | 4 +- 5 files changed, 140 insertions(+), 8 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index df8d881..2b4cffd 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -60,6 +60,134 @@ get_stmt_location (const gimple *stmt, function *fun) return stmt->location; } +static tree +fixup_tree_for_diagnostic_1 (tree expr, hash_set *visited); + +/* 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. + SSA_NAME must be non-NULL. + VISITED must be non-NULL; it is used to ensure termination. + + Return NULL_TREE if there is a problem. */ + +static tree +maybe_reconstruct_from_def_stmt (tree ssa_name, + hash_set *visited) +{ + /* Ensure termination. */ + if (visited->contains (ssa_name)) + return NULL_TREE; + visited->add (ssa_name); + + gimple *def_stmt = SSA_NAME_DEF_STMT (ssa_name); + + switch (gimple_code (def_stmt)) + { + default: + gcc_unreachable (); + case GIMPLE_NOP: + case GIMPLE_PHI: + /* 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; + case GIMPLE_CALL: + { + gcall *call_stmt = as_a (def_stmt); + tree return_type = gimple_call_return_type (call_stmt); + tree fn = fixup_tree_for_diagnostic_1 (gimple_call_fn (call_stmt), + visited); + unsigned num_args = gimple_call_num_args (call_stmt); + auto_vec args (num_args); + for (unsigned i = 0; i < num_args; i++) + { + tree arg = gimple_call_arg (call_stmt, i); + arg = fixup_tree_for_diagnostic_1 (arg, visited); + if (arg == NULL_TREE) + return NULL_TREE; + args.quick_push (arg); + } + return build_call_array_loc (gimple_location (call_stmt), + return_type, fn, + num_args, &args[0]); + } + break; + } +} + +/* Subroutine of fixup_tree_for_diagnostic: attempt to fixup EXPR, + which can be NULL. + VISITED must be non-NULL; it is used to ensure termination. */ + +static tree +fixup_tree_for_diagnostic_1 (tree expr, hash_set *visited) +{ + if (expr + && TREE_CODE (expr) == SSA_NAME + && (SSA_NAME_VAR (expr) == NULL_TREE + || DECL_ARTIFICIAL (SSA_NAME_VAR (expr)))) + if (tree expr2 = maybe_reconstruct_from_def_stmt (expr, visited)) + return expr2; + return expr; +} + +/* We don't want to print '' in our diagnostics (PR analyzer/99771), + but sometimes we generate diagnostics involving an ssa name for a + temporary. + + Work around this by attempting to reconstruct a tree expression for + such temporaries based on their def-stmts. + + Otherwise return EXPR. + + EXPR can be NULL. */ + +tree +fixup_tree_for_diagnostic (tree expr) +{ + hash_set visited; + return fixup_tree_for_diagnostic_1 (expr, &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 f50ac66..fb568e4 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -108,6 +108,7 @@ extern void dump_quoted_tree (pretty_printer *pp, tree t); 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); /* 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/checker-path.cc b/gcc/analyzer/checker-path.cc index e6e3ec1..7d229bb 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -634,12 +634,13 @@ call_event::get_desc (bool can_colorize) const if (m_critical_state && m_pending_diagnostic) { gcc_assert (m_var); + tree var = fixup_tree_for_diagnostic (m_var); label_text custom_desc = m_pending_diagnostic->describe_call_with_state (evdesc::call_with_state (can_colorize, m_sedge->m_src->m_fun->decl, m_sedge->m_dest->m_fun->decl, - m_var, + var, m_critical_state)); if (custom_desc.m_buffer) return custom_desc; @@ -880,19 +881,20 @@ warning_event::get_desc (bool can_colorize) const { if (m_pending_diagnostic) { + tree var = fixup_tree_for_diagnostic (m_var); label_text ev_desc = m_pending_diagnostic->describe_final_event - (evdesc::final_event (can_colorize, m_var, m_state)); + (evdesc::final_event (can_colorize, var, m_state)); if (ev_desc.m_buffer) { if (m_sm && flag_analyzer_verbose_state_changes) { label_text result; - if (m_var) + if (var) result = make_label_text (can_colorize, "%s (%qE is in state %qs)", ev_desc.m_buffer, - m_var, m_state->get_name ()); + var, m_state->get_name ()); else result = make_label_text (can_colorize, "%s (in global state %qs)", diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index d7866b5..1fb96de 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -634,12 +634,13 @@ impl_region_model_context::on_state_leak (const state_machine &sm, } } - pending_diagnostic *pd = sm.on_leak (leaked_tree); + tree leaked_tree_for_diag = fixup_tree_for_diagnostic (leaked_tree); + pending_diagnostic *pd = sm.on_leak (leaked_tree_for_diag); if (pd) m_eg->get_diagnostic_manager ().add_diagnostic (&sm, m_enode_for_diag, m_enode_for_diag->get_supernode (), m_stmt, &stmt_finder, - leaked_tree, sval, state, pd); + leaked_tree_for_diag, sval, state, pd); } /* Implementation of region_model_context::on_condition vfunc. diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index fb5dc39..c245bfe 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -2325,9 +2325,9 @@ region_model::get_representative_tree (const svalue *sval) const /* Strip off any top-level cast. */ if (expr && TREE_CODE (expr) == NOP_EXPR) - return TREE_OPERAND (expr, 0); + expr = TREE_OPERAND (expr, 0); - return expr; + return fixup_tree_for_diagnostic (expr); } /* Implementation of region_model::get_representative_path_var. -- cgit v1.1 From 95d217ab52d31dc06fda42fc136dea165909e88b Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 1 Apr 2021 00:16:39 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 730039a..514fc50 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,19 @@ +2021-03-31 David Malcolm + + PR analyzer/99771 + * analyzer.cc (maybe_reconstruct_from_def_stmt): New. + (fixup_tree_for_diagnostic_1): New. + (fixup_tree_for_diagnostic): New. + * analyzer.h (fixup_tree_for_diagnostic): New decl. + * checker-path.cc (call_event::get_desc): Call + fixup_tree_for_diagnostic and use it for the call_with_state call. + (warning_event::get_desc): Likewise for the final_event and + make_label_text calls. + * engine.cc (impl_region_model_context::on_state_leak): Likewise + for the on_leak and add_diagnostic calls. + * region-model.cc (region_model::get_representative_tree): + Likewise for the result. + 2021-03-30 David Malcolm * region.h (region::dump_to_pp): Remove old decl. -- cgit v1.1 From 6e943d5a2e3c581e40ccab46bec986a1f22c70c2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 1 Apr 2021 18:22:52 -0400 Subject: analyzer: record per-enode saved_diagnostics Various places iterate through all of the saved_diagnostics to find just the ones that are at a given enode. This patch adds a per-enode record of the diagnostics that are at each node, to save iterating through all of the diagnostics each time. gcc/analyzer/ChangeLog: * diagnostic-manager.cc (diagnostic_manager::add_diagnostic): Make enode param non-constant, and call add_diagnostic on it. Add enode index to log message. (diagnostic_manager::add_diagnostic): Make enode param non-constant. * diagnostic-manager.h (diagnostic_manager::add_diagnostic): Likewise for both decls. * engine.cc (impl_region_model_context::impl_region_model_context): Likewise for enode_for_diag. (impl_sm_context::impl_sm_context): Likewise. (impl_sm_context::m_enode_for_diag): Likewise. (exploded_node::dump_dot): Don't pass the diagnostic manager to dump_saved_diagnostics. (exploded_node::dump_saved_diagnostics): Drop param. Iterate directly through all saved diagnostics for the enode, rather than all saved diagnostics in the diagnostic_manager and filtering. (exploded_node::on_stmt): Make non-const. (exploded_node::on_edge): Likewise. (exploded_node::on_longjmp): Likewise. (exploded_node::detect_leaks): Likewise. (exploded_graph::get_or_create_node): Make enode_for_diag param non-const. (exploded_graph_annotator::print_enode): Iterate directly through all saved diagnostics for the enode, rather than all saved diagnostics in the diagnostic_manager and filtering. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Make enode_for_diag param non-constant. (impl_region_model_context::m_enode_for_diag): Likewise. (exploded_node::dump_saved_diagnostics): Drop param. (exploded_node::on_stmt): Make non-const. (exploded_node::on_edge): Likewise. (exploded_node::on_longjmp): Likewise. (exploded_node::detect_leaks): Likewise. (exploded_node::add_diagnostic): New. (exploded_node::get_num_diagnostics): New. (exploded_node::get_saved_diagnostic): New. (exploded_node::m_saved_diagnostics): New. (exploded_graph::get_or_create_node): Make enode_for_diag param non-constant. * feasible-graph.cc (feasible_node::dump_dot): Drop diagnostic_manager from call to dump_saved_diagnostics. * program-state.cc (program_state::on_edge): Convert enode param to non-const pointer. (program_state::prune_for_point): Likewise for enode_for_diag param. * program-state.h (program_state::on_edge): Convert enode param to non-const pointer. (program_state::prune_for_point): Likewise for enode_for_diag param. --- gcc/analyzer/diagnostic-manager.cc | 9 ++++--- gcc/analyzer/diagnostic-manager.h | 4 +-- gcc/analyzer/engine.cc | 52 +++++++++++++++++--------------------- gcc/analyzer/exploded-graph.h | 34 ++++++++++++++++++------- gcc/analyzer/feasible-graph.cc | 5 ++-- gcc/analyzer/program-state.cc | 12 ++++----- gcc/analyzer/program-state.h | 4 +-- 7 files changed, 65 insertions(+), 55 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index a376755..9ec3e89 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -791,7 +791,7 @@ diagnostic_manager::diagnostic_manager (logger *logger, engine *eng, void diagnostic_manager::add_diagnostic (const state_machine *sm, - const exploded_node *enode, + exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, tree var, @@ -809,16 +809,17 @@ diagnostic_manager::add_diagnostic (const state_machine *sm, = new saved_diagnostic (sm, enode, snode, stmt, finder, var, sval, state, d, m_saved_diagnostics.length ()); m_saved_diagnostics.safe_push (sd); + enode->add_diagnostic (sd); if (get_logger ()) - log ("adding saved diagnostic %i at SN %i: %qs", + log ("adding saved diagnostic %i at SN %i to EN %i: %qs", sd->get_index (), - snode->m_index, d->get_kind ()); + snode->m_index, enode->m_index, d->get_kind ()); } /* Queue pending_diagnostic D at ENODE for later emission. */ void -diagnostic_manager::add_diagnostic (const exploded_node *enode, +diagnostic_manager::add_diagnostic (exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, pending_diagnostic *d) diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h index 1454977..fc8ac26 100644 --- a/gcc/analyzer/diagnostic-manager.h +++ b/gcc/analyzer/diagnostic-manager.h @@ -101,7 +101,7 @@ public: json::object *to_json () const; void add_diagnostic (const state_machine *sm, - const exploded_node *enode, + exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, tree var, @@ -109,7 +109,7 @@ public: state_machine::state_t state, pending_diagnostic *d); - void add_diagnostic (const exploded_node *enode, + void add_diagnostic (exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, pending_diagnostic *d); diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 1fb96de..857d434 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -75,7 +75,7 @@ namespace ana { impl_region_model_context:: impl_region_model_context (exploded_graph &eg, - const exploded_node *enode_for_diag, + exploded_node *enode_for_diag, const program_state *old_state, program_state *new_state, const gimple *stmt, @@ -199,7 +199,7 @@ public: impl_sm_context (exploded_graph &eg, int sm_idx, const state_machine &sm, - const exploded_node *enode_for_diag, + exploded_node *enode_for_diag, const program_state *old_state, program_state *new_state, const sm_state_map *old_smap, @@ -352,7 +352,7 @@ public: log_user m_logger; exploded_graph &m_eg; - const exploded_node *m_enode_for_diag; + exploded_node *m_enode_for_diag; const program_state *m_old_state; program_state *m_new_state; const sm_state_map *m_old_smap; @@ -950,7 +950,7 @@ exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &args) const dump_processed_stmts (pp); } - dump_saved_diagnostics (pp, args.m_eg.get_diagnostic_manager ()); + dump_saved_diagnostics (pp); args.dump_extra_info (this, pp); @@ -988,18 +988,15 @@ exploded_node::dump_processed_stmts (pretty_printer *pp) const /* Dump any saved_diagnostics at this enode to PP. */ void -exploded_node::dump_saved_diagnostics (pretty_printer *pp, - const diagnostic_manager &dm) const +exploded_node::dump_saved_diagnostics (pretty_printer *pp) const { - for (unsigned i = 0; i < dm.get_num_diagnostics (); i++) + unsigned i; + const saved_diagnostic *sd; + FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd) { - const saved_diagnostic *sd = dm.get_saved_diagnostic (i); - if (sd->m_enode == this) - { - pp_printf (pp, "DIAGNOSTIC: %s (sd: %i)", - sd->m_d->get_kind (), sd->get_index ()); - pp_newline (pp); - } + pp_printf (pp, "DIAGNOSTIC: %s (sd: %i)", + sd->m_d->get_kind (), sd->get_index ()); + pp_newline (pp); } } @@ -1119,7 +1116,7 @@ exploded_node::on_stmt_flags exploded_node::on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, - program_state *state) const + program_state *state) { logger *logger = eg.get_logger (); LOG_SCOPE (logger); @@ -1303,14 +1300,14 @@ bool exploded_node::on_edge (exploded_graph &eg, const superedge *succ, program_point *next_point, - program_state *next_state) const + program_state *next_state) { LOG_FUNC (eg.get_logger ()); if (!next_point->on_edge (eg, succ)) return false; - if (!next_state->on_edge (eg, *this, succ)) + if (!next_state->on_edge (eg, this, succ)) return false; return true; @@ -1435,7 +1432,7 @@ void exploded_node::on_longjmp (exploded_graph &eg, const gcall *longjmp_call, program_state *new_state, - region_model_context *ctxt) const + region_model_context *ctxt) { tree buf_ptr = gimple_call_arg (longjmp_call, 0); gcc_assert (POINTER_TYPE_P (TREE_TYPE (buf_ptr))); @@ -1544,7 +1541,7 @@ exploded_node::on_longjmp (exploded_graph &eg, leaks. */ void -exploded_node::detect_leaks (exploded_graph &eg) const +exploded_node::detect_leaks (exploded_graph &eg) { LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index); @@ -2163,7 +2160,7 @@ exploded_graph::add_function_entry (function *fun) exploded_node * exploded_graph::get_or_create_node (const program_point &point, const program_state &state, - const exploded_node *enode_for_diag) + exploded_node *enode_for_diag) { logger * const logger = get_logger (); LOG_FUNC (logger); @@ -4664,16 +4661,13 @@ private: break; } gv->end_tdtr (); + /* Dump any saved_diagnostics at this enode. */ - { - const diagnostic_manager &dm = m_eg.get_diagnostic_manager (); - for (unsigned i = 0; i < dm.get_num_diagnostics (); i++) - { - const saved_diagnostic *sd = dm.get_saved_diagnostic (i); - if (sd->m_enode == enode) - print_saved_diagnostic (gv, sd); - } - } + for (unsigned i = 0; i < enode->get_num_diagnostics (); i++) + { + const saved_diagnostic *sd = enode->get_saved_diagnostic (i); + print_saved_diagnostic (gv, sd); + } pp_printf (pp, ""); pp_printf (pp, ""); } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index deb739f..2566641 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -30,7 +30,7 @@ class impl_region_model_context : public region_model_context { public: impl_region_model_context (exploded_graph &eg, - const exploded_node *enode_for_diag, + exploded_node *enode_for_diag, /* TODO: should we be getting the ECs from the old state, rather than the new? */ @@ -70,7 +70,7 @@ class impl_region_model_context : public region_model_context exploded_graph *m_eg; log_user m_logger; - const exploded_node *m_enode_for_diag; + exploded_node *m_enode_for_diag; const program_state *m_old_state; program_state *m_new_state; const gimple *m_stmt; @@ -186,8 +186,7 @@ class exploded_node : public dnode void dump (const extrinsic_state &ext_state) const; void dump_processed_stmts (pretty_printer *pp) const; - void dump_saved_diagnostics (pretty_printer *pp, - const diagnostic_manager &dm) const; + void dump_saved_diagnostics (pretty_printer *pp) const; json::object *to_json (const extrinsic_state &ext_state) const; @@ -227,17 +226,17 @@ class exploded_node : public dnode on_stmt_flags on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, - program_state *state) const; + program_state *state); bool on_edge (exploded_graph &eg, const superedge *succ, program_point *next_point, - program_state *next_state) const; + program_state *next_state); void on_longjmp (exploded_graph &eg, const gcall *call, program_state *new_state, - region_model_context *ctxt) const; + region_model_context *ctxt); - void detect_leaks (exploded_graph &eg) const; + void detect_leaks (exploded_graph &eg); const program_point &get_point () const { return m_ps.get_point (); } const supernode *get_supernode () const @@ -269,6 +268,19 @@ class exploded_node : public dnode m_status = status; } + void add_diagnostic (const saved_diagnostic *sd) + { + m_saved_diagnostics.safe_push (sd); + } + unsigned get_num_diagnostics () const + { + return m_saved_diagnostics.length (); + } + const saved_diagnostic *get_saved_diagnostic (unsigned idx) const + { + return m_saved_diagnostics[idx]; + } + private: DISABLE_COPY_AND_ASSIGN (exploded_node); @@ -278,6 +290,10 @@ private: enum status m_status; + /* The saved_diagnostics at this enode, borrowed from the + diagnostic_manager. */ + auto_vec m_saved_diagnostics; + public: /* The index of this exploded_node. */ const int m_index; @@ -761,7 +777,7 @@ public: exploded_node *get_or_create_node (const program_point &point, const program_state &state, - const exploded_node *enode_for_diag); + exploded_node *enode_for_diag); exploded_edge *add_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, exploded_edge::custom_info_t *custom = NULL); diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc index bb409d6..675bda9 100644 --- a/gcc/analyzer/feasible-graph.cc +++ b/gcc/analyzer/feasible-graph.cc @@ -79,7 +79,7 @@ base_feasible_node::dump_dot_id (pretty_printer *pp) const void feasible_node::dump_dot (graphviz_out *gv, - const dump_args_t &args) const + const dump_args_t &) const { pretty_printer *pp = gv->get_pp (); @@ -102,8 +102,7 @@ feasible_node::dump_dot (graphviz_out *gv, pp_newline (pp); m_inner_node->dump_processed_stmts (pp); - m_inner_node->dump_saved_diagnostics - (pp, args.m_inner_args.m_eg.get_diagnostic_manager ()); + m_inner_node->dump_saved_diagnostics (pp); pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true); diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index fcea5ce..347cb29 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -959,11 +959,11 @@ program_state::get_current_function () const bool program_state::on_edge (exploded_graph &eg, - const exploded_node &enode, + exploded_node *enode, const superedge *succ) { /* Update state. */ - const program_point &point = enode.get_point (); + const program_point &point = enode->get_point (); const gimple *last_stmt = point.get_supernode ()->get_last_stmt (); /* For conditionals and switch statements, add the @@ -975,8 +975,8 @@ program_state::on_edge (exploded_graph &eg, sm-state transitions (e.g. transitions due to ptrs becoming known to be NULL or non-NULL) */ - impl_region_model_context ctxt (eg, &enode, - &enode.get_state (), + impl_region_model_context ctxt (eg, enode, + &enode->get_state (), this, last_stmt); if (!m_region_model->maybe_update_for_edge (*succ, @@ -991,7 +991,7 @@ program_state::on_edge (exploded_graph &eg, return false; } - program_state::detect_leaks (enode.get_state (), *this, + program_state::detect_leaks (enode->get_state (), *this, NULL, eg.get_ext_state (), &ctxt); @@ -1007,7 +1007,7 @@ program_state::on_edge (exploded_graph &eg, program_state program_state::prune_for_point (exploded_graph &eg, const program_point &point, - const exploded_node *enode_for_diag) const + exploded_node *enode_for_diag) const { logger * const logger = eg.get_logger (); LOG_SCOPE (logger); diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 54fdb5b..71b6f01 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -221,12 +221,12 @@ public: function * get_current_function () const; bool on_edge (exploded_graph &eg, - const exploded_node &enode, + exploded_node *enode, const superedge *succ); program_state prune_for_point (exploded_graph &eg, const program_point &point, - const exploded_node *enode_for_diag) const; + exploded_node *enode_for_diag) const; tree get_representative_tree (const svalue *sval) const; -- cgit v1.1 From f1607029aea3043f7bd4f86c005e0997795f5ffd Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Fri, 2 Apr 2021 00:16:26 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 514fc50..fadf9d3 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,59 @@ +2021-04-01 David Malcolm + + * diagnostic-manager.cc (diagnostic_manager::add_diagnostic): Make + enode param non-constant, and call add_diagnostic on it. Add + enode index to log message. + (diagnostic_manager::add_diagnostic): Make enode param + non-constant. + * diagnostic-manager.h (diagnostic_manager::add_diagnostic): + Likewise for both decls. + * engine.cc + (impl_region_model_context::impl_region_model_context): Likewise + for enode_for_diag. + (impl_sm_context::impl_sm_context): Likewise. + (impl_sm_context::m_enode_for_diag): Likewise. + (exploded_node::dump_dot): Don't pass the diagnostic manager + to dump_saved_diagnostics. + (exploded_node::dump_saved_diagnostics): Drop param. Iterate + directly through all saved diagnostics for the enode, rather + than all saved diagnostics in the diagnostic_manager and + filtering. + (exploded_node::on_stmt): Make non-const. + (exploded_node::on_edge): Likewise. + (exploded_node::on_longjmp): Likewise. + (exploded_node::detect_leaks): Likewise. + (exploded_graph::get_or_create_node): Make enode_for_diag param + non-const. + (exploded_graph_annotator::print_enode): Iterate + directly through all saved diagnostics for the enode, rather + than all saved diagnostics in the diagnostic_manager and + filtering. + * exploded-graph.h + (impl_region_model_context::impl_region_model_context): Make + enode_for_diag param non-constant. + (impl_region_model_context::m_enode_for_diag): Likewise. + (exploded_node::dump_saved_diagnostics): Drop param. + (exploded_node::on_stmt): Make non-const. + (exploded_node::on_edge): Likewise. + (exploded_node::on_longjmp): Likewise. + (exploded_node::detect_leaks): Likewise. + (exploded_node::add_diagnostic): New. + (exploded_node::get_num_diagnostics): New. + (exploded_node::get_saved_diagnostic): New. + (exploded_node::m_saved_diagnostics): New. + (exploded_graph::get_or_create_node): Make enode_for_diag param + non-constant. + * feasible-graph.cc (feasible_node::dump_dot): Drop + diagnostic_manager from call to dump_saved_diagnostics. + * program-state.cc (program_state::on_edge): Convert enode param + to non-const pointer. + (program_state::prune_for_point): Likewise for enode_for_diag + param. + * program-state.h (program_state::on_edge): Convert enode param + to non-const pointer. + (program_state::prune_for_point): Likewise for enode_for_diag + param. + 2021-03-31 David Malcolm PR analyzer/99771 -- cgit v1.1 From 69b66ff02353a87585329bb3cf4ac20d6dee1b16 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 5 Apr 2021 10:48:01 -0400 Subject: analyzer: fix apparent hang with -fanalyzer-verbosity=0 [PR analyzer/99886] The analyzer appeared to enter an infinite loop on malloc-1.c when -fanalyzer-verbosity=0 was used. In fact, it was slowly counting from 0 to 0xffffffff. Root cause is looping up to effectively ((unsigned)0) - 1 in diagnostic_manager::consolidate_conditions when there are no events in the path. Fixed by the following, which uses signed integers when subtracting from path->num_events () when simplifying checker_paths. gcc/analyzer/ChangeLog: PR analyzer/99886 * diagnostic-manager.cc (diagnostic_manager::prune_interproc_events): Use signed integers when subtracting one from path->num_events (). (diagnostic_manager::consolidate_conditions): Likewise. Convert next_idx to a signed int. gcc/testsuite/ChangeLog: PR analyzer/99886 * gcc.dg/analyzer/pr99886.c: New test. --- gcc/analyzer/diagnostic-manager.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 9ec3e89..443ff05 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2081,7 +2081,7 @@ diagnostic_manager::prune_interproc_events (checker_path *path) const do { changed = false; - int idx = path->num_events () - 1; + int idx = (signed)path->num_events () - 1; while (idx >= 0) { /* Prune [..., call, function-entry, return, ...] triples. */ @@ -2200,7 +2200,9 @@ diagnostic_manager::consolidate_conditions (checker_path *path) const if (flag_analyzer_verbose_edges) return; - for (unsigned start_idx = 0; start_idx < path->num_events () - 1; start_idx++) + for (int start_idx = 0; + start_idx < (signed)path->num_events () - 1; + start_idx++) { if (path->cfg_edge_pair_at_p (start_idx)) { @@ -2231,7 +2233,7 @@ diagnostic_manager::consolidate_conditions (checker_path *path) const [start_idx, next_idx) where all apart from the final event are on the same line, and all are either TRUE or FALSE edges, matching the initial. */ - unsigned next_idx = start_idx + 2; + int next_idx = start_idx + 2; while (path->cfg_edge_pair_at_p (next_idx) && same_line_as_p (start_exp_loc, path, next_idx)) { -- cgit v1.1 From 7d8f4240c94e2e7643ac13cda1fdd0bb6ca3a3fb Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 5 Apr 2021 10:51:46 -0400 Subject: analyzer: fix ICE on zero-arg calls passed to __attribute__((nonnull)) [PR 99906] gcc/analyzer/ChangeLog: PR analyzer/99906 * analyzer.cc (maybe_reconstruct_from_def_stmt): Fix NULL dereference on calls with zero arguments. * sm-malloc.cc (malloc_state_machine::on_stmt): When handling __attribute__((nonnull)), only call get_diagnostic_tree if the result will be used. gcc/testsuite/ChangeLog: PR analyzer/99906 * gcc.dg/analyzer/pr99906.c: New test. --- gcc/analyzer/analyzer.cc | 2 +- gcc/analyzer/sm-malloc.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index 2b4cffd..12c03f6 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -148,7 +148,7 @@ maybe_reconstruct_from_def_stmt (tree ssa_name, } return build_call_array_loc (gimple_location (call_stmt), return_type, fn, - num_args, &args[0]); + num_args, args.address ()); } break; } diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index ae03b06..1d5b860 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1600,11 +1600,11 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, if (bitmap_empty_p (nonnull_args) || bitmap_bit_p (nonnull_args, i)) { - tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); state_t state = sm_ctxt->get_state (stmt, arg); /* Can't use a switch as the states are non-const. */ if (unchecked_p (state)) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new possible_null_arg (*this, diag_arg, callee_fndecl, @@ -1616,6 +1616,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, } else if (state == m_null) { + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); sm_ctxt->warn (node, stmt, arg, new null_arg (*this, diag_arg, callee_fndecl, i)); -- cgit v1.1 From b1da991623341a2ecd97bf9034b93b0d63516517 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 6 Apr 2021 00:16:43 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index fadf9d3..68ef2aa 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,21 @@ +2021-04-05 David Malcolm + + PR analyzer/99906 + * analyzer.cc (maybe_reconstruct_from_def_stmt): Fix NULL + dereference on calls with zero arguments. + * sm-malloc.cc (malloc_state_machine::on_stmt): When handling + __attribute__((nonnull)), only call get_diagnostic_tree if the + result will be used. + +2021-04-05 David Malcolm + + PR analyzer/99886 + * diagnostic-manager.cc + (diagnostic_manager::prune_interproc_events): Use signed integers + when subtracting one from path->num_events (). + (diagnostic_manager::consolidate_conditions): Likewise. Convert + next_idx to a signed int. + 2021-04-01 David Malcolm * diagnostic-manager.cc (diagnostic_manager::add_diagnostic): Make -- cgit v1.1 From 3a66c289a3f395e50de79424e1e6f401a4dc1ab7 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 8 Apr 2021 09:46:03 -0400 Subject: analyzer: fix leak false +ves due to maybe-clobbered regions [PR99042,PR99774] Prior to this patch, program_state::detect_leaks worked by finding all live svalues in the old state and in the new state, and calling on_svalue_leak for each svalue that has changed from being live to not being live. PR analyzer/99042 and PR analyzer/99774 both describe false leak diagnostics from -fanalyzer (a false FILE * leak in git, and a false malloc leak in qemu, respectively). In both cases the root cause of the false leak diagnostic relates to svalues no longer being explicitly bound in the store due to regions being conservatively clobbered, due to an unknown function being called, or due to a write through a pointer that could alias the region, respectively. We have a transition from an svalue being explicitly live to not being explicitly live - but only because the store is being conservative, clobbering the binding. The leak detection is looking for transitions from "definitely live" to "not definitely live", when it should be looking for transitions from "definitely live" to "definitely not live". This patch introduces a new class to temporarily capture information about svalues that were explicitly live, but for which a region bound to them got clobbered for conservative reasons. This new "uncertainty_t" class is passed around to capture the data long enough for use in program_state::detect_leaks, where it is used to only complain about svalues that were definitely live and are now both not definitely live *or* possibly-live i.e. definitely not-live. The class also captures for which svalues we can't meaningfully track sm-state anymore, and resets the svalues back to the "start" state. Together, these changes fix the false leak reports. gcc/analyzer/ChangeLog: PR analyzer/99042 PR analyzer/99774 * engine.cc (impl_region_model_context::impl_region_model_context): Add uncertainty param and use it to initialize m_uncertainty. (impl_region_model_context::get_uncertainty): New. (impl_sm_context::get_fndecl_for_call): Add NULL for new uncertainty param when constructing impl_region_model_context. (impl_sm_context::get_state): Likewise. (impl_sm_context::set_next_state): Likewise. (impl_sm_context::warn): Likewise. (exploded_node::on_stmt): Add uncertainty param and use it when constructing impl_region_model_context. (exploded_node::on_edge): Add uncertainty param and pass to on_edge call. (exploded_node::detect_leaks): Create uncertainty_t and pass to impl_region_model_context. (exploded_graph::get_or_create_node): Create uncertainty_t and pass to prune_for_point. (maybe_process_run_of_before_supernode_enodes): Create uncertainty_t and pass to impl_region_model_context. (exploded_graph::process_node): Create uncertainty_t instances and pass around as needed. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Add uncertainty param. (impl_region_model_context::get_uncertainty): New decl. (impl_region_model_context::m_uncertainty): New field. (exploded_node::on_stmt): Add uncertainty param. (exploded_node::on_edge): Likewise. * program-state.cc (sm_state_map::on_liveness_change): Get uncertainty from context and use it to unset sm-state from svalues as appropriate. (program_state::on_edge): Add uncertainty param and use it when constructing impl_region_model_context. Fix indentation. (program_state::prune_for_point): Add uncertainty param and use it when constructing impl_region_model_context. (program_state::detect_leaks): Get any uncertainty from ctxt and use it to get maybe-live svalues for dest_state, rather than definitely-live ones; use this when determining which svalues have leaked. (selftest::test_program_state_merging): Create uncertainty_t and pass to impl_region_model_context. * program-state.h (program_state::on_edge): Add uncertainty param. (program_state::prune_for_point): Likewise. * region-model-impl-calls.cc (call_details::get_uncertainty): New. (region_model::impl_call_memcpy): Pass uncertainty to mark_region_as_unknown call. (region_model::impl_call_memset): Likewise. (region_model::impl_call_strcpy): Likewise. * region-model-reachability.cc (reachable_regions::handle_sval): Also add sval to m_mutable_svals. * region-model.cc (region_model::on_assignment): Pass any uncertainty from ctxt to the store::set_value call. (region_model::handle_unrecognized_call): Get any uncertainty from ctxt and use it to record mutable svalues at the unknown call. (region_model::get_reachable_svalues): Add uncertainty param and use it to mark any maybe-bound svalues as being reachable. (region_model::set_value): Pass any uncertainty from ctxt to the store::set_value call. (region_model::mark_region_as_unknown): Add uncertainty param and pass it on to the store::mark_region_as_unknown call. (region_model::update_for_call_summary): Add uncertainty param and pass it on to the region_model::mark_region_as_unknown call. * region-model.h (call_details::get_uncertainty): New decl. (region_model::get_reachable_svalues): Add uncertainty param. (region_model::mark_region_as_unknown): Add uncertainty param. (region_model_context::get_uncertainty): New vfunc. (noop_region_model_context::get_uncertainty): New vfunc implementation. * store.cc (dump_svalue_set): New. (uncertainty_t::dump_to_pp): New. (uncertainty_t::dump): New. (binding_cluster::clobber_region): Pass NULL for uncertainty to remove_overlapping_bindings. (binding_cluster::mark_region_as_unknown): Add uncertainty param and pass it to remove_overlapping_bindings. (binding_cluster::remove_overlapping_bindings): Add uncertainty param. Use it to record any svalues that were in clobbered bindings. (store::set_value): Add uncertainty param. Pass it to binding_cluster::mark_region_as_unknown when handling symbolic regions. (store::mark_region_as_unknown): Add uncertainty param and pass it to binding_cluster::mark_region_as_unknown. (store::remove_overlapping_bindings): Add uncertainty param and pass it to binding_cluster::remove_overlapping_bindings. * store.h (binding_cluster::mark_region_as_unknown): Add uncertainty param. (binding_cluster::remove_overlapping_bindings): Likewise. (store::set_value): Likewise. (store::mark_region_as_unknown): Likewise. gcc/testsuite/ChangeLog: PR analyzer/99042 PR analyzer/99774 * gcc.dg/analyzer/pr99042.c: New test. * gcc.dg/analyzer/pr99774-1.c: New test. * gcc.dg/analyzer/pr99774-2.c: New test. --- gcc/analyzer/engine.cc | 64 ++++++++++++++-------- gcc/analyzer/exploded-graph.h | 11 +++- gcc/analyzer/program-state.cc | 70 ++++++++++++++++-------- gcc/analyzer/program-state.h | 6 ++- gcc/analyzer/region-model-impl-calls.cc | 14 +++-- gcc/analyzer/region-model-reachability.cc | 1 + gcc/analyzer/region-model.cc | 29 +++++++--- gcc/analyzer/region-model.h | 10 +++- gcc/analyzer/store.cc | 88 ++++++++++++++++++++++++++---- gcc/analyzer/store.h | 89 +++++++++++++++++++++++++++++-- 10 files changed, 309 insertions(+), 73 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 857d434..5b519fd 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -78,6 +78,7 @@ impl_region_model_context (exploded_graph &eg, exploded_node *enode_for_diag, const program_state *old_state, program_state *new_state, + uncertainty_t *uncertainty, const gimple *stmt, stmt_finder *stmt_finder) : m_eg (&eg), m_logger (eg.get_logger ()), @@ -86,20 +87,23 @@ impl_region_model_context (exploded_graph &eg, m_new_state (new_state), m_stmt (stmt), m_stmt_finder (stmt_finder), - m_ext_state (eg.get_ext_state ()) + m_ext_state (eg.get_ext_state ()), + m_uncertainty (uncertainty) { } impl_region_model_context:: impl_region_model_context (program_state *state, const extrinsic_state &ext_state, + uncertainty_t *uncertainty, logger *logger) : m_eg (NULL), m_logger (logger), m_enode_for_diag (NULL), m_old_state (NULL), m_new_state (state), m_stmt (NULL), m_stmt_finder (NULL), - m_ext_state (ext_state) + m_ext_state (ext_state), + m_uncertainty (uncertainty) { } @@ -150,6 +154,12 @@ impl_region_model_context::on_escaped_function (tree fndecl) m_eg->on_escaped_function (fndecl); } +uncertainty_t * +impl_region_model_context::get_uncertainty () +{ + return m_uncertainty; +} + /* struct setjmp_record. */ int @@ -220,7 +230,7 @@ public: { impl_region_model_context old_ctxt (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, - call); + NULL, call); region_model *model = m_new_state->m_region_model; return model->get_fndecl_for_call (call, &old_ctxt); } @@ -232,7 +242,7 @@ public: LOG_FUNC (logger); impl_region_model_context old_ctxt (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, - stmt); + NULL, stmt); const svalue *var_old_sval = m_old_state->m_region_model->get_rvalue (var, &old_ctxt); @@ -250,12 +260,13 @@ public: LOG_FUNC (logger); impl_region_model_context old_ctxt (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, - stmt); + 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, stmt); const svalue *var_new_sval = m_new_state->m_region_model->get_rvalue (var, &new_ctxt); @@ -280,7 +291,7 @@ public: LOG_FUNC (get_logger ()); gcc_assert (d); // take ownership impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL); const svalue *var_old_sval = m_old_state->m_region_model->get_rvalue (var, &old_ctxt); @@ -340,7 +351,7 @@ public: if (!assign_stmt) return NULL_TREE; impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, stmt); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt); if (const svalue *sval = m_new_state->m_region_model->get_gassign_result (assign_stmt, &old_ctxt)) @@ -1116,7 +1127,8 @@ exploded_node::on_stmt_flags exploded_node::on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, - program_state *state) + program_state *state, + uncertainty_t *uncertainty) { logger *logger = eg.get_logger (); LOG_SCOPE (logger); @@ -1140,7 +1152,7 @@ exploded_node::on_stmt (exploded_graph &eg, const program_state old_state (*state); impl_region_model_context ctxt (eg, this, - &old_state, state, + &old_state, state, uncertainty, stmt); bool unknown_side_effects = false; @@ -1300,14 +1312,15 @@ bool exploded_node::on_edge (exploded_graph &eg, const superedge *succ, program_point *next_point, - program_state *next_state) + program_state *next_state, + uncertainty_t *uncertainty) { LOG_FUNC (eg.get_logger ()); if (!next_point->on_edge (eg, succ)) return false; - if (!next_state->on_edge (eg, this, succ)) + if (!next_state->on_edge (eg, this, succ, uncertainty)) return false; return true; @@ -1563,8 +1576,9 @@ exploded_node::detect_leaks (exploded_graph &eg) gcc_assert (new_state.m_region_model); + uncertainty_t uncertainty; impl_region_model_context ctxt (eg, this, - &old_state, &new_state, + &old_state, &new_state, &uncertainty, get_stmt ()); const svalue *result = NULL; new_state.m_region_model->pop_frame (NULL, &result, &ctxt); @@ -2195,8 +2209,9 @@ exploded_graph::get_or_create_node (const program_point &point, /* Prune state to try to improve the chances of a cache hit, avoiding generating redundant nodes. */ + uncertainty_t uncertainty; program_state pruned_state - = state.prune_for_point (*this, point, enode_for_diag); + = state.prune_for_point (*this, point, enode_for_diag, &uncertainty); pruned_state.validate (get_ext_state ()); @@ -2775,8 +2790,10 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) const program_point &iter_point = iter_enode->get_point (); if (const superedge *iter_sedge = iter_point.get_from_edge ()) { + uncertainty_t uncertainty; impl_region_model_context ctxt (*this, iter_enode, - &state, next_state, NULL); + &state, next_state, + &uncertainty, NULL); const cfg_superedge *last_cfg_superedge = iter_sedge->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -2950,11 +2967,13 @@ exploded_graph::process_node (exploded_node *node) case PK_BEFORE_SUPERNODE: { program_state next_state (state); + uncertainty_t uncertainty; if (point.get_from_edge ()) { impl_region_model_context ctxt (*this, node, - &state, &next_state, NULL); + &state, &next_state, + &uncertainty, NULL); const cfg_superedge *last_cfg_superedge = point.get_from_edge ()->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -2992,6 +3011,7 @@ exploded_graph::process_node (exploded_node *node) the sm-state-change occurs on an edge where the src enode has exactly one stmt, the one that caused the change. */ program_state next_state (state); + uncertainty_t uncertainty; const supernode *snode = point.get_supernode (); unsigned stmt_idx; const gimple *prev_stmt = NULL; @@ -3013,7 +3033,7 @@ exploded_graph::process_node (exploded_node *node) /* Process the stmt. */ exploded_node::on_stmt_flags flags - = node->on_stmt (*this, snode, stmt, &next_state); + = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty); node->m_num_processed_stmts++; /* If flags.m_terminate_path, stop analyzing; any nodes/edges @@ -3024,7 +3044,8 @@ exploded_graph::process_node (exploded_node *node) if (next_state.m_region_model) { impl_region_model_context ctxt (*this, node, - &old_state, &next_state, stmt); + &old_state, &next_state, + &uncertainty, stmt); program_state::detect_leaks (old_state, next_state, NULL, get_ext_state (), &ctxt); } @@ -3036,7 +3057,8 @@ exploded_graph::process_node (exploded_node *node) point.get_call_string ()) : program_point::after_supernode (point.get_supernode (), point.get_call_string ())); - next_state = next_state.prune_for_point (*this, next_point, node); + next_state = next_state.prune_for_point (*this, next_point, node, + &uncertainty); if (flags.m_sm_changes || flag_analyzer_fine_grained) { @@ -3128,15 +3150,15 @@ exploded_graph::process_node (exploded_node *node) = program_point::before_supernode (succ->m_dest, succ, point.get_call_string ()); program_state next_state (state); - - if (!node->on_edge (*this, succ, &next_point, &next_state)) + uncertainty_t uncertainty; + if (!node->on_edge (*this, succ, &next_point, &next_state, + &uncertainty)) { if (logger) logger->log ("skipping impossible edge to SN: %i", succ->m_dest->m_index); continue; } - exploded_node *next = get_or_create_node (next_point, next_state, node); if (next) diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 2566641..c67f7b7 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -36,12 +36,14 @@ class impl_region_model_context : public region_model_context old state, rather than the new? */ const program_state *old_state, program_state *new_state, + uncertainty_t *uncertainty, const gimple *stmt, stmt_finder *stmt_finder = NULL); impl_region_model_context (program_state *state, const extrinsic_state &ext_state, + uncertainty_t *uncertainty, logger *logger = NULL); void warn (pending_diagnostic *d) FINAL OVERRIDE; @@ -68,6 +70,8 @@ class impl_region_model_context : public region_model_context void on_escaped_function (tree fndecl) FINAL OVERRIDE; + uncertainty_t *get_uncertainty () FINAL OVERRIDE; + exploded_graph *m_eg; log_user m_logger; exploded_node *m_enode_for_diag; @@ -76,6 +80,7 @@ class impl_region_model_context : public region_model_context const gimple *m_stmt; stmt_finder *m_stmt_finder; const extrinsic_state &m_ext_state; + uncertainty_t *m_uncertainty; }; /* A pair, used internally by @@ -226,11 +231,13 @@ class exploded_node : public dnode on_stmt_flags on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, - program_state *state); + program_state *state, + uncertainty_t *uncertainty); bool on_edge (exploded_graph &eg, const superedge *succ, program_point *next_point, - program_state *next_state); + program_state *next_state, + uncertainty_t *uncertainty); void on_longjmp (exploded_graph &eg, const gcall *call, program_state *new_state, diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 347cb29..f809462 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -516,6 +516,7 @@ sm_state_map::on_liveness_change (const svalue_set &live_svalues, impl_region_model_context *ctxt) { svalue_set svals_to_unset; + uncertainty_t *uncertainty = ctxt->get_uncertainty (); auto_vec leaked_svals (m_map.elements ()); for (map_t::iterator iter = m_map.begin (); @@ -530,6 +531,9 @@ sm_state_map::on_liveness_change (const svalue_set &live_svalues, if (!m_sm.can_purge_p (e.m_state)) leaked_svals.quick_push (iter_sval); } + if (uncertainty) + if (uncertainty->unknown_sm_state_p (iter_sval)) + svals_to_unset.add (iter_sval); } leaked_svals.qsort (svalue::cmp_ptr_ptr); @@ -960,7 +964,8 @@ program_state::get_current_function () const bool program_state::on_edge (exploded_graph &eg, exploded_node *enode, - const superedge *succ) + const superedge *succ, + uncertainty_t *uncertainty) { /* Update state. */ const program_point &point = enode->get_point (); @@ -978,6 +983,7 @@ program_state::on_edge (exploded_graph &eg, impl_region_model_context ctxt (eg, enode, &enode->get_state (), this, + uncertainty, last_stmt); if (!m_region_model->maybe_update_for_edge (*succ, last_stmt, @@ -992,8 +998,8 @@ program_state::on_edge (exploded_graph &eg, } program_state::detect_leaks (enode->get_state (), *this, - NULL, eg.get_ext_state (), - &ctxt); + NULL, eg.get_ext_state (), + &ctxt); return true; } @@ -1007,7 +1013,8 @@ program_state::on_edge (exploded_graph &eg, program_state program_state::prune_for_point (exploded_graph &eg, const program_point &point, - exploded_node *enode_for_diag) const + exploded_node *enode_for_diag, + uncertainty_t *uncertainty) const { logger * const logger = eg.get_logger (); LOG_SCOPE (logger); @@ -1071,6 +1078,7 @@ program_state::prune_for_point (exploded_graph &eg, impl_region_model_context ctxt (eg, enode_for_diag, this, &new_state, + uncertainty, point.get_stmt ()); detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt); } @@ -1189,6 +1197,7 @@ program_state::detect_leaks (const program_state &src_state, { logger *logger = ext_state.get_logger (); LOG_SCOPE (logger); + const uncertainty_t *uncertainty = ctxt->get_uncertainty (); if (logger) { pretty_printer *pp = logger->get_printer (); @@ -1207,31 +1216,46 @@ program_state::detect_leaks (const program_state &src_state, extra_sval->dump_to_pp (pp, true); logger->end_log_line (); } + if (uncertainty) + { + logger->start_log_line (); + pp_string (pp, "uncertainty: "); + uncertainty->dump_to_pp (pp, true); + logger->end_log_line (); + } } - /* Get svalues reachable from each of src_state and dst_state. */ - svalue_set src_svalues; - svalue_set dest_svalues; - src_state.m_region_model->get_reachable_svalues (&src_svalues, NULL); - dest_state.m_region_model->get_reachable_svalues (&dest_svalues, extra_sval); + /* Get svalues reachable from each of src_state and dest_state. + Get svalues *known* to be reachable in src_state. + Pass in uncertainty for dest_state so that we additionally get svalues that + *might* still be reachable in dst_state. */ + svalue_set known_src_svalues; + src_state.m_region_model->get_reachable_svalues (&known_src_svalues, + NULL, NULL); + svalue_set maybe_dest_svalues; + dest_state.m_region_model->get_reachable_svalues (&maybe_dest_svalues, + extra_sval, uncertainty); if (logger) { - log_set_of_svalues (logger, "src_state reachable svalues:", src_svalues); - log_set_of_svalues (logger, "dest_state reachable svalues:", - dest_svalues); + log_set_of_svalues (logger, "src_state known reachable svalues:", + known_src_svalues); + log_set_of_svalues (logger, "dest_state maybe reachable svalues:", + maybe_dest_svalues); } - auto_vec dead_svals (src_svalues.elements ()); - for (svalue_set::iterator iter = src_svalues.begin (); - iter != src_svalues.end (); ++iter) + auto_vec dead_svals (known_src_svalues.elements ()); + for (svalue_set::iterator iter = known_src_svalues.begin (); + iter != known_src_svalues.end (); ++iter) { const svalue *sval = (*iter); /* For each sval reachable from SRC_STATE, determine if it is - live in DEST_STATE: either explicitly reachable, or implicitly - live based on the set of explicitly reachable svalues. - Record those that have ceased to be live. */ - if (!sval->live_p (&dest_svalues, dest_state.m_region_model)) + live in DEST_STATE: either explicitly reachable, implicitly + live based on the set of explicitly reachable svalues, + or possibly reachable as recorded in uncertainty. + Record those that have ceased to be live i.e. were known + to be live, and are now not known to be even possibly-live. */ + if (!sval->live_p (&maybe_dest_svalues, dest_state.m_region_model)) dead_svals.quick_push (sval); } @@ -1244,11 +1268,12 @@ program_state::detect_leaks (const program_state &src_state, ctxt->on_svalue_leak (sval); /* Purge dead svals from sm-state. */ - ctxt->on_liveness_change (dest_svalues, dest_state.m_region_model); + ctxt->on_liveness_change (maybe_dest_svalues, + dest_state.m_region_model); /* Purge dead svals from constraints. */ dest_state.m_region_model->get_constraints ()->on_liveness_change - (dest_svalues, dest_state.m_region_model); + (maybe_dest_svalues, dest_state.m_region_model); } #if CHECKING_P @@ -1456,7 +1481,8 @@ test_program_state_merging () region_model_manager *mgr = eng.get_model_manager (); program_state s0 (ext_state); - impl_region_model_context ctxt (&s0, ext_state); + uncertainty_t uncertainty; + impl_region_model_context ctxt (&s0, ext_state, &uncertainty); region_model *model0 = s0.m_region_model; const svalue *size_in_bytes diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 71b6f01..898c57ff 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -222,11 +222,13 @@ public: bool on_edge (exploded_graph &eg, exploded_node *enode, - const superedge *succ); + const superedge *succ, + uncertainty_t *uncertainty); program_state prune_for_point (exploded_graph &eg, const program_point &point, - exploded_node *enode_for_diag) const; + exploded_node *enode_for_diag, + uncertainty_t *uncertainty) const; tree get_representative_tree (const svalue *sval) const; diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index f83c12b..4052bb39 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -79,6 +79,14 @@ call_details::call_details (const gcall *call, region_model *model, } } +/* Get any uncertainty_t associated with the region_model_context. */ + +uncertainty_t * +call_details::get_uncertainty () const +{ + return m_ctxt->get_uncertainty (); +} + /* If the callsite has a left-hand-side region, set it to RESULT and return true. Otherwise do nothing and return false. */ @@ -346,7 +354,7 @@ region_model::impl_call_memcpy (const call_details &cd) check_for_writable_region (dest_reg, cd.get_ctxt ()); /* Otherwise, mark region's contents as unknown. */ - mark_region_as_unknown (dest_reg); + mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); } /* Handle the on_call_pre part of "memset" and "__builtin_memset". */ @@ -389,7 +397,7 @@ region_model::impl_call_memset (const call_details &cd) check_for_writable_region (dest_reg, cd.get_ctxt ()); /* Otherwise, mark region's contents as unknown. */ - mark_region_as_unknown (dest_reg); + mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); return false; } @@ -453,7 +461,7 @@ region_model::impl_call_strcpy (const call_details &cd) check_for_writable_region (dest_reg, cd.get_ctxt ()); /* For now, just mark region's contents as unknown. */ - mark_region_as_unknown (dest_reg); + mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); } /* Handle the on_call_pre part of "strlen". diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc index 087185b..e165cda 100644 --- a/gcc/analyzer/region-model-reachability.cc +++ b/gcc/analyzer/region-model-reachability.cc @@ -170,6 +170,7 @@ void reachable_regions::handle_sval (const svalue *sval) { m_reachable_svals.add (sval); + m_mutable_svals.add (sval); if (const region_svalue *ptr = sval->dyn_cast_region_svalue ()) { const region *pointee = ptr->get_pointee (); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index c245bfe..2d3880b 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -726,7 +726,7 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt) access will "inherit" the individual chars. */ const svalue *rhs_sval = get_rvalue (rhs1, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - BK_default); + BK_default, ctxt->get_uncertainty ()); } break; } @@ -1003,6 +1003,8 @@ region_model::handle_unrecognized_call (const gcall *call, } } + uncertainty_t *uncertainty = ctxt->get_uncertainty (); + /* Purge sm-state for the svalues that were reachable, both in non-mutable and mutable form. */ for (svalue_set::iterator iter @@ -1018,6 +1020,8 @@ region_model::handle_unrecognized_call (const gcall *call, { const svalue *sval = (*iter); ctxt->on_unknown_change (sval, true); + if (uncertainty) + uncertainty->on_mutable_sval_at_unknown_call (sval); } /* Mark any clusters that have escaped. */ @@ -1035,11 +1039,15 @@ region_model::handle_unrecognized_call (const gcall *call, for reachability (for handling return values from functions when analyzing return of the only function on the stack). + If UNCERTAINTY is non-NULL, treat any svalues that were recorded + within it as being maybe-bound as additional "roots" for reachability. + Find svalues that haven't leaked. */ void region_model::get_reachable_svalues (svalue_set *out, - const svalue *extra_sval) + const svalue *extra_sval, + const uncertainty_t *uncertainty) { reachable_regions reachable_regs (this); @@ -1051,6 +1059,12 @@ region_model::get_reachable_svalues (svalue_set *out, if (extra_sval) reachable_regs.handle_sval (extra_sval); + if (uncertainty) + for (uncertainty_t::iterator iter + = uncertainty->begin_maybe_bound_svals (); + iter != uncertainty->end_maybe_bound_svals (); ++iter) + reachable_regs.handle_sval (*iter); + /* Get regions for locals that have explicitly bound values. */ for (store::cluster_map_t::iterator iter = m_store.begin (); iter != m_store.end (); ++iter) @@ -1798,7 +1812,7 @@ region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, check_for_writable_region (lhs_reg, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - BK_direct); + BK_direct, ctxt ? ctxt->get_uncertainty () : NULL); } /* Set the value of the region given by LHS to the value given by RHS. */ @@ -1840,9 +1854,11 @@ region_model::zero_fill_region (const region *reg) /* Mark REG as having unknown content. */ void -region_model::mark_region_as_unknown (const region *reg) +region_model::mark_region_as_unknown (const region *reg, + uncertainty_t *uncertainty) { - m_store.mark_region_as_unknown (m_mgr->get_store_manager(), reg); + m_store.mark_region_as_unknown (m_mgr->get_store_manager(), reg, + uncertainty); } /* Determine what is known about the condition "LHS_SVAL OP RHS_SVAL" within @@ -2666,7 +2682,8 @@ region_model::update_for_call_summary (const callgraph_superedge &cg_sedge, const gcall *call_stmt = cg_sedge.get_call_stmt (); tree lhs = gimple_call_lhs (call_stmt); if (lhs) - mark_region_as_unknown (get_lvalue (lhs, ctxt)); + mark_region_as_unknown (get_lvalue (lhs, ctxt), + ctxt ? ctxt->get_uncertainty () : NULL); // TODO: actually implement some kind of summary here } diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 54977f9..4123500 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -378,6 +378,7 @@ public: region_model_context *ctxt); region_model_context *get_ctxt () const { return m_ctxt; } + uncertainty_t *get_uncertainty () const; tree get_lhs_type () const { return m_lhs_type; } const region *get_lhs_region () const { return m_lhs_region; } @@ -474,7 +475,8 @@ class region_model void handle_unrecognized_call (const gcall *call, region_model_context *ctxt); void get_reachable_svalues (svalue_set *out, - const svalue *extra_sval); + const svalue *extra_sval, + const uncertainty_t *uncertainty); void on_return (const greturn *stmt, region_model_context *ctxt); void on_setjmp (const gcall *stmt, const exploded_node *enode, @@ -518,7 +520,7 @@ class region_model void clobber_region (const region *reg); void purge_region (const region *reg); void zero_fill_region (const region *reg); - void mark_region_as_unknown (const region *reg); + void mark_region_as_unknown (const region *reg, uncertainty_t *uncertainty); void copy_region (const region *dst_reg, const region *src_reg, region_model_context *ctxt); @@ -698,6 +700,8 @@ class region_model_context /* Hook for clients to be notified when a function_decl escapes. */ virtual void on_escaped_function (tree fndecl) = 0; + + virtual uncertainty_t *get_uncertainty () = 0; }; /* A "do nothing" subclass of region_model_context. */ @@ -726,6 +730,8 @@ public: void on_unexpected_tree_code (tree, const dump_location_t &) OVERRIDE {} void on_escaped_function (tree) OVERRIDE {} + + uncertainty_t *get_uncertainty () OVERRIDE { return NULL; } }; /* A subclass of region_model_context for determining if operations fail diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index 53b6e21..b1874a5 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -63,6 +63,61 @@ along with GCC; see the file COPYING3. If not see namespace ana { +/* Dump SVALS to PP, sorting them to ensure determinism. */ + +static void +dump_svalue_set (const hash_set &svals, + pretty_printer *pp, bool simple) +{ + auto_vec v; + for (hash_set::iterator iter = svals.begin (); + iter != svals.end (); ++iter) + { + v.safe_push (*iter); + } + v.qsort (svalue::cmp_ptr_ptr); + + pp_character (pp, '{'); + const svalue *sval; + unsigned i; + FOR_EACH_VEC_ELT (v, i, sval) + { + if (i > 0) + pp_string (pp, ", "); + sval->dump_to_pp (pp, simple); + } + pp_character (pp, '}'); +} + +/* class uncertainty_t. */ + +/* Dump this object to PP. */ + +void +uncertainty_t::dump_to_pp (pretty_printer *pp, bool simple) const +{ + pp_string (pp, "{m_maybe_bound_svals: "); + dump_svalue_set (m_maybe_bound_svals, pp, simple); + + pp_string (pp, ", m_mutable_at_unknown_call_svals: "); + dump_svalue_set (m_mutable_at_unknown_call_svals, pp, simple); + pp_string (pp, "}"); +} + +/* Dump this object to stderr. */ + +DEBUG_FUNCTION void +uncertainty_t::dump (bool simple) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, simple); + pp_newline (&pp); + pp_flush (&pp); +} + /* Get a human-readable string for KIND for dumps. */ const char *binding_kind_to_string (enum binding_kind kind) @@ -876,7 +931,7 @@ binding_cluster::bind_compound_sval (store_manager *mgr, void binding_cluster::clobber_region (store_manager *mgr, const region *reg) { - remove_overlapping_bindings (mgr, reg); + remove_overlapping_bindings (mgr, reg, NULL); } /* Remove any bindings for REG within this cluster. */ @@ -913,13 +968,16 @@ binding_cluster::zero_fill_region (store_manager *mgr, const region *reg) m_touched = false; } -/* Mark REG within this cluster as being unknown. */ +/* Mark REG within this cluster as being unknown. + If UNCERTAINTY is non-NULL, use it to record any svalues that + had bindings to them removed, as being maybe-bound. */ void binding_cluster::mark_region_as_unknown (store_manager *mgr, - const region *reg) + const region *reg, + uncertainty_t *uncertainty) { - remove_overlapping_bindings (mgr, reg); + remove_overlapping_bindings (mgr, reg, uncertainty); /* Add a default binding to "unknown". */ region_model_manager *sval_mgr = mgr->get_svalue_manager (); @@ -1143,11 +1201,14 @@ binding_cluster::get_overlapping_bindings (store_manager *mgr, /* Remove any bindings within this cluster that overlap REG, but retain default bindings that overlap but aren't fully covered - by REG. */ + by REG. + If UNCERTAINTY is non-NULL, use it to record any svalues that + were removed, as being maybe-bound. */ void binding_cluster::remove_overlapping_bindings (store_manager *mgr, - const region *reg) + const region *reg, + uncertainty_t *uncertainty) { auto_vec bindings; get_overlapping_bindings (mgr, reg, &bindings); @@ -1165,6 +1226,8 @@ binding_cluster::remove_overlapping_bindings (store_manager *mgr, if (reg_binding != iter_binding) continue; } + if (uncertainty) + uncertainty->on_maybe_bound_sval (m_map.get (iter_binding)); m_map.remove (iter_binding); } } @@ -1826,7 +1889,8 @@ store::get_any_binding (store_manager *mgr, const region *reg) const void store::set_value (store_manager *mgr, const region *lhs_reg, - const svalue *rhs_sval, enum binding_kind kind) + const svalue *rhs_sval, enum binding_kind kind, + uncertainty_t *uncertainty) { remove_overlapping_bindings (mgr, lhs_reg); @@ -1880,7 +1944,8 @@ store::set_value (store_manager *mgr, const region *lhs_reg, gcc_unreachable (); case tristate::TS_UNKNOWN: - iter_cluster->mark_region_as_unknown (mgr, iter_base_reg); + iter_cluster->mark_region_as_unknown (mgr, iter_base_reg, + uncertainty); break; case tristate::TS_TRUE: @@ -2021,13 +2086,14 @@ store::zero_fill_region (store_manager *mgr, const region *reg) /* Mark REG as having unknown content. */ void -store::mark_region_as_unknown (store_manager *mgr, const region *reg) +store::mark_region_as_unknown (store_manager *mgr, const region *reg, + uncertainty_t *uncertainty) { const region *base_reg = reg->get_base_region (); if (base_reg->symbolic_for_unknown_ptr_p ()) return; binding_cluster *cluster = get_or_create_cluster (base_reg); - cluster->mark_region_as_unknown (mgr, reg); + cluster->mark_region_as_unknown (mgr, reg, uncertainty); } /* Get the cluster for BASE_REG, or NULL (const version). */ @@ -2238,7 +2304,7 @@ store::remove_overlapping_bindings (store_manager *mgr, const region *reg) delete cluster; return; } - cluster->remove_overlapping_bindings (mgr, reg); + cluster->remove_overlapping_bindings (mgr, reg, NULL); } } diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index 2bcef6c..dc22d96 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -119,6 +119,83 @@ along with GCC; see the file COPYING3. If not see namespace ana { +/* A class for keeping track of aspects of a program_state that we don't + know about, to avoid false positives about leaks. + + Consider: + + p->field = malloc (1024); + q->field = NULL; + + where we don't know whether or not p and q point to the same memory, + and: + + p->field = malloc (1024); + unknown_fn (p); + + In both cases, the svalue for the address of the allocated buffer + goes from being bound to p->field to not having anything explicitly bound + to it. + + Given that we conservatively discard bindings due to possible aliasing or + calls to unknown function, the store loses references to svalues, + but these svalues could still be live. We don't want to warn about + them leaking - they're effectively in a "maybe live" state. + + This "maybe live" information is somewhat transient. + + We don't want to store this "maybe live" information in the program_state, + region_model, or store, since we don't want to bloat these objects (and + potentially bloat the exploded_graph with more nodes). + However, we can't store it in the region_model_context, as these context + objects sometimes don't last long enough to be around when comparing the + old vs the new state. + + This class is a way to track a set of such svalues, so that we can + temporarily capture that they are in a "maybe live" state whilst + comparing old and new states. */ + +class uncertainty_t +{ +public: + typedef hash_set::iterator iterator; + + void on_maybe_bound_sval (const svalue *sval) + { + m_maybe_bound_svals.add (sval); + } + void on_mutable_sval_at_unknown_call (const svalue *sval) + { + m_mutable_at_unknown_call_svals.add (sval); + } + + bool unknown_sm_state_p (const svalue *sval) + { + return (m_maybe_bound_svals.contains (sval) + || m_mutable_at_unknown_call_svals.contains (sval)); + } + + void dump_to_pp (pretty_printer *pp, bool simple) const; + void dump (bool simple) const; + + iterator begin_maybe_bound_svals () const + { + return m_maybe_bound_svals.begin (); + } + iterator end_maybe_bound_svals () const + { + return m_maybe_bound_svals.end (); + } + +private: + + /* svalues that might or might not still be bound. */ + hash_set m_maybe_bound_svals; + + /* svalues that have mutable sm-state at unknown calls. */ + hash_set m_mutable_at_unknown_call_svals; +}; + class concrete_binding; /* An enum for discriminating between "direct" vs "default" levels of @@ -409,7 +486,8 @@ public: void clobber_region (store_manager *mgr, const region *reg); void purge_region (store_manager *mgr, const region *reg); void zero_fill_region (store_manager *mgr, const region *reg); - void mark_region_as_unknown (store_manager *mgr, const region *reg); + void mark_region_as_unknown (store_manager *mgr, const region *reg, + uncertainty_t *uncertainty); const svalue *get_binding (store_manager *mgr, const region *reg, binding_kind kind) const; @@ -421,7 +499,8 @@ public: const svalue *maybe_get_compound_binding (store_manager *mgr, const region *reg) const; - void remove_overlapping_bindings (store_manager *mgr, const region *reg); + void remove_overlapping_bindings (store_manager *mgr, const region *reg, + uncertainty_t *uncertainty); template void for_each_value (void (*cb) (const svalue *sval, T user_data), @@ -539,11 +618,13 @@ public: bool called_unknown_fn_p () const { return m_called_unknown_fn; } void set_value (store_manager *mgr, const region *lhs_reg, - const svalue *rhs_sval, enum binding_kind kind); + const svalue *rhs_sval, enum binding_kind kind, + uncertainty_t *uncertainty); void clobber_region (store_manager *mgr, const region *reg); void purge_region (store_manager *mgr, const region *reg); void zero_fill_region (store_manager *mgr, const region *reg); - void mark_region_as_unknown (store_manager *mgr, const region *reg); + void mark_region_as_unknown (store_manager *mgr, const region *reg, + uncertainty_t *uncertainty); const binding_cluster *get_cluster (const region *base_reg) const; binding_cluster *get_cluster (const region *base_reg); -- cgit v1.1 From 019a922063f26784d5a070d9198a1f937b8a8343 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Fri, 9 Apr 2021 00:16:56 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 68ef2aa..bccd9db 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,97 @@ +2021-04-08 David Malcolm + + PR analyzer/99042 + PR analyzer/99774 + * engine.cc + (impl_region_model_context::impl_region_model_context): Add + uncertainty param and use it to initialize m_uncertainty. + (impl_region_model_context::get_uncertainty): New. + (impl_sm_context::get_fndecl_for_call): Add NULL for new + uncertainty param when constructing impl_region_model_context. + (impl_sm_context::get_state): Likewise. + (impl_sm_context::set_next_state): Likewise. + (impl_sm_context::warn): Likewise. + (exploded_node::on_stmt): Add uncertainty param + and use it when constructing impl_region_model_context. + (exploded_node::on_edge): Add uncertainty param and pass + to on_edge call. + (exploded_node::detect_leaks): Create uncertainty_t and pass to + impl_region_model_context. + (exploded_graph::get_or_create_node): Create uncertainty_t and + pass to prune_for_point. + (maybe_process_run_of_before_supernode_enodes): Create + uncertainty_t and pass to impl_region_model_context. + (exploded_graph::process_node): Create uncertainty_t instances and + pass around as needed. + * exploded-graph.h + (impl_region_model_context::impl_region_model_context): Add + uncertainty param. + (impl_region_model_context::get_uncertainty): New decl. + (impl_region_model_context::m_uncertainty): New field. + (exploded_node::on_stmt): Add uncertainty param. + (exploded_node::on_edge): Likewise. + * program-state.cc (sm_state_map::on_liveness_change): Get + uncertainty from context and use it to unset sm-state from + svalues as appropriate. + (program_state::on_edge): Add uncertainty param and use it when + constructing impl_region_model_context. Fix indentation. + (program_state::prune_for_point): Add uncertainty param and use it + when constructing impl_region_model_context. + (program_state::detect_leaks): Get any uncertainty from ctxt and + use it to get maybe-live svalues for dest_state, rather than + definitely-live ones; use this when determining which svalues + have leaked. + (selftest::test_program_state_merging): Create uncertainty_t and + pass to impl_region_model_context. + * program-state.h (program_state::on_edge): Add uncertainty param. + (program_state::prune_for_point): Likewise. + * region-model-impl-calls.cc (call_details::get_uncertainty): New. + (region_model::impl_call_memcpy): Pass uncertainty to + mark_region_as_unknown call. + (region_model::impl_call_memset): Likewise. + (region_model::impl_call_strcpy): Likewise. + * region-model-reachability.cc (reachable_regions::handle_sval): + Also add sval to m_mutable_svals. + * region-model.cc (region_model::on_assignment): Pass any + uncertainty from ctxt to the store::set_value call. + (region_model::handle_unrecognized_call): Get any uncertainty from + ctxt and use it to record mutable svalues at the unknown call. + (region_model::get_reachable_svalues): Add uncertainty param and + use it to mark any maybe-bound svalues as being reachable. + (region_model::set_value): Pass any uncertainty from ctxt to the + store::set_value call. + (region_model::mark_region_as_unknown): Add uncertainty param and + pass it on to the store::mark_region_as_unknown call. + (region_model::update_for_call_summary): Add uncertainty param and + pass it on to the region_model::mark_region_as_unknown call. + * region-model.h (call_details::get_uncertainty): New decl. + (region_model::get_reachable_svalues): Add uncertainty param. + (region_model::mark_region_as_unknown): Add uncertainty param. + (region_model_context::get_uncertainty): New vfunc. + (noop_region_model_context::get_uncertainty): New vfunc + implementation. + * store.cc (dump_svalue_set): New. + (uncertainty_t::dump_to_pp): New. + (uncertainty_t::dump): New. + (binding_cluster::clobber_region): Pass NULL for uncertainty to + remove_overlapping_bindings. + (binding_cluster::mark_region_as_unknown): Add uncertainty param + and pass it to remove_overlapping_bindings. + (binding_cluster::remove_overlapping_bindings): Add uncertainty param. + Use it to record any svalues that were in clobbered bindings. + (store::set_value): Add uncertainty param. Pass it to + binding_cluster::mark_region_as_unknown when handling symbolic + regions. + (store::mark_region_as_unknown): Add uncertainty param and pass it + to binding_cluster::mark_region_as_unknown. + (store::remove_overlapping_bindings): Add uncertainty param and + pass it to binding_cluster::remove_overlapping_bindings. + * store.h (binding_cluster::mark_region_as_unknown): Add + uncertainty param. + (binding_cluster::remove_overlapping_bindings): Likewise. + (store::set_value): Likewise. + (store::mark_region_as_unknown): Likewise. + 2021-04-05 David Malcolm PR analyzer/99906 -- cgit v1.1 From ec633d3777bd71f7bde5e671b61ec18e5b7b43ea Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Sat, 10 Apr 2021 16:23:23 -0400 Subject: analyzer: fix ICE on assignment from STRING_CST when building path [PR100011] gcc/analyzer/ChangeLog: PR analyzer/100011 * region-model.cc (region_model::on_assignment): Avoid NULL dereference if ctxt is NULL when assigning from a STRING_CST. gcc/testsuite/ChangeLog: PR analyzer/100011 * gcc.dg/analyzer/pr100011.c: New test. --- gcc/analyzer/region-model.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 2d3880b..c7038dd 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -726,7 +726,7 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt) access will "inherit" the individual chars. */ const svalue *rhs_sval = get_rvalue (rhs1, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - BK_default, ctxt->get_uncertainty ()); + BK_default, ctxt ? ctxt->get_uncertainty () : NULL); } break; } -- cgit v1.1 From 1d54b13841774aa40f5d0a5ab87b19e7e1276d42 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sun, 11 Apr 2021 00:16:24 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index bccd9db..0e2e7e5 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,9 @@ +2021-04-10 David Malcolm + + PR analyzer/100011 + * region-model.cc (region_model::on_assignment): Avoid NULL + dereference if ctxt is NULL when assigning from a STRING_CST. + 2021-04-08 David Malcolm PR analyzer/99042 -- cgit v1.1 From 17f3c2b8ac477b07ca0aafbc7d74ba305dc1ee33 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 12 Apr 2021 21:13:40 -0400 Subject: gimple UIDs, LTO and -fanalyzer [PR98599] gimple.h has this comment for gimple's uid field: /* UID of this statement. This is used by passes that want to assign IDs to statements. It must be assigned and used by each pass. By default it should be assumed to contain garbage. */ unsigned uid; and gimple_set_uid has: Please note that this UID property is supposed to be undefined at pass boundaries. This means that a given pass should not assume it contains any useful value when the pass starts and thus can set it to any value it sees fit. which suggests that any pass can use the uid field as an arbitrary scratch space. PR analyzer/98599 reports a case where this error occurs in LTO mode: fatal error: Cgraph edge statement index out of range on certain inputs with -fanalyzer. The error occurs in the LTRANS phase after -fanalyzer runs in the WPA phase. The analyzer pass writes to the uid fields of all stmts. The error occurs when LTRANS is streaming callgraph edges back in. The LTO format uses stmt uids to associate call stmts with callgraph edges between WPA and LTRANS. For example, in lto-cgraph.c, lto_output_edge writes out the gimple_uid, and input_edge reads it back in. lto_prepare_function_for_streaming has code to renumber the stmt UIDs when the code is streamed back out, but for some reason this isn't called for clones: 307 /* Do body modifications needed for streaming before we fork out 308 worker processes. */ 309 FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) 310 if (!node->clone_of && gimple_has_body_p (node->decl)) 311 lto_prepare_function_for_streaming (node); Hence the combination of -fanalyzer and -flto will fail in LTRANS's stream-in if any function clones are encountered. It's not fully clear to me why this isn't done for clones, and what the correct fix should be to allow arbitrary changes to uids within WPA passes. In the meantime, this patch works around the issue by updating the analyzer to save and restore the UIDs, fixing the error. gcc/analyzer/ChangeLog: PR analyzer/98599 * supergraph.cc (saved_uids::make_uid_unique): New. (saved_uids::restore_uids): New. (supergraph::supergraph): Replace assignments to stmt->uid with calls to m_stmt_uids.make_uid_unique. (supergraph::~supergraph): New. * supergraph.h (class saved_uids): New. (supergraph::~supergraph): New decl. (supergraph::m_stmt_uids): New field. gcc/testsuite/ChangeLog: PR analyzer/98599 * gcc.dg/analyzer/pr98599-a.c: New test. * gcc.dg/analyzer/pr98599-b.c: New test. --- gcc/analyzer/supergraph.cc | 57 ++++++++++++++++++++++++++++++++++++++++++---- gcc/analyzer/supergraph.h | 15 ++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 4b93456..8611d0f 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -87,6 +87,50 @@ supergraph_call_edge (function *fun, gimple *stmt) return edge; } +/* class saved_uids. + + In order to ensure consistent results without relying on the ordering + of pointer values we assign a uid to each gimple stmt, globally unique + across all functions. + + Normally, the stmt uids are a scratch space that each pass can freely + assign its own values to. However, in the case of LTO, the uids are + used to associate call stmts with callgraph edges between the WPA phase + (where the analyzer runs in LTO mode) and the LTRANS phase; if the + analyzer changes them in the WPA phase, it leads to errors when + streaming the code back in at LTRANS. + lto_prepare_function_for_streaming has code to renumber the stmt UIDs + when the code is streamed back out, but for some reason this isn't + called for clones. + + Hence, as a workaround, this class has responsibility for tracking + the original uids and restoring them once the pass is complete + (in the supergraph dtor). */ + +/* Give STMT a globally unique uid, storing its original uid so it can + later be restored. */ + +void +saved_uids::make_uid_unique (gimple *stmt) +{ + unsigned next_uid = m_old_stmt_uids.length (); + unsigned old_stmt_uid = stmt->uid; + stmt->uid = next_uid; + m_old_stmt_uids.safe_push + (std::pair (stmt, old_stmt_uid)); +} + +/* Restore the saved uids of all stmts. */ + +void +saved_uids::restore_uids () const +{ + unsigned i; + std::pair *pair; + FOR_EACH_VEC_ELT (m_old_stmt_uids, i, pair) + pair->first->uid = pair->second; +} + /* supergraph's ctor. Walk the callgraph, building supernodes for each CFG basic block, splitting the basic blocks at callsites. Join together the supernodes with interprocedural and intraprocedural @@ -101,8 +145,6 @@ supergraph::supergraph (logger *logger) /* First pass: make supernodes (and assign UIDs to the gimple stmts). */ { - unsigned next_uid = 0; - /* Sort the cgraph_nodes? */ cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) @@ -127,7 +169,7 @@ supergraph::supergraph (logger *logger) { gimple *stmt = gsi_stmt (gpi); m_stmt_to_node_t.put (stmt, node_for_stmts); - stmt->uid = next_uid++; + m_stmt_uids.make_uid_unique (stmt); } /* Append statements from BB to the current supernode, splitting @@ -139,7 +181,7 @@ supergraph::supergraph (logger *logger) gimple *stmt = gsi_stmt (gsi); node_for_stmts->m_stmts.safe_push (stmt); m_stmt_to_node_t.put (stmt, node_for_stmts); - stmt->uid = next_uid++; + m_stmt_uids.make_uid_unique (stmt); if (cgraph_edge *edge = supergraph_call_edge (fun, stmt)) { m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts); @@ -257,6 +299,13 @@ supergraph::supergraph (logger *logger) } } +/* supergraph's dtor. Reset stmt uids. */ + +supergraph::~supergraph () +{ + m_stmt_uids.restore_uids (); +} + /* Dump this graph in .dot format to PP, using DUMP_ARGS. Cluster the supernodes by function, then by BB from original CFG. */ diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index 5d1268e..f4090fd 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -79,6 +79,18 @@ struct supergraph_traits typedef supercluster cluster_t; }; +/* A class to manage the setting and restoring of statement uids. */ + +class saved_uids +{ +public: + void make_uid_unique (gimple *stmt); + void restore_uids () const; + +private: + auto_vec > m_old_stmt_uids; +}; + /* A "supergraph" is a directed graph formed by joining together all CFGs, linking them via interprocedural call and return edges. @@ -90,6 +102,7 @@ class supergraph : public digraph { public: supergraph (logger *logger); + ~supergraph (); supernode *get_node_for_function_entry (function *fun) const { @@ -205,6 +218,8 @@ private: typedef hash_map function_to_num_snodes_t; function_to_num_snodes_t m_function_to_num_snodes; + + saved_uids m_stmt_uids; }; /* A node within a supergraph. */ -- cgit v1.1 From 6d0d35d518a12ee43c1fbd77df73a66d02305a69 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 14 Apr 2021 00:16:24 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 0e2e7e5..85dd462 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,15 @@ +2021-04-13 David Malcolm + + PR analyzer/98599 + * supergraph.cc (saved_uids::make_uid_unique): New. + (saved_uids::restore_uids): New. + (supergraph::supergraph): Replace assignments to stmt->uid with + calls to m_stmt_uids.make_uid_unique. + (supergraph::~supergraph): New. + * supergraph.h (class saved_uids): New. + (supergraph::~supergraph): New decl. + (supergraph::m_stmt_uids): New field. + 2021-04-10 David Malcolm PR analyzer/100011 -- cgit v1.1 From 61bfff562e3b6091d5a0a412a7d496bd523868a8 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Sat, 24 Apr 2021 19:49:31 -0400 Subject: analyzer: fix ICE on NULL change.m_expr [PR100244] PR analyzer/100244 reports an ICE on a -Wanalyzer-free-of-non-heap due to a case where free_of_non_heap::describe_state_change can be passed a NULL change.m_expr for a suitably complicated symbolic value. Bulletproof it by checking for change.m_expr being NULL before dereferencing it. gcc/analyzer/ChangeLog: PR analyzer/100244 * sm-malloc.cc (free_of_non_heap::describe_state_change): Bulletproof against change.m_expr being NULL. gcc/testsuite/ChangeLog: PR analyzer/100244 * g++.dg/analyzer/pr100244.C: New test. --- gcc/analyzer/sm-malloc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 1d5b860..f02b73a 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1303,7 +1303,7 @@ public: { /* Attempt to reconstruct what kind of pointer it is. (It seems neater for this to be a part of the state, though). */ - if (TREE_CODE (change.m_expr) == SSA_NAME) + if (change.m_expr && TREE_CODE (change.m_expr) == SSA_NAME) { gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr); if (gcall *call = dyn_cast (def_stmt)) -- cgit v1.1 From 502ef97c4f442777e5f61c506d17f8776a69b207 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sun, 25 Apr 2021 00:16:26 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 85dd462..182bf78 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,9 @@ +2021-04-24 David Malcolm + + PR analyzer/100244 + * sm-malloc.cc (free_of_non_heap::describe_state_change): + Bulletproof against change.m_expr being NULL. + 2021-04-13 David Malcolm PR analyzer/98599 -- cgit v1.1 From 4bc6fb21bd932ba37ffb14795002f7214b8e3cfd Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Thu, 22 Apr 2021 09:39:39 +0200 Subject: Remove __cplusplus >= 201103 Right now, we require a C++11 compiler, so the check is not needed any longer. gcc/analyzer/ChangeLog: * program-state.cc (program_state::operator=): Remove __cplusplus >= 201103. (program_state::program_state): Likewise. * program-state.h: Likewise. * region-model.h (class region_model): Remove dead code. gcc/ChangeLog: * bitmap.h (class auto_bitmap): Remove __cplusplus >= 201103. * config/aarch64/aarch64.c: Likewise. * gimple-ssa-store-merging.c (store_immediate_info::store_immediate_info): Likewise. * sbitmap.h: Likewise. --- gcc/analyzer/program-state.cc | 2 -- gcc/analyzer/program-state.h | 4 ---- gcc/analyzer/region-model.h | 5 ----- 3 files changed, 11 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index f809462..5c690b0 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -731,7 +731,6 @@ program_state::operator= (const program_state &other) return *this; } -#if __cplusplus >= 201103 /* Move constructor for program_state (when building with C++11). */ program_state::program_state (program_state &&other) : m_region_model (other.m_region_model), @@ -747,7 +746,6 @@ program_state::program_state (program_state &&other) m_valid = other.m_valid; } -#endif /* program_state's dtor. */ diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 898c57ff..f16fe6b 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -192,11 +192,7 @@ public: program_state (const extrinsic_state &ext_state); program_state (const program_state &other); program_state& operator= (const program_state &other); - -#if __cplusplus >= 201103 program_state (program_state &&other); -#endif - ~program_state (); hashval_t hash () const; diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 4123500..a169396 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -414,11 +414,6 @@ class region_model region_model (region_model_manager *mgr); region_model (const region_model &other); ~region_model (); - -#if 0//__cplusplus >= 201103 - region_model (region_model &&other); -#endif - region_model &operator= (const region_model &other); bool operator== (const region_model &other) const; -- cgit v1.1 From 6ba3079dce89d9b63bf5dbd5e320ea2bf96f196b Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Wed, 17 Mar 2021 16:36:44 +0100 Subject: Come up with startswith function. gcc/ada/ChangeLog: * gcc-interface/utils.c (def_builtin_1): Use startswith function instead of strncmp. gcc/analyzer/ChangeLog: * sm-file.cc (is_file_using_fn_p): Use startswith function instead of strncmp. gcc/ChangeLog: * builtins.c (is_builtin_name): Use startswith function instead of strncmp. * collect2.c (main): Likewise. (has_lto_section): Likewise. (scan_libraries): Likewise. * coverage.c (coverage_checksum_string): Likewise. (coverage_init): Likewise. * dwarf2out.c (is_cxx): Likewise. (gen_compile_unit_die): Likewise. * gcc-ar.c (main): Likewise. * gcc.c (init_spec): Likewise. (read_specs): Likewise. (execute): Likewise. (check_live_switch): Likewise. * genattrtab.c (write_attr_case): Likewise. (IS_ATTR_GROUP): Likewise. * gencfn-macros.c (main): Likewise. * gengtype.c (type_for_name): Likewise. (gen_rtx_next): Likewise. (get_file_langdir): Likewise. (write_local): Likewise. * genmatch.c (get_operator): Likewise. (get_operand_type): Likewise. (expr::gen_transform): Likewise. * genoutput.c (validate_optab_operands): Likewise. * incpath.c (add_sysroot_to_chain): Likewise. * langhooks.c (lang_GNU_C): Likewise. (lang_GNU_CXX): Likewise. (lang_GNU_Fortran): Likewise. (lang_GNU_OBJC): Likewise. * lto-wrapper.c (run_gcc): Likewise. * omp-general.c (omp_max_simt_vf): Likewise. * omp-low.c (omp_runtime_api_call): Likewise. * opts-common.c (parse_options_from_collect_gcc_options): Likewise. * read-rtl-function.c (function_reader::read_rtx_operand_r): Likewise. * real.c (real_from_string): Likewise. * selftest.c (assert_str_startswith): Likewise. * timevar.c (timer::validate_phases): Likewise. * tree.c (get_file_function_name): Likewise. * ubsan.c (ubsan_use_new_style_p): Likewise. * varasm.c (default_function_rodata_section): Likewise. (incorporeal_function_p): Likewise. (default_section_type_flags): Likewise. * system.h (startswith): Define startswith. gcc/c-family/ChangeLog: * c-ada-spec.c (print_destructor): Use startswith function instead of strncmp. (dump_ada_declaration): Likewise. * c-common.c (disable_builtin_function): Likewise. (def_builtin_1): Likewise. * c-format.c (check_tokens): Likewise. (check_plain): Likewise. (convert_format_name_to_system_name): Likewise. gcc/c/ChangeLog: * c-aux-info.c (affix_data_type): Use startswith function instead of strncmp. * c-typeck.c (build_function_call_vec): Likewise. * gimple-parser.c (c_parser_gimple_parse_bb_spec): Likewise. gcc/cp/ChangeLog: * decl.c (duplicate_decls): Use startswith function instead of strncmp. (cxx_builtin_function): Likewise. (omp_declare_variant_finalize_one): Likewise. (grokfndecl): Likewise. * error.c (dump_decl_name): Likewise. * mangle.c (find_decomp_unqualified_name): Likewise. (write_guarded_var_name): Likewise. (decl_tls_wrapper_p): Likewise. * parser.c (cp_parser_simple_type_specifier): Likewise. (cp_parser_tx_qualifier_opt): Likewise. * pt.c (template_parm_object_p): Likewise. (dguide_name_p): Likewise. gcc/d/ChangeLog: * d-builtins.cc (do_build_builtin_fn): Use startswith function instead of strncmp. * dmd/dinterpret.c (evaluateIfBuiltin): Likewise. * dmd/dmangle.c: Likewise. * dmd/hdrgen.c: Likewise. * dmd/identifier.c (Identifier::toHChars2): Likewise. gcc/fortran/ChangeLog: * decl.c (variable_decl): Use startswith function instead of strncmp. (gfc_match_end): Likewise. * gfortran.h (gfc_str_startswith): Likewise. * module.c (load_omp_udrs): Likewise. (read_module): Likewise. * options.c (gfc_handle_runtime_check_option): Likewise. * primary.c (match_arg_list_function): Likewise. * trans-decl.c (gfc_get_symbol_decl): Likewise. * trans-expr.c (gfc_conv_procedure_call): Likewise. * trans-intrinsic.c (gfc_conv_ieee_arithmetic_function): Likewise. gcc/go/ChangeLog: * gofrontend/runtime.cc (Runtime::name_to_code): Use startswith function instead of strncmp. gcc/objc/ChangeLog: * objc-act.c (objc_string_ref_type_p): Use startswith function instead of strncmp. * objc-encoding.c (encode_type): Likewise. * objc-next-runtime-abi-02.c (has_load_impl): Likewise. --- gcc/analyzer/sm-file.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index d64c313..3a5f95d 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -312,9 +312,8 @@ is_file_using_fn_p (tree fndecl) /* Also support variants of these names prefixed with "_IO_". */ const char *name = IDENTIFIER_POINTER (DECL_NAME (fndecl)); - if (strncmp (name, "_IO_", 4) == 0) - if (fs.contains_name_p (name + 4)) - return true; + if (startswith (name, "_IO_") && fs.contains_name_p (name + 4)) + return true; return false; } -- cgit v1.1 From aa891c56f25baac94db004e309d1b6e40b770a95 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 11 May 2021 00:16:36 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 182bf78..e6c0631 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,16 @@ +2021-05-10 Martin Liska + + * sm-file.cc (is_file_using_fn_p): Use startswith + function instead of strncmp. + +2021-05-10 Martin Liska + + * program-state.cc (program_state::operator=): Remove + __cplusplus >= 201103. + (program_state::program_state): Likewise. + * program-state.h: Likewise. + * region-model.h (class region_model): Remove dead code. + 2021-04-24 David Malcolm PR analyzer/100244 -- cgit v1.1 From cd323d97d0592135ca4345701ef051659d8d4507 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 18 May 2021 12:29:58 -0400 Subject: analyzer: fix missing leak after call to strsep [PR100615] PR analyzer/100615 reports a missing leak diagnostic. The issue is that the code calls strsep which the analyzer doesn't have special knowledge of, and so conservatively assumes that it could free the pointer, so drops malloc state for it. Properly "teaching" the analyzer about strsep would require it to support bifurcating state at a call, which is currently fiddly to do, so for now this patch notes that strsep doesn't affect the malloc state machine, allowing the analyzer to correctly detect the leak. gcc/analyzer/ChangeLog: PR analyzer/100615 * sm-malloc.cc: Include "analyzer/function-set.h". (malloc_state_machine::on_stmt): Call unaffected_by_call_p and bail on the functions it recognizes. (malloc_state_machine::unaffected_by_call_p): New. gcc/testsuite/ChangeLog: PR analyzer/100615 * gcc.dg/analyzer/pr100615.c: New test. --- gcc/analyzer/sm-malloc.cc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index f02b73a..a1582ca 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -44,6 +44,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/region-model.h" #include "stringpool.h" #include "attribs.h" +#include "analyzer/function-set.h" #if ENABLE_ANALYZER @@ -384,6 +385,8 @@ public: bool reset_when_passed_to_unknown_fn_p (state_t s, bool is_mutable) const FINAL OVERRIDE; + static bool unaffected_by_call_p (tree fndecl); + standard_deallocator_set m_free; standard_deallocator_set m_scalar_delete; standard_deallocator_set m_vector_delete; @@ -1569,6 +1572,9 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, return true; } + if (unaffected_by_call_p (callee_fndecl)) + return true; + /* Cast away const-ness for cache-like operations. */ malloc_state_machine *mutable_this = const_cast (this); @@ -1925,6 +1931,28 @@ malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s, return is_mutable; } +/* Return true if calls to FNDECL are known to not affect this sm-state. */ + +bool +malloc_state_machine::unaffected_by_call_p (tree fndecl) +{ + /* A set of functions that are known to not affect allocation + status, even if we haven't fully modelled the rest of their + behavior yet. */ + static const char * const funcnames[] = { + /* This array must be kept sorted. */ + "strsep", + }; + const size_t count + = sizeof(funcnames) / sizeof (funcnames[0]); + function_set fs (funcnames, count); + + if (fs.contains_decl_p (fndecl)) + return true; + + return false; +} + /* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that assign zero to LHS. */ -- cgit v1.1 From a8daf9a19a5eae6b98acede14bb6c27b2e0038e0 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 19 May 2021 00:16:45 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index e6c0631..7396971 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,11 @@ +2021-05-18 David Malcolm + + PR analyzer/100615 + * sm-malloc.cc: Include "analyzer/function-set.h". + (malloc_state_machine::on_stmt): Call unaffected_by_call_p and + bail on the functions it recognizes. + (malloc_state_machine::unaffected_by_call_p): New. + 2021-05-10 Martin Liska * sm-file.cc (is_file_using_fn_p): Use startswith -- cgit v1.1 From e84fe25f6386666efe1ead4304693d91d7555e7a Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 3 Jun 2021 10:35:27 -0400 Subject: analyzer: show types for poisoned_svalue and compound_svalue gcc/analyzer/ChangeLog: * svalue.cc (poisoned_svalue::dump_to_pp): Dump type. (compound_svalue::dump_to_pp): Dump any type. Signed-off-by: David Malcolm --- gcc/analyzer/svalue.cc | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 897e84e..a16563d 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -735,9 +735,17 @@ void poisoned_svalue::dump_to_pp (pretty_printer *pp, bool simple) const { if (simple) - pp_printf (pp, "POISONED(%s)", poison_kind_to_str (m_kind)); + { + pp_string (pp, "POISONED("); + print_quoted_type (pp, get_type ()); + pp_printf (pp, ", %s)", poison_kind_to_str (m_kind)); + } else - pp_printf (pp, "poisoned_svalue(%s)", poison_kind_to_str (m_kind)); + { + pp_string (pp, "poisoned_svalue("); + print_quoted_type (pp, get_type ()); + pp_printf (pp, ", %s)", poison_kind_to_str (m_kind)); + } } /* Implementation of svalue::accept vfunc for poisoned_svalue. */ @@ -1228,17 +1236,26 @@ compound_svalue::dump_to_pp (pretty_printer *pp, bool simple) const if (simple) { pp_string (pp, "COMPOUND("); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } + pp_character (pp, '{'); m_map.dump_to_pp (pp, simple, false); - pp_character (pp, ')'); + pp_string (pp, "})"); } else { pp_string (pp, "compound_svalue ("); - pp_string (pp, ", "); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } pp_character (pp, '{'); m_map.dump_to_pp (pp, simple, false); - pp_string (pp, "}, "); - pp_character (pp, ')'); + pp_string (pp, "})"); } } -- cgit v1.1 From 981d98b883ed521c88c295ed82227c605d82add4 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 3 Jun 2021 10:37:41 -0400 Subject: analyzer: remove unused prototypes gcc/analyzer/ChangeLog: * store.h (store::get_direct_binding): Remove unused decl. (store::get_default_binding): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/store.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index dc22d96..d68513c 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -611,8 +611,6 @@ public: json::object *to_json () const; - const svalue *get_direct_binding (store_manager *mgr, const region *reg); - const svalue *get_default_binding (store_manager *mgr, const region *reg); const svalue *get_any_binding (store_manager *mgr, const region *reg) const; bool called_unknown_fn_p () const { return m_called_unknown_fn; } -- cgit v1.1 From 440c8a0a91b7ea1603e3e1eaae64fc0e12f0c4f1 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Fri, 4 Jun 2021 00:16:24 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 7396971..838d5f1 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,13 @@ +2021-06-03 David Malcolm + + * store.h (store::get_direct_binding): Remove unused decl. + (store::get_default_binding): Likewise. + +2021-06-03 David Malcolm + + * svalue.cc (poisoned_svalue::dump_to_pp): Dump type. + (compound_svalue::dump_to_pp): Dump any type. + 2021-05-18 David Malcolm PR analyzer/100615 -- cgit v1.1 From 8c5a5404cb68e5e39e296849944019b93a591646 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 8 Jun 2021 14:42:48 -0400 Subject: analyzer: remove redundant typedef Delete an overzealous copy&paste. gcc/analyzer/ChangeLog: * svalue.h (conjured_svalue::iterator_t): Delete. Signed-off-by: David Malcolm --- gcc/analyzer/svalue.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 7fe0ba3..d9e34aa 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -1073,8 +1073,6 @@ namespace ana { class conjured_svalue : public svalue { public: - typedef binding_map::iterator_t iterator_t; - /* A support class for uniquifying instances of conjured_svalue. */ struct key_t { -- cgit v1.1 From 6b400aef1bdc84bbdf5011caff3fe5f82c68d253 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 8 Jun 2021 14:43:48 -0400 Subject: analyzer: split out struct bit_range from class concrete_binding gcc/analyzer/ChangeLog: * store.cc (concrete_binding::dump_to_pp): Move bulk of implementation to... (bit_range::dump_to_pp): ...this new function. (bit_range::cmp): New. (concrete_binding::overlaps_p): Update for use of bit_range. (concrete_binding::cmp_ptr_ptr): Likewise. * store.h (struct bit_range): New. (class concrete_binding): Replace fields m_start_bit_offset and m_size_in_bits with new field m_bit_range. Signed-off-by: David Malcolm --- gcc/analyzer/store.cc | 38 ++++++++++++++++++++++---------- gcc/analyzer/store.h | 61 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 77 insertions(+), 22 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index b1874a5..f4bb7de 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -236,15 +236,12 @@ binding_key::cmp (const binding_key *k1, const binding_key *k2) } } -/* class concrete_binding : public binding_key. */ - -/* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */ +/* struct struct bit_range. */ void -concrete_binding::dump_to_pp (pretty_printer *pp, bool simple) const +bit_range::dump_to_pp (pretty_printer *pp) const { - binding_key::dump_to_pp (pp, simple); - pp_string (pp, ", start: "); + pp_string (pp, "start: "); pp_wide_int (pp, m_start_bit_offset, SIGNED); pp_string (pp, ", size: "); pp_wide_int (pp, m_size_in_bits, SIGNED); @@ -252,12 +249,34 @@ concrete_binding::dump_to_pp (pretty_printer *pp, bool simple) const pp_wide_int (pp, get_next_bit_offset (), SIGNED); } +int +bit_range::cmp (const bit_range &br1, const bit_range &br2) +{ + if (int start_cmp = wi::cmps (br1.m_start_bit_offset, + br2.m_start_bit_offset)) + return start_cmp; + + return wi::cmpu (br1.m_size_in_bits, br2.m_size_in_bits); +} + +/* class concrete_binding : public binding_key. */ + +/* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */ + +void +concrete_binding::dump_to_pp (pretty_printer *pp, bool simple) const +{ + binding_key::dump_to_pp (pp, simple); + pp_string (pp, ", "); + m_bit_range.dump_to_pp (pp); +} + /* Return true if this binding overlaps with OTHER. */ bool concrete_binding::overlaps_p (const concrete_binding &other) const { - if (m_start_bit_offset < other.get_next_bit_offset () + if (get_start_bit_offset () < other.get_next_bit_offset () && get_next_bit_offset () > other.get_start_bit_offset ()) return true; return false; @@ -274,10 +293,7 @@ concrete_binding::cmp_ptr_ptr (const void *p1, const void *p2) if (int kind_cmp = b1->get_kind () - b2->get_kind ()) return kind_cmp; - if (int start_cmp = wi::cmps (b1->m_start_bit_offset, b2->m_start_bit_offset)) - return start_cmp; - - return wi::cmpu (b1->m_size_in_bits, b2->m_size_in_bits); + return bit_range::cmp (b1->m_bit_range, b2->m_bit_range); } /* class symbolic_binding : public binding_key. */ diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index d68513c..be09b42 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -267,6 +267,42 @@ private: enum binding_kind m_kind; }; +struct bit_range +{ + bit_range (bit_offset_t start_bit_offset, bit_size_t size_in_bits) + : m_start_bit_offset (start_bit_offset), + m_size_in_bits (size_in_bits) + {} + + void dump_to_pp (pretty_printer *pp) const; + + bit_offset_t get_start_bit_offset () const + { + return m_start_bit_offset; + } + bit_offset_t get_next_bit_offset () const + { + return m_start_bit_offset + m_size_in_bits; + } + + bool contains_p (bit_offset_t offset) const + { + return (offset >= get_start_bit_offset () + && offset < get_next_bit_offset ()); + } + + bool operator== (const bit_range &other) const + { + return (m_start_bit_offset == other.m_start_bit_offset + && m_size_in_bits == other.m_size_in_bits); + } + + static int cmp (const bit_range &br1, const bit_range &br2); + + bit_offset_t m_start_bit_offset; + bit_size_t m_size_in_bits; +}; + /* Concrete subclass of binding_key, for describing a concrete range of bits within the binding_map (e.g. "bits 8-15"). */ @@ -279,24 +315,22 @@ public: concrete_binding (bit_offset_t start_bit_offset, bit_size_t size_in_bits, enum binding_kind kind) : binding_key (kind), - m_start_bit_offset (start_bit_offset), - m_size_in_bits (size_in_bits) + m_bit_range (start_bit_offset, size_in_bits) {} bool concrete_p () const FINAL OVERRIDE { return true; } hashval_t hash () const { inchash::hash hstate; - hstate.add_wide_int (m_start_bit_offset); - hstate.add_wide_int (m_size_in_bits); + hstate.add_wide_int (m_bit_range.m_start_bit_offset); + hstate.add_wide_int (m_bit_range.m_size_in_bits); return hstate.end () ^ binding_key::impl_hash (); } bool operator== (const concrete_binding &other) const { if (!binding_key::impl_eq (other)) return false; - return (m_start_bit_offset == other.m_start_bit_offset - && m_size_in_bits == other.m_size_in_bits); + return m_bit_range == other.m_bit_range; } void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; @@ -304,12 +338,18 @@ public: const concrete_binding *dyn_cast_concrete_binding () const FINAL OVERRIDE { return this; } - bit_offset_t get_start_bit_offset () const { return m_start_bit_offset; } - bit_size_t get_size_in_bits () const { return m_size_in_bits; } + bit_offset_t get_start_bit_offset () const + { + return m_bit_range.m_start_bit_offset; + } + bit_size_t get_size_in_bits () const + { + return m_bit_range.m_size_in_bits; + } /* Return the next bit offset after the end of this binding. */ bit_offset_t get_next_bit_offset () const { - return m_start_bit_offset + m_size_in_bits; + return m_bit_range.get_next_bit_offset (); } bool overlaps_p (const concrete_binding &other) const; @@ -317,8 +357,7 @@ public: static int cmp_ptr_ptr (const void *, const void *); private: - bit_offset_t m_start_bit_offset; - bit_size_t m_size_in_bits; + bit_range m_bit_range; }; } // namespace ana -- cgit v1.1 From c957d38044d7eb6a45f57a8a9f707c3c0a798e9f Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 8 Jun 2021 14:45:07 -0400 Subject: analyzer: fix region::get_bit_size for bitfields gcc/analyzer/ChangeLog: * analyzer.h (int_size_in_bits): New decl. * region.cc (int_size_in_bits): New function. (region::get_bit_size): Reimplement in terms of the above. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.h | 2 ++ gcc/analyzer/region.cc | 33 +++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index fb568e4..525eb06 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -144,6 +144,8 @@ typedef offset_int bit_offset_t; typedef offset_int bit_size_t; typedef offset_int byte_size_t; +extern bool int_size_in_bits (const_tree type, bit_size_t *out); + /* The location of a region expressesd as an offset relative to a base region. */ diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 6db1fc9..5f246df 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -208,6 +208,29 @@ region::get_byte_size (byte_size_t *out) const return true; } +/* If the size of TYPE (in bits) is constant, write it to *OUT + and return true. + Otherwise return false. */ + +bool +int_size_in_bits (const_tree type, bit_size_t *out) +{ + if (INTEGRAL_TYPE_P (type)) + { + *out = TYPE_PRECISION (type); + return true; + } + + tree sz = TYPE_SIZE (type); + if (sz && tree_fits_uhwi_p (sz)) + { + *out = TREE_INT_CST_LOW (sz); + return true; + } + else + return false; +} + /* If the size of this region (in bits) is known statically, write it to *OUT and return true. Otherwise return false. */ @@ -215,11 +238,13 @@ region::get_byte_size (byte_size_t *out) const bool region::get_bit_size (bit_size_t *out) const { - byte_size_t byte_size; - if (!get_byte_size (&byte_size)) + tree type = get_type (); + + /* Bail out e.g. for heap-allocated regions. */ + if (!type) return false; - *out = byte_size * BITS_PER_UNIT; - return true; + + return int_size_in_bits (type, out); } /* Get the field within RECORD_TYPE at BIT_OFFSET. */ -- cgit v1.1 From d3b1ef7a83c0c0cd5b20a1dd1714b868f3d2b442 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 8 Jun 2021 14:45:57 -0400 Subject: analyzer: bitfield fixes [PR99212] This patch verifies the previous fix for bitfield sizes by implementing enough support for bitfields in the analyzer to get the test cases to pass. The patch implements support in the analyzer for reading from a BIT_FIELD_REF, and support for folding BIT_AND_EXPR of a mask, to handle the cases generated in tests. The existing bitfields tests in data-model-1.c turned out to rely on undefined behavior, in that they were assigning values to a signed bitfield that were outside of the valid range of values. I believe that that's why we were seeing target-specific differences in the test results (PR analyzer/99212). The patch updates the test to remove the undefined behaviors. gcc/analyzer/ChangeLog: PR analyzer/99212 * region-model-manager.cc (region_model_manager::maybe_fold_binop): Add support for folding BIT_AND_EXPR of compound_svalue and a mask constant. * region-model.cc (region_model::get_rvalue_1): Implement BIT_FIELD_REF in terms of... (region_model::get_rvalue_for_bits): New function. * region-model.h (region_model::get_rvalue_for_bits): New decl. * store.cc (bit_range::from_mask): New function. (selftest::test_bit_range_intersects_p): New selftest. (selftest::assert_bit_range_from_mask_eq): New. (ASSERT_BIT_RANGE_FROM_MASK_EQ): New macro. (selftest::assert_no_bit_range_from_mask_eq): New. (ASSERT_NO_BIT_RANGE_FROM_MASK): New macro. (selftest::test_bit_range_from_mask): New selftest. (selftest::analyzer_store_cc_tests): Call the new selftests. * store.h (bit_range::intersects_p): New. (bit_range::from_mask): New decl. (concrete_binding::get_bit_range): New accessor. (store_manager::get_concrete_binding): New overload taking const bit_range &. gcc/testsuite/ChangeLog: PR analyzer/99212 * gcc.dg/analyzer/bitfields-1.c: New test. * gcc.dg/analyzer/data-model-1.c (struct sbits): Make bitfields explicitly signed. (test_44): Update test values assigned to the bits to ones that fit in the range of the bitfield type. Remove xfails. (test_45): Remove xfails. Signed-off-by: David Malcolm --- gcc/analyzer/region-model-manager.cc | 46 ++++++++- gcc/analyzer/region-model.cc | 65 +++++++++++- gcc/analyzer/region-model.h | 4 + gcc/analyzer/store.cc | 186 +++++++++++++++++++++++++++++++++++ gcc/analyzer/store.h | 18 ++++ 5 files changed, 315 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index dfd2413..0ca0c8a 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -480,9 +480,49 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, break; case BIT_AND_EXPR: if (cst1) - if (zerop (cst1) && INTEGRAL_TYPE_P (type)) - /* "(ARG0 & 0)" -> "0". */ - return get_or_create_constant_svalue (build_int_cst (type, 0)); + { + if (zerop (cst1) && INTEGRAL_TYPE_P (type)) + /* "(ARG0 & 0)" -> "0". */ + return get_or_create_constant_svalue (build_int_cst (type, 0)); + + /* Support masking out bits from a compound_svalue, as this + is generated when accessing bitfields. */ + if (const compound_svalue *compound_sval + = arg0->dyn_cast_compound_svalue ()) + { + const binding_map &map = compound_sval->get_map (); + unsigned HOST_WIDE_INT mask = TREE_INT_CST_LOW (cst1); + /* If "mask" is a contiguous range of set bits, see if the + compound_sval has a value for those bits. */ + bit_range bits (0, 0); + if (bit_range::from_mask (mask, &bits)) + { + const concrete_binding *conc + = get_store_manager ()->get_concrete_binding (bits, + BK_direct); + if (const svalue *sval = map.get (conc)) + { + /* We have a value; + shift it by the correct number of bits. */ + const svalue *lhs = get_or_create_cast (type, sval); + HOST_WIDE_INT bit_offset + = bits.get_start_bit_offset ().to_shwi (); + tree shift_amt = build_int_cst (type, bit_offset); + const svalue *shift_sval + = get_or_create_constant_svalue (shift_amt); + const svalue *shifted_sval + = get_or_create_binop (type, + LSHIFT_EXPR, + lhs, shift_sval); + /* Reapply the mask (needed for negative + signed bitfields). */ + return get_or_create_binop (type, + BIT_AND_EXPR, + shifted_sval, arg1); + } + } + } + } break; case TRUTH_ANDIF_EXPR: case TRUTH_AND_EXPR: diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index c7038dd..0d363fb 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1357,7 +1357,18 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) break; case BIT_FIELD_REF: - return m_mgr->get_or_create_unknown_svalue (TREE_TYPE (pv.m_tree)); + { + tree expr = pv.m_tree; + tree op0 = TREE_OPERAND (expr, 0); + const region *reg = get_lvalue (op0, ctxt); + tree num_bits = TREE_OPERAND (expr, 1); + tree first_bit_offset = TREE_OPERAND (expr, 2); + gcc_assert (TREE_CODE (num_bits) == INTEGER_CST); + gcc_assert (TREE_CODE (first_bit_offset) == INTEGER_CST); + bit_range bits (TREE_INT_CST_LOW (first_bit_offset), + TREE_INT_CST_LOW (num_bits)); + return get_rvalue_for_bits (TREE_TYPE (expr), reg, bits); + } case SSA_NAME: case VAR_DECL: @@ -1686,6 +1697,58 @@ region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, return m_mgr->get_symbolic_region (ptr_sval); } +/* Attempt to get BITS within any value of REG, as TYPE. + In particular, extract values from compound_svalues for the case + where there's a concrete binding at BITS. + Return an unknown svalue if we can't handle the given case. */ + +const svalue * +region_model::get_rvalue_for_bits (tree type, + const region *reg, + const bit_range &bits) +{ + const svalue *sval = get_store_value (reg); + if (const compound_svalue *compound_sval = sval->dyn_cast_compound_svalue ()) + { + const binding_map &map = compound_sval->get_map (); + binding_map result_map; + for (auto iter : map) + { + const binding_key *key = iter.first; + if (const concrete_binding *conc_key + = key->dyn_cast_concrete_binding ()) + { + /* Ignore concrete bindings outside BITS. */ + if (!conc_key->get_bit_range ().intersects_p (bits)) + continue; + if ((conc_key->get_start_bit_offset () + < bits.get_start_bit_offset ()) + || (conc_key->get_next_bit_offset () + > bits.get_next_bit_offset ())) + { + /* If we have any concrete keys that aren't fully within BITS, + then bail out. */ + return m_mgr->get_or_create_unknown_svalue (type); + } + const concrete_binding *offset_conc_key + = m_mgr->get_store_manager ()->get_concrete_binding + (conc_key->get_start_bit_offset () + - bits.get_start_bit_offset (), + conc_key->get_size_in_bits (), + conc_key->get_kind ()); + const svalue *sval = iter.second; + result_map.put (offset_conc_key, sval); + } + else + /* If we have any symbolic keys we can't get it as bits. */ + return m_mgr->get_or_create_unknown_svalue (type); + } + return m_mgr->get_or_create_compound_svalue (type, result_map); + } + + return m_mgr->get_or_create_unknown_svalue (type); +} + /* A subclass of pending_diagnostic for complaining about writes to constant regions of memory. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index a169396..5e43e54 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -509,6 +509,10 @@ class region_model const region *deref_rvalue (const svalue *ptr_sval, tree ptr_tree, region_model_context *ctxt); + const svalue *get_rvalue_for_bits (tree type, + const region *reg, + const bit_range &bits); + void set_value (const region *lhs_reg, const svalue *rhs_sval, region_model_context *ctxt); void set_value (tree lhs, tree rhs, region_model_context *ctxt); diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index f4bb7de..699de94 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -259,6 +259,64 @@ bit_range::cmp (const bit_range &br1, const bit_range &br2) return wi::cmpu (br1.m_size_in_bits, br2.m_size_in_bits); } +/* If MASK is a contiguous range of set bits, write them + to *OUT and return true. + Otherwise return false. */ + +bool +bit_range::from_mask (unsigned HOST_WIDE_INT mask, bit_range *out) +{ + unsigned iter_bit_idx = 0; + unsigned HOST_WIDE_INT iter_bit_mask = 1; + + /* Find the first contiguous run of set bits in MASK. */ + + /* Find first set bit in MASK. */ + while (iter_bit_idx < HOST_BITS_PER_WIDE_INT) + { + if (mask & iter_bit_mask) + break; + iter_bit_idx++; + iter_bit_mask <<= 1; + } + if (iter_bit_idx == HOST_BITS_PER_WIDE_INT) + /* MASK is zero. */ + return false; + + unsigned first_set_iter_bit_idx = iter_bit_idx; + unsigned num_set_bits = 1; + iter_bit_idx++; + iter_bit_mask <<= 1; + + /* Find next unset bit in MASK. */ + while (iter_bit_idx < HOST_BITS_PER_WIDE_INT) + { + if (!(mask & iter_bit_mask)) + break; + num_set_bits++; + iter_bit_idx++; + iter_bit_mask <<= 1; + } + if (iter_bit_idx == HOST_BITS_PER_WIDE_INT) + { + *out = bit_range (first_set_iter_bit_idx, num_set_bits); + return true; + } + + /* We now have the first contiguous run of set bits in MASK. + Fail if any other bits are set. */ + while (iter_bit_idx < HOST_BITS_PER_WIDE_INT) + { + if (mask & iter_bit_mask) + return false; + iter_bit_idx++; + iter_bit_mask <<= 1; + } + + *out = bit_range (first_set_iter_bit_idx, num_set_bits); + return true; +} + /* class concrete_binding : public binding_key. */ /* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */ @@ -2448,6 +2506,132 @@ store::loop_replay_fixup (const store *other_store, namespace selftest { +/* Verify that bit_range::intersects_p works as expected. */ + +static void +test_bit_range_intersects_p () +{ + bit_range b0 (0, 1); + bit_range b1 (1, 1); + bit_range b2 (2, 1); + bit_range b3 (3, 1); + bit_range b4 (4, 1); + bit_range b5 (5, 1); + bit_range b6 (6, 1); + bit_range b7 (7, 1); + bit_range b1_to_6 (1, 6); + bit_range b0_to_7 (0, 8); + bit_range b3_to_5 (3, 3); + bit_range b6_to_7 (6, 2); + + /* self-intersection is true. */ + ASSERT_TRUE (b0.intersects_p (b0)); + ASSERT_TRUE (b7.intersects_p (b7)); + ASSERT_TRUE (b1_to_6.intersects_p (b1_to_6)); + ASSERT_TRUE (b0_to_7.intersects_p (b0_to_7)); + + ASSERT_FALSE (b0.intersects_p (b1)); + ASSERT_FALSE (b1.intersects_p (b0)); + ASSERT_FALSE (b0.intersects_p (b7)); + ASSERT_FALSE (b7.intersects_p (b0)); + + ASSERT_TRUE (b0_to_7.intersects_p (b0)); + ASSERT_TRUE (b0_to_7.intersects_p (b7)); + ASSERT_TRUE (b0.intersects_p (b0_to_7)); + ASSERT_TRUE (b7.intersects_p (b0_to_7)); + + ASSERT_FALSE (b0.intersects_p (b1_to_6)); + ASSERT_FALSE (b1_to_6.intersects_p (b0)); + ASSERT_TRUE (b1.intersects_p (b1_to_6)); + ASSERT_TRUE (b1_to_6.intersects_p (b1)); + ASSERT_TRUE (b1_to_6.intersects_p (b6)); + ASSERT_FALSE (b1_to_6.intersects_p (b7)); + + ASSERT_TRUE (b1_to_6.intersects_p (b0_to_7)); + ASSERT_TRUE (b0_to_7.intersects_p (b1_to_6)); + + ASSERT_FALSE (b3_to_5.intersects_p (b6_to_7)); + ASSERT_FALSE (b6_to_7.intersects_p (b3_to_5)); +} + +/* Implementation detail of ASSERT_BIT_RANGE_FROM_MASK_EQ. */ + +static void +assert_bit_range_from_mask_eq (const location &loc, + unsigned HOST_WIDE_INT mask, + const bit_range &expected) +{ + bit_range actual (0, 0); + bool ok = bit_range::from_mask (mask, &actual); + ASSERT_TRUE_AT (loc, ok); + ASSERT_EQ_AT (loc, actual, expected); +} + +/* Assert that bit_range::from_mask (MASK) returns true, and writes + out EXPECTED_BIT_RANGE. */ + +#define ASSERT_BIT_RANGE_FROM_MASK_EQ(MASK, EXPECTED_BIT_RANGE) \ + SELFTEST_BEGIN_STMT \ + assert_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK, \ + EXPECTED_BIT_RANGE); \ + SELFTEST_END_STMT + +/* Implementation detail of ASSERT_NO_BIT_RANGE_FROM_MASK. */ + +static void +assert_no_bit_range_from_mask_eq (const location &loc, + unsigned HOST_WIDE_INT mask) +{ + bit_range actual (0, 0); + bool ok = bit_range::from_mask (mask, &actual); + ASSERT_FALSE_AT (loc, ok); +} + +/* Assert that bit_range::from_mask (MASK) returns false. */ + +#define ASSERT_NO_BIT_RANGE_FROM_MASK(MASK) \ + SELFTEST_BEGIN_STMT \ + assert_no_bit_range_from_mask_eq (SELFTEST_LOCATION, MASK); \ + SELFTEST_END_STMT + +/* Verify that bit_range::from_mask works as expected. */ + +static void +test_bit_range_from_mask () +{ + /* Should fail on zero. */ + ASSERT_NO_BIT_RANGE_FROM_MASK (0); + + /* Verify 1-bit masks. */ + ASSERT_BIT_RANGE_FROM_MASK_EQ (1, bit_range (0, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (2, bit_range (1, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (4, bit_range (2, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (8, bit_range (3, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (16, bit_range (4, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (32, bit_range (5, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (64, bit_range (6, 1)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (128, bit_range (7, 1)); + + /* Verify N-bit masks starting at bit 0. */ + ASSERT_BIT_RANGE_FROM_MASK_EQ (3, bit_range (0, 2)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (7, bit_range (0, 3)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (15, bit_range (0, 4)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (31, bit_range (0, 5)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (63, bit_range (0, 6)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (127, bit_range (0, 7)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (255, bit_range (0, 8)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (0xffff, bit_range (0, 16)); + + /* Various other tests. */ + ASSERT_BIT_RANGE_FROM_MASK_EQ (0x30, bit_range (4, 2)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (0x700, bit_range (8, 3)); + ASSERT_BIT_RANGE_FROM_MASK_EQ (0x600, bit_range (9, 2)); + + /* Multiple ranges of set bits should fail. */ + ASSERT_NO_BIT_RANGE_FROM_MASK (0x101); + ASSERT_NO_BIT_RANGE_FROM_MASK (0xf0f0f0f0); +} + /* Implementation detail of ASSERT_OVERLAP. */ static void @@ -2546,6 +2730,8 @@ test_binding_key_overlap () void analyzer_store_cc_tests () { + test_bit_range_intersects_p (); + test_bit_range_from_mask (); test_binding_key_overlap (); } diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index be09b42..7bd2824 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -297,8 +297,16 @@ struct bit_range && m_size_in_bits == other.m_size_in_bits); } + bool intersects_p (const bit_range &other) const + { + return (get_start_bit_offset () < other.get_next_bit_offset () + && other.get_start_bit_offset () < get_next_bit_offset ()); + } + static int cmp (const bit_range &br1, const bit_range &br2); + static bool from_mask (unsigned HOST_WIDE_INT mask, bit_range *out); + bit_offset_t m_start_bit_offset; bit_size_t m_size_in_bits; }; @@ -338,6 +346,8 @@ public: const concrete_binding *dyn_cast_concrete_binding () const FINAL OVERRIDE { return this; } + const bit_range &get_bit_range () const { return m_bit_range; } + bit_offset_t get_start_bit_offset () const { return m_bit_range.m_start_bit_offset; @@ -739,6 +749,14 @@ public: get_concrete_binding (bit_offset_t start_bit_offset, bit_offset_t size_in_bits, enum binding_kind kind); + const concrete_binding * + get_concrete_binding (const bit_range &bits, + enum binding_kind kind) + { + return get_concrete_binding (bits.get_start_bit_offset (), + bits.m_size_in_bits, + kind); + } const symbolic_binding * get_symbolic_binding (const region *region, enum binding_kind kind); -- cgit v1.1 From c60387214593445d1514bf7852f27f4523458cda Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 9 Jun 2021 00:16:30 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 838d5f1..c3a3d39 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,49 @@ +2021-06-08 David Malcolm + + PR analyzer/99212 + * region-model-manager.cc + (region_model_manager::maybe_fold_binop): Add support for folding + BIT_AND_EXPR of compound_svalue and a mask constant. + * region-model.cc (region_model::get_rvalue_1): Implement + BIT_FIELD_REF in terms of... + (region_model::get_rvalue_for_bits): New function. + * region-model.h (region_model::get_rvalue_for_bits): New decl. + * store.cc (bit_range::from_mask): New function. + (selftest::test_bit_range_intersects_p): New selftest. + (selftest::assert_bit_range_from_mask_eq): New. + (ASSERT_BIT_RANGE_FROM_MASK_EQ): New macro. + (selftest::assert_no_bit_range_from_mask_eq): New. + (ASSERT_NO_BIT_RANGE_FROM_MASK): New macro. + (selftest::test_bit_range_from_mask): New selftest. + (selftest::analyzer_store_cc_tests): Call the new selftests. + * store.h (bit_range::intersects_p): New. + (bit_range::from_mask): New decl. + (concrete_binding::get_bit_range): New accessor. + (store_manager::get_concrete_binding): New overload taking + const bit_range &. + +2021-06-08 David Malcolm + + * analyzer.h (int_size_in_bits): New decl. + * region.cc (int_size_in_bits): New function. + (region::get_bit_size): Reimplement in terms of the above. + +2021-06-08 David Malcolm + + * store.cc (concrete_binding::dump_to_pp): Move bulk of + implementation to... + (bit_range::dump_to_pp): ...this new function. + (bit_range::cmp): New. + (concrete_binding::overlaps_p): Update for use of bit_range. + (concrete_binding::cmp_ptr_ptr): Likewise. + * store.h (struct bit_range): New. + (class concrete_binding): Replace fields m_start_bit_offset and + m_size_in_bits with new field m_bit_range. + +2021-06-08 David Malcolm + + * svalue.h (conjured_svalue::iterator_t): Delete. + 2021-06-03 David Malcolm * store.h (store::get_direct_binding): Remove unused decl. -- cgit v1.1 From 53cb324cb4f9475d4eabcd9f5a858c5edaacc0cf Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 9 Jun 2021 18:32:08 -0400 Subject: analyzer: make various region_model member functions const gcc/analyzer/ChangeLog: * region-model.cc (region_model::get_lvalue_1): Make const. (region_model::get_lvalue): Likewise. (region_model::get_rvalue_1): Likewise. (region_model::get_rvalue): Likewise. (region_model::deref_rvalue): Likewise. (region_model::get_rvalue_for_bits): Likewise. * region-model.h (region_model::get_lvalue): Likewise. (region_model::get_rvalue): Likewise. (region_model::deref_rvalue): Likewise. (region_model::get_rvalue_for_bits): Likewise. (region_model::get_lvalue_1): Likewise. (region_model::get_rvalue_1): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 16 ++++++++-------- gcc/analyzer/region-model.h | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 0d363fb..551ee79 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1213,7 +1213,7 @@ region_model::handle_phi (const gphi *phi, emitting any diagnostics to CTXT. */ const region * -region_model::get_lvalue_1 (path_var pv, region_model_context *ctxt) +region_model::get_lvalue_1 (path_var pv, region_model_context *ctxt) const { tree expr = pv.m_tree; @@ -1312,7 +1312,7 @@ assert_compat_types (tree src_type, tree dst_type) emitting any diagnostics to CTXT. */ const region * -region_model::get_lvalue (path_var pv, region_model_context *ctxt) +region_model::get_lvalue (path_var pv, region_model_context *ctxt) const { if (pv.m_tree == NULL_TREE) return NULL; @@ -1326,7 +1326,7 @@ region_model::get_lvalue (path_var pv, region_model_context *ctxt) recent stack frame if it's a local). */ const region * -region_model::get_lvalue (tree expr, region_model_context *ctxt) +region_model::get_lvalue (tree expr, region_model_context *ctxt) const { return get_lvalue (path_var (expr, get_stack_depth () - 1), ctxt); } @@ -1337,7 +1337,7 @@ region_model::get_lvalue (tree expr, region_model_context *ctxt) emitting any diagnostics to CTXT. */ const svalue * -region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) +region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) const { gcc_assert (pv.m_tree); @@ -1441,7 +1441,7 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) emitting any diagnostics to CTXT. */ const svalue * -region_model::get_rvalue (path_var pv, region_model_context *ctxt) +region_model::get_rvalue (path_var pv, region_model_context *ctxt) const { if (pv.m_tree == NULL_TREE) return NULL; @@ -1457,7 +1457,7 @@ region_model::get_rvalue (path_var pv, region_model_context *ctxt) recent stack frame if it's a local). */ const svalue * -region_model::get_rvalue (tree expr, region_model_context *ctxt) +region_model::get_rvalue (tree expr, region_model_context *ctxt) const { return get_rvalue (path_var (expr, get_stack_depth () - 1), ctxt); } @@ -1624,7 +1624,7 @@ region_model::region_exists_p (const region *reg) const const region * region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, - region_model_context *ctxt) + region_model_context *ctxt) const { gcc_assert (ptr_sval); gcc_assert (POINTER_TYPE_P (ptr_sval->get_type ())); @@ -1705,7 +1705,7 @@ region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, const svalue * region_model::get_rvalue_for_bits (tree type, const region *reg, - const bit_range &bits) + const bit_range &bits) const { const svalue *sval = get_store_value (reg); if (const compound_svalue *compound_sval = sval->dyn_cast_compound_svalue ()) diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 5e43e54..e251a5b 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -501,17 +501,17 @@ class region_model int get_stack_depth () const; const frame_region *get_frame_at_index (int index) const; - const region *get_lvalue (path_var pv, region_model_context *ctxt); - const region *get_lvalue (tree expr, region_model_context *ctxt); - const svalue *get_rvalue (path_var pv, region_model_context *ctxt); - const svalue *get_rvalue (tree expr, region_model_context *ctxt); + const region *get_lvalue (path_var pv, region_model_context *ctxt) const; + const region *get_lvalue (tree expr, region_model_context *ctxt) const; + const svalue *get_rvalue (path_var pv, region_model_context *ctxt) const; + const svalue *get_rvalue (tree expr, region_model_context *ctxt) const; const region *deref_rvalue (const svalue *ptr_sval, tree ptr_tree, - region_model_context *ctxt); + region_model_context *ctxt) const; const svalue *get_rvalue_for_bits (tree type, const region *reg, - const bit_range &bits); + const bit_range &bits) const; void set_value (const region *lhs_reg, const svalue *rhs_sval, region_model_context *ctxt); @@ -585,8 +585,8 @@ class region_model void loop_replay_fixup (const region_model *dst_state); private: - const region *get_lvalue_1 (path_var pv, region_model_context *ctxt); - const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt); + const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const; + const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const; path_var get_representative_path_var_1 (const svalue *sval, -- cgit v1.1 From 4f625f47b4456e5c05a025fca4d072831e59126c Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 10 Jun 2021 00:16:30 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index c3a3d39..f2061ac 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,18 @@ +2021-06-09 David Malcolm + + * region-model.cc (region_model::get_lvalue_1): Make const. + (region_model::get_lvalue): Likewise. + (region_model::get_rvalue_1): Likewise. + (region_model::get_rvalue): Likewise. + (region_model::deref_rvalue): Likewise. + (region_model::get_rvalue_for_bits): Likewise. + * region-model.h (region_model::get_lvalue): Likewise. + (region_model::get_rvalue): Likewise. + (region_model::deref_rvalue): Likewise. + (region_model::get_rvalue_for_bits): Likewise. + (region_model::get_lvalue_1): Likewise. + (region_model::get_rvalue_1): Likewise. + 2021-06-08 David Malcolm PR analyzer/99212 -- cgit v1.1 From 9d20ec97475b1102d6ca005ad165056d34615a3d Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 11 Jun 2021 09:30:33 -0400 Subject: analyzer: tweak priority of callstrings in worklist::key_t::cmp While debugging another issue I noticed that the analyzer could fail to merge nodes for control flow in which one path had called a function and another path hadn't: BB / \ / \ fn call no fn call \ / \ / join BB The root cause was that the worklist sort function wasn't prioritizing call strings, and thus it was fully exploring the "no function called" path to the exit BB, and only then exploring the "within the function call" parts of the "funcion called" path. This patch prioritizes call strings when sorting the worklist so that the nodes with deeper call strings are processed before those with shallower call strings, thus allowing such nodes to be merged at the joinpoint. gcc/analyzer/ChangeLog: * engine.cc (worklist::key_t::cmp): Move sort by call_string to before SCC. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/loop-0-up-to-n-by-1-with-iter-obj.c: Update expected number of enodes after the loop. * gcc.dg/analyzer/paths-8.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/engine.cc | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 5b519fd..48320bc 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -2004,7 +2004,25 @@ worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb) return cmp; } - /* First, order by SCC. */ + /* Sort by callstring, so that nodes with deeper call strings are processed + before those with shallower call strings. + If we have + splitting BB + / \ + / \ + fn call no fn call + \ / + \ / + join BB + then we want the path inside the function call to be fully explored up + to the return to the join BB before we explore on the "no fn call" path, + so that both enodes at the join BB reach the front of the worklist at + the same time and thus have a chance of being merged. */ + int cs_cmp = call_string::cmp (call_string_a, call_string_b); + if (cs_cmp) + return cs_cmp; + + /* Order by SCC. */ int scc_id_a = ka.get_scc_id (ka.m_enode); int scc_id_b = kb.get_scc_id (kb.m_enode); if (scc_id_a != scc_id_b) @@ -2033,11 +2051,6 @@ worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb) gcc_assert (snode_a == snode_b); - /* The points might vary by callstring; try sorting by callstring. */ - int cs_cmp = call_string::cmp (call_string_a, call_string_b); - if (cs_cmp) - return cs_cmp; - /* Order within supernode via program point. */ int within_snode_cmp = function_point::cmp_within_supernode (point_a.get_function_point (), -- cgit v1.1 From f16f65f8364b5bf23c72a8fdbba4974ecadc5cb6 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sat, 12 Jun 2021 00:16:27 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index f2061ac..da4f536 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,8 @@ +2021-06-11 David Malcolm + + * engine.cc (worklist::key_t::cmp): Move sort by call_string to + before SCC. + 2021-06-09 David Malcolm * region-model.cc (region_model::get_lvalue_1): Make const. -- cgit v1.1 From 3f207ab314c071c6060c7c9a429fcf29fd87b594 Mon Sep 17 00:00:00 2001 From: Trevor Saunders Date: Fri, 11 Jun 2021 23:49:22 -0400 Subject: use range based for loops to iterate over vec<> This changes users of FOR_EACH_VEC_ELT to use range based for loops, where the index variables are otherwise unused. As such the index variables are all deleted, producing shorter and simpler code. Signed-off-by: Trevor Saunders gcc/analyzer/ChangeLog: * call-string.cc (call_string::call_string): Use range based for to iterate over vec<>. (call_string::to_json): Likewise. (call_string::hash): Likewise. (call_string::calc_recursion_depth): Likewise. * checker-path.cc (checker_path::fixup_locations): Likewise. * constraint-manager.cc (equiv_class::equiv_class): Likewise. (equiv_class::to_json): Likewise. (equiv_class::hash): Likewise. (constraint_manager::to_json): Likewise. * engine.cc (impl_region_model_context::on_svalue_leak): Likewise. (on_liveness_change): Likewise. (impl_region_model_context::on_unknown_change): Likewise. * program-state.cc (sm_state_map::set_state): Likewise. * region-model.cc (test_canonicalization_4): Likewise. gcc/ChangeLog: * attribs.c (find_attribute_namespace): Iterate over vec<> with range based for. * auto-profile.c (afdo_find_equiv_class): Likewise. * gcc.c (do_specs_vec): Likewise. (do_spec_1): Likewise. (driver::set_up_specs): Likewise. * gimple-loop-jam.c (any_access_function_variant_p): Likewise. * gimple-ssa-store-merging.c (compatible_load_p): Likewise. (imm_store_chain_info::try_coalesce_bswap): Likewise. (imm_store_chain_info::coalesce_immediate_stores): Likewise. (get_location_for_stmts): Likewise. * graphite-poly.c (print_iteration_domains): Likewise. (free_poly_bb): Likewise. (remove_gbbs_in_scop): Likewise. (free_scop): Likewise. (dump_gbb_cases): Likewise. (dump_gbb_conditions): Likewise. (print_pdrs): Likewise. (print_scop): Likewise. * ifcvt.c (cond_move_process_if_block): Likewise. * lower-subreg.c (decompose_multiword_subregs): Likewise. * regcprop.c (pass_cprop_hardreg::execute): Likewise. * sanopt.c (sanitize_rewrite_addressable_params): Likewise. * sel-sched-dump.c (dump_insn_vector): Likewise. * store-motion.c (store_ops_ok): Likewise. (store_killed_in_insn): Likewise. * timevar.c (timer::named_items::print): Likewise. * tree-cfgcleanup.c (cleanup_control_flow_pre): Likewise. (cleanup_tree_cfg_noloop): Likewise. * tree-data-ref.c (dump_data_references): Likewise. (print_dir_vectors): Likewise. (print_dist_vectors): Likewise. (dump_data_dependence_relations): Likewise. (dump_dist_dir_vectors): Likewise. (dump_ddrs): Likewise. (create_runtime_alias_checks): Likewise. (free_subscripts): Likewise. (save_dist_v): Likewise. (save_dir_v): Likewise. (invariant_access_functions): Likewise. (same_access_functions): Likewise. (access_functions_are_affine_or_constant_p): Likewise. (find_data_references_in_stmt): Likewise. (graphite_find_data_references_in_stmt): Likewise. (free_dependence_relations): Likewise. (free_data_refs): Likewise. * tree-inline.c (copy_debug_stmts): Likewise. * tree-into-ssa.c (dump_currdefs): Likewise. (rewrite_update_phi_arguments): Likewise. * tree-ssa-propagate.c (clean_up_loop_closed_phi): Likewise. * tree-vect-data-refs.c (vect_analyze_possibly_independent_ddr): Likewise. (vect_slp_analyze_node_dependences): Likewise. (vect_slp_analyze_instance_dependence): Likewise. (vect_record_base_alignments): Likewise. (vect_get_peeling_costs_all_drs): Likewise. (vect_peeling_supportable): Likewise. * tree-vectorizer.c (vec_info::~vec_info): Likewise. (vec_info::free_stmt_vec_infos): Likewise. gcc/cp/ChangeLog: * constexpr.c (cxx_eval_call_expression): Iterate over vec<> with range based for. (cxx_eval_store_expression): Likewise. (cxx_eval_loop_expr): Likewise. * decl.c (wrapup_namespace_globals): Likewise. (cp_finish_decl): Likewise. (cxx_simulate_enum_decl): Likewise. * parser.c (cp_parser_postfix_expression): Likewise. --- gcc/analyzer/call-string.cc | 16 ++++------------ gcc/analyzer/checker-path.cc | 4 +--- gcc/analyzer/constraint-manager.cc | 22 ++++++---------------- gcc/analyzer/engine.cc | 12 +++--------- gcc/analyzer/program-state.cc | 4 +--- gcc/analyzer/region-model.cc | 4 +--- 6 files changed, 16 insertions(+), 46 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc index 224b2e2..9f4f77a 100644 --- a/gcc/analyzer/call-string.cc +++ b/gcc/analyzer/call-string.cc @@ -50,9 +50,7 @@ along with GCC; see the file COPYING3. If not see call_string::call_string (const call_string &other) : m_return_edges (other.m_return_edges.length ()) { - const return_superedge *e; - int i; - FOR_EACH_VEC_ELT (other.m_return_edges, i, e) + for (const return_superedge *e : other.m_return_edges) m_return_edges.quick_push (e); } @@ -118,9 +116,7 @@ call_string::to_json () const { json::array *arr = new json::array (); - const return_superedge *e; - int i; - FOR_EACH_VEC_ELT (m_return_edges, i, e) + for (const return_superedge *e : m_return_edges) { json::object *e_obj = new json::object (); e_obj->set ("src_snode_idx", @@ -141,9 +137,7 @@ hashval_t call_string::hash () const { inchash::hash hstate; - int i; - const return_superedge *e; - FOR_EACH_VEC_ELT (m_return_edges, i, e) + for (const return_superedge *e : m_return_edges) hstate.add_ptr (e); return hstate.end (); } @@ -173,9 +167,7 @@ call_string::calc_recursion_depth () const = m_return_edges[m_return_edges.length () - 1]; int result = 0; - const return_superedge *e; - int i; - FOR_EACH_VEC_ELT (m_return_edges, i, e) + for (const return_superedge *e : m_return_edges) if (e == top_return_sedge) ++result; return result; diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index 7d229bb..e6f838b 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -1001,9 +1001,7 @@ checker_path::add_final_event (const state_machine *sm, void checker_path::fixup_locations (pending_diagnostic *pd) { - checker_event *e; - int i; - FOR_EACH_VEC_ELT (m_events, i, e) + for (checker_event *e : m_events) e->set_location (pd->fixup_location (e->get_location ())); } diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index 4dadd20..51cf522 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -270,9 +270,7 @@ equiv_class::equiv_class (const equiv_class &other) : m_constant (other.m_constant), m_cst_sval (other.m_cst_sval), m_vars (other.m_vars.length ()) { - int i; - const svalue *sval; - FOR_EACH_VEC_ELT (other.m_vars, i, sval) + for (const svalue *sval : other.m_vars) m_vars.quick_push (sval); } @@ -310,9 +308,7 @@ equiv_class::to_json () const json::object *ec_obj = new json::object (); json::array *sval_arr = new json::array (); - int i; - const svalue *sval; - FOR_EACH_VEC_ELT (m_vars, i, sval) + for (const svalue *sval : m_vars) sval_arr->append (sval->to_json ()); ec_obj->set ("svals", sval_arr); @@ -337,9 +333,7 @@ equiv_class::hash () const inchash::hash hstate; inchash::add_expr (m_constant, hstate); - int i; - const svalue *sval; - FOR_EACH_VEC_ELT (m_vars, i, sval) + for (const svalue * sval : m_vars) hstate.add_ptr (sval); return hstate.end (); } @@ -811,9 +805,7 @@ constraint_manager::to_json () const /* Equivalence classes. */ { json::array *ec_arr = new json::array (); - int i; - equiv_class *ec; - FOR_EACH_VEC_ELT (m_equiv_classes, i, ec) + for (const equiv_class *ec : m_equiv_classes) ec_arr->append (ec->to_json ()); cm_obj->set ("ecs", ec_arr); } @@ -821,10 +813,8 @@ constraint_manager::to_json () const /* Constraints. */ { json::array *con_arr = new json::array (); - int i; - constraint *c; - FOR_EACH_VEC_ELT (m_constraints, i, c) - con_arr->append (c->to_json ()); + for (const constraint &c : m_constraints) + con_arr->append (c.to_json ()); cm_obj->set ("constraints", con_arr); } diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 48320bc..50652b2 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -121,9 +121,7 @@ void impl_region_model_context::on_svalue_leak (const svalue *sval) { - int sm_idx; - sm_state_map *smap; - FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap) + for (sm_state_map *smap : m_new_state->m_checker_states) smap->on_svalue_leak (sval, this); } @@ -132,9 +130,7 @@ impl_region_model_context:: on_liveness_change (const svalue_set &live_svalues, const region_model *model) { - int sm_idx; - sm_state_map *smap; - FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap) + for (sm_state_map *smap : m_new_state->m_checker_states) smap->on_liveness_change (live_svalues, model, this); } @@ -142,9 +138,7 @@ void impl_region_model_context::on_unknown_change (const svalue *sval, bool is_mutable) { - int sm_idx; - sm_state_map *smap; - FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap) + for (sm_state_map *smap : m_new_state->m_checker_states) smap->on_unknown_change (sval, is_mutable, m_ext_state); } diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 5c690b0..76959c1 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -441,10 +441,8 @@ sm_state_map::set_state (const equiv_class &ec, const svalue *origin, const extrinsic_state &ext_state) { - int i; - const svalue *sval; bool any_changed = false; - FOR_EACH_VEC_ELT (ec.m_vars, i, sval) + for (const svalue *sval : ec.m_vars) any_changed |= impl_set_state (sval, state, origin, ext_state); return any_changed; } diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 551ee79..4b9620d 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -4294,9 +4294,7 @@ test_canonicalization_4 () region_model_manager mgr; region_model model (&mgr); - unsigned i; - tree cst; - FOR_EACH_VEC_ELT (csts, i, cst) + for (tree cst : csts) model.get_rvalue (cst, NULL); model.canonicalize (); -- cgit v1.1 From 4e70c34e5ce6fefba22015bd4fe0df33a13567d4 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Mon, 14 Jun 2021 00:16:35 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index da4f536..400175a 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,22 @@ +2021-06-13 Trevor Saunders + + * call-string.cc (call_string::call_string): Use range based for + to iterate over vec<>. + (call_string::to_json): Likewise. + (call_string::hash): Likewise. + (call_string::calc_recursion_depth): Likewise. + * checker-path.cc (checker_path::fixup_locations): Likewise. + * constraint-manager.cc (equiv_class::equiv_class): Likewise. + (equiv_class::to_json): Likewise. + (equiv_class::hash): Likewise. + (constraint_manager::to_json): Likewise. + * engine.cc (impl_region_model_context::on_svalue_leak): + Likewise. + (on_liveness_change): Likewise. + (impl_region_model_context::on_unknown_change): Likewise. + * program-state.cc (sm_state_map::set_state): Likewise. + * region-model.cc (test_canonicalization_4): Likewise. + 2021-06-11 David Malcolm * engine.cc (worklist::key_t::cmp): Move sort by call_string to -- cgit v1.1 From d726a57b993e00294891e2a05d5868c89bb75b76 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 15 Jun 2021 09:30:18 -0400 Subject: analyzer: add class region_to_value_map Add a class for associating symbolic values with regions, for use initially for recording the sizes of dynamically-allocated regions, though this also could potentially be used for e.g. tracking strlen() values. gcc/analyzer/ChangeLog: * region-model.cc (region_to_value_map::operator=): New. (region_to_value_map::operator==): New. (region_to_value_map::dump_to_pp): New. (region_to_value_map::dump): New. (region_to_value_map::can_merge_with_p): New. * region-model.h (class region_to_value_map): New class. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 110 +++++++++++++++++++++++++++++++++++++++++++ gcc/analyzer/region-model.h | 45 ++++++++++++++++++ 2 files changed, 155 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 4b9620d..43f991a 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -110,6 +110,116 @@ print_quoted_type (pretty_printer *pp, tree t) pp_end_quote (pp, pp_show_color (pp)); } +/* class region_to_value_map. */ + +/* Assignment operator for region_to_value_map. */ + +region_to_value_map & +region_to_value_map::operator= (const region_to_value_map &other) +{ + m_hash_map.empty (); + for (auto iter : other.m_hash_map) + { + const region *reg = iter.first; + const svalue *sval = iter.second; + m_hash_map.put (reg, sval); + } + return *this; +} + +/* Equality operator for region_to_value_map. */ + +bool +region_to_value_map::operator== (const region_to_value_map &other) const +{ + if (m_hash_map.elements () != other.m_hash_map.elements ()) + return false; + + for (auto iter : *this) + { + const region *reg = iter.first; + const svalue *sval = iter.second; + const svalue * const *other_slot = other.get (reg); + if (other_slot == NULL) + return false; + if (sval != *other_slot) + return false; + } + + return true; +} + +/* Dump this object to PP. */ + +void +region_to_value_map::dump_to_pp (pretty_printer *pp, bool simple, + bool multiline) const +{ + auto_vec regs; + for (iterator iter = begin (); iter != end (); ++iter) + regs.safe_push ((*iter).first); + regs.qsort (region::cmp_ptr_ptr); + if (multiline) + pp_newline (pp); + else + pp_string (pp, " {"); + unsigned i; + const region *reg; + FOR_EACH_VEC_ELT (regs, i, reg) + { + if (multiline) + pp_string (pp, " "); + else if (i > 0) + pp_string (pp, ", "); + reg->dump_to_pp (pp, simple); + pp_string (pp, ": "); + const svalue *sval = *get (reg); + sval->dump_to_pp (pp, true); + if (multiline) + pp_newline (pp); + } + if (!multiline) + pp_string (pp, "}"); +} + +/* Dump this object to stderr. */ + +DEBUG_FUNCTION void +region_to_value_map::dump (bool simple) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, simple, true); + pp_newline (&pp); + pp_flush (&pp); +} + + +/* Attempt to merge THIS with OTHER, writing the result + to OUT. + + For now, write (region, value) mappings that are in common between THIS + and OTHER to OUT, effectively taking the intersection, rather than + rejecting differences. */ + +bool +region_to_value_map::can_merge_with_p (const region_to_value_map &other, + region_to_value_map *out) const +{ + for (auto iter : *this) + { + const region *iter_reg = iter.first; + const svalue *iter_sval = iter.second; + const svalue * const * other_slot = other.get (iter_reg); + if (other_slot) + if (iter_sval == *other_slot) + out->put (iter_reg, iter_sval); + } + return true; +} + /* class region_model. */ /* Ctor for region_model: construct an "empty" model. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index e251a5b..0afcb86 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -128,6 +128,51 @@ one_way_id_map::update (T *id) const *id = get_dst_for_src (*id); } +/* A mapping from region to svalue for use when tracking state. */ + +class region_to_value_map +{ +public: + typedef hash_map hash_map_t; + typedef hash_map_t::iterator iterator; + + region_to_value_map () : m_hash_map () {} + region_to_value_map (const region_to_value_map &other) + : m_hash_map (other.m_hash_map) {} + region_to_value_map &operator= (const region_to_value_map &other); + + bool operator== (const region_to_value_map &other) const; + bool operator!= (const region_to_value_map &other) const + { + return !(*this == other); + } + + iterator begin () const { return m_hash_map.begin (); } + iterator end () const { return m_hash_map.end (); } + + const svalue * const *get (const region *reg) const + { + return const_cast (m_hash_map).get (reg); + } + void put (const region *reg, const svalue *sval) + { + m_hash_map.put (reg, sval); + } + void remove (const region *reg) + { + m_hash_map.remove (reg); + } + + void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const; + void dump (bool simple) const; + + bool can_merge_with_p (const region_to_value_map &other, + region_to_value_map *out) const; + +private: + hash_map_t m_hash_map; +}; + /* Various operations delete information from a region_model. This struct tracks how many of each kind of entity were purged (e.g. -- cgit v1.1 From 9a2c9579fdbf5d24dfe27fb961286ad7a9c3a98b Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 15 Jun 2021 09:31:26 -0400 Subject: analyzer: track dynamic extents of regions This patch extends region_model to add tracking of the sizes of dynamically-allocated regions, both on the heap (via malloc etc) and stack (via alloca). It adds enough purging of this state to avoid blowing up any existing analyzer test cases. The state can be queried via a new "__analyzer_dump_capacity" for use in DejaGnu tests but other than that doesn't do anything - I have various followup experiments that make use of this. gcc/analyzer/ChangeLog: * engine.cc (exploded_node::on_stmt): Handle __analyzer_dump_capacity. (exploded_node::on_stmt): Drop m_sm_changes from on_stmt_flags. (state_change_requires_new_enode_p): New function... (exploded_graph::process_node): Call it, rather than querying flags.m_sm_changes, so that dynamic-extent differences can also trigger the splitting of nodes. * exploded-graph.h (struct on_stmt_flags): Drop field m_sm_changes. * program-state.cc (program_state::detect_leaks): Purge dead heap-allocated regions from dynamic extents. (selftest::test_program_state_1): Fix type of "size_in_bytes". (selftest::test_program_state_merging): Likewise. * region-model-impl-calls.cc (region_model::impl_call_analyzer_dump_capacity): New. (region_model::impl_call_free): Remove dynamic extents from the freed region. * region-model-reachability.h (reachable_regions::begin_mutable_base_regs): New. (reachable_regions::end_mutable_base_regs): New. * region-model.cc: Include "tree-object-size.h". (region_model::region_model): Support new field m_dynamic_extents. (region_model::operator=): Likewise. (region_model::operator==): Likewise. (region_model::dump_to_pp): Dump sizes of dynamic regions. (region_model::handle_unrecognized_call): Purge dynamic extents from any regions that have escaped mutably:. (region_model::get_capacity): New function. (region_model::add_constraint): Unset dynamic extents when a heap-allocated region's address is NULL. (region_model::unbind_region_and_descendents): Purge dynamic extents of unbound regions. (region_model::can_merge_with_p): Call m_dynamic_extents.can_merge_with_p. (region_model::create_region_for_heap_alloc): Assert that size_in_bytes's type is compatible with size_type_node. Update for renaming of record_dynamic_extents to set_dynamic_extents. (region_model::create_region_for_alloca): Likewise. (region_model::record_dynamic_extents): Rename to... (region_model::set_dynamic_extents): ...this. Assert that size_in_bytes's type is compatible with size_type_node. Add it to the m_dynamic_extents map. (region_model::get_dynamic_extents): New. (region_model::unset_dynamic_extents): New. (selftest::test_state_merging): Fix type of "size". (selftest::test_malloc_constraints): Likewise. (selftest::test_malloc): Verify dynamic extents. (selftest::test_alloca): Likewise. * region-model.h (region_to_value_map::is_empty): New. (region_model::dynamic_extents_t): New typedef. (region_model::impl_call_analyzer_dump_capacity): New decl. (region_model::get_dynamic_extents): New function. (region_model::get_dynamic_extents): New decl. (region_model::set_dynamic_extents): New decl. (region_model::unset_dynamic_extents): New decl. (region_model::get_capacity): New decl. (region_model::record_dynamic_extents): Rename to set_dynamic_extents. (region_model::m_dynamic_extents): New field. gcc/ChangeLog: * doc/analyzer.texi (Special Functions for Debugging the Analyzer): Add __analyzer_dump_capacity. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/analyzer-decls.h (__analyzer_dump_capacity): New decl. * gcc.dg/analyzer/capacity-1.c: New test. * gcc.dg/analyzer/capacity-2.c: New test. * gcc.dg/analyzer/capacity-3.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/engine.cc | 40 ++++++++-- gcc/analyzer/exploded-graph.h | 20 +---- gcc/analyzer/program-state.cc | 13 +++- gcc/analyzer/region-model-impl-calls.cc | 20 +++++ gcc/analyzer/region-model-reachability.h | 8 ++ gcc/analyzer/region-model.cc | 130 +++++++++++++++++++++++++++---- gcc/analyzer/region-model.h | 33 ++++++-- 7 files changed, 222 insertions(+), 42 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 50652b2..df04b0b 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1185,6 +1185,8 @@ exploded_node::on_stmt (exploded_graph &eg, 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 @@ -1237,7 +1239,6 @@ exploded_node::on_stmt (exploded_graph &eg, if (terminate_path) return on_stmt_flags::terminate_path (); - bool any_sm_changes = false; int sm_idx; sm_state_map *smap; FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap) @@ -1276,14 +1277,12 @@ exploded_node::on_stmt (exploded_graph &eg, /* Allow the state_machine to handle the stmt. */ if (sm.on_stmt (&sm_ctxt, snode, stmt)) unknown_side_effects = false; - if (*old_smap != *new_smap) - any_sm_changes = true; } if (const gcall *call = dyn_cast (stmt)) state->m_region_model->on_call_post (call, unknown_side_effects, &ctxt); - return on_stmt_flags (any_sm_changes); + return on_stmt_flags (); } /* Consider the effect of following superedge SUCC from this node. @@ -2925,6 +2924,36 @@ stmt_requires_new_enode_p (const gimple *stmt, return false; } +/* Return true if OLD_STATE and NEW_STATE are sufficiently different that + we should split enodes and create an exploded_edge separating them + (which makes it easier to identify state changes of intereset when + constructing checker_paths). */ + +static bool +state_change_requires_new_enode_p (const program_state &old_state, + const program_state &new_state) +{ + /* Changes in dynamic extents signify creations of heap/alloca regions + and resizings of heap regions; likely to be of interest in + diagnostic paths. */ + if (old_state.m_region_model->get_dynamic_extents () + != new_state.m_region_model->get_dynamic_extents ()) + return true; + + /* Changes in sm-state are of interest. */ + int sm_idx; + sm_state_map *smap; + FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap) + { + const sm_state_map *old_smap = old_state.m_checker_states[sm_idx]; + const sm_state_map *new_smap = new_state.m_checker_states[sm_idx]; + if (*old_smap != *new_smap) + return true; + } + + return false; +} + /* The core of exploded_graph::process_worklist (the main analysis loop), handling one node in the worklist. @@ -3067,7 +3096,8 @@ exploded_graph::process_node (exploded_node *node) next_state = next_state.prune_for_point (*this, next_point, node, &uncertainty); - if (flags.m_sm_changes || flag_analyzer_fine_grained) + if (flag_analyzer_fine_grained + || state_change_requires_new_enode_p (old_state, next_state)) { program_point split_point = program_point::before_stmt (point.get_supernode (), diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index c67f7b7..eb1baef 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -198,33 +198,21 @@ class exploded_node : public dnode /* The result of on_stmt. */ struct on_stmt_flags { - on_stmt_flags (bool sm_changes) - : m_sm_changes (sm_changes), - m_terminate_path (false) + on_stmt_flags () : m_terminate_path (false) {} static on_stmt_flags terminate_path () { - return on_stmt_flags (true, true); + return on_stmt_flags (true); } - static on_stmt_flags state_change (bool any_sm_changes) - { - return on_stmt_flags (any_sm_changes, false); - } - - /* Did any sm-changes occur handling the stmt. */ - bool m_sm_changes : 1; - /* Should we stop analyzing this path (on_stmt may have already added nodes/edges, e.g. when handling longjmp). */ bool m_terminate_path : 1; private: - on_stmt_flags (bool sm_changes, - bool terminate_path) - : m_sm_changes (sm_changes), - m_terminate_path (terminate_path) + on_stmt_flags (bool terminate_path) + : m_terminate_path (terminate_path) {} }; diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 76959c1..67dd785 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1270,6 +1270,15 @@ program_state::detect_leaks (const program_state &src_state, /* Purge dead svals from constraints. */ dest_state.m_region_model->get_constraints ()->on_liveness_change (maybe_dest_svalues, dest_state.m_region_model); + + /* Purge dead heap-allocated regions from dynamic extents. */ + for (const svalue *sval : dead_svals) + if (const region_svalue *region_sval = sval->dyn_cast_region_svalue ()) + { + const region *reg = region_sval->get_pointee (); + if (reg->get_kind () == RK_HEAP_ALLOCATED) + dest_state.m_region_model->unset_dynamic_extents (reg); + } } #if CHECKING_P @@ -1426,7 +1435,7 @@ test_program_state_1 () program_state s (ext_state); region_model *model = s.m_region_model; const svalue *size_in_bytes - = mgr->get_or_create_unknown_svalue (integer_type_node); + = mgr->get_or_create_unknown_svalue (size_type_node); const region *new_reg = model->create_region_for_heap_alloc (size_in_bytes); const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg); model->set_value (model->get_lvalue (p, NULL), @@ -1482,7 +1491,7 @@ test_program_state_merging () region_model *model0 = s0.m_region_model; const svalue *size_in_bytes - = mgr->get_or_create_unknown_svalue (integer_type_node); + = mgr->get_or_create_unknown_svalue (size_type_node); const region *new_reg = model0->create_region_for_heap_alloc (size_in_bytes); const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg); model0->set_value (model0->get_lvalue (p, &ctxt), diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 4052bb39..099520a 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -206,6 +206,25 @@ region_model::impl_call_analyzer_describe (const gcall *call, warning_at (call->location, 0, "svalue: %qs", desc.m_buffer); } +/* Handle a call to "__analyzer_dump_capacity". + + Emit a warning describing the capacity of the base region of + the region pointed to by the 1st argument. + This is for use when debugging, and may be of use in DejaGnu tests. */ + +void +region_model::impl_call_analyzer_dump_capacity (const gcall *call, + region_model_context *ctxt) +{ + tree t_ptr = gimple_call_arg (call, 0); + const svalue *sval_ptr = get_rvalue (t_ptr, ctxt); + const region *reg = deref_rvalue (sval_ptr, t_ptr, ctxt); + const region *base_reg = reg->get_base_region (); + const svalue *capacity = get_capacity (base_reg); + label_text desc = capacity->get_desc (true); + warning_at (call->location, 0, "capacity: %qs", desc.m_buffer); +} + /* Handle a call to "__analyzer_eval" by evaluating the input and dumping as a dummy warning, so that test cases can use dg-warning to validate the result (and so unexpected warnings will @@ -312,6 +331,7 @@ region_model::impl_call_free (const call_details &cd) poisoning pointers. */ const region *freed_reg = ptr_to_region_sval->get_pointee (); unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); + m_dynamic_extents.remove (freed_reg); } } diff --git a/gcc/analyzer/region-model-reachability.h b/gcc/analyzer/region-model-reachability.h index c6a21e9..57daf72 100644 --- a/gcc/analyzer/region-model-reachability.h +++ b/gcc/analyzer/region-model-reachability.h @@ -89,6 +89,14 @@ public: { return m_mutable_svals.end (); } + hash_set::iterator begin_mutable_base_regs () + { + return m_mutable_base_regs.begin (); + } + hash_set::iterator end_mutable_base_regs () + { + return m_mutable_base_regs.end (); + } void dump_to_pp (pretty_printer *pp) const; diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 43f991a..e02a897 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -66,6 +66,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/analyzer-selftests.h" #include "stor-layout.h" #include "attribs.h" +#include "tree-object-size.h" #if ENABLE_ANALYZER @@ -225,7 +226,8 @@ region_to_value_map::can_merge_with_p (const region_to_value_map &other, /* Ctor for region_model: construct an "empty" model. */ region_model::region_model (region_model_manager *mgr) -: m_mgr (mgr), m_store (), m_current_frame (NULL) +: m_mgr (mgr), m_store (), m_current_frame (NULL), + m_dynamic_extents () { m_constraints = new constraint_manager (mgr); } @@ -235,7 +237,8 @@ region_model::region_model (region_model_manager *mgr) region_model::region_model (const region_model &other) : m_mgr (other.m_mgr), m_store (other.m_store), m_constraints (new constraint_manager (*other.m_constraints)), - m_current_frame (other.m_current_frame) + m_current_frame (other.m_current_frame), + m_dynamic_extents (other.m_dynamic_extents) { } @@ -261,6 +264,8 @@ region_model::operator= (const region_model &other) m_current_frame = other.m_current_frame; + m_dynamic_extents = other.m_dynamic_extents; + return *this; } @@ -285,6 +290,9 @@ region_model::operator== (const region_model &other) const if (m_current_frame != other.m_current_frame) return false; + if (m_dynamic_extents != other.m_dynamic_extents) + return false; + gcc_checking_assert (hash () == other.hash ()); return true; @@ -346,6 +354,13 @@ region_model::dump_to_pp (pretty_printer *pp, bool simple, m_constraints->dump_to_pp (pp, multiline); if (!multiline) pp_string (pp, "}"); + + /* Dump sizes of dynamic regions, if any are known. */ + if (!m_dynamic_extents.is_empty ()) + { + pp_string (pp, "dynamic_extents:"); + m_dynamic_extents.dump_to_pp (pp, simple, multiline); + } } /* Dump a representation of this model to FILE. */ @@ -1140,6 +1155,17 @@ region_model::handle_unrecognized_call (const gcall *call, /* Update bindings for all clusters that have escaped, whether above, or previously. */ m_store.on_unknown_fncall (call, m_mgr->get_store_manager ()); + + /* Purge dynamic extents from any regions that have escaped mutably: + realloc could have been called on them. */ + for (hash_set::iterator + iter = reachable_regs.begin_mutable_base_regs (); + iter != reachable_regs.end_mutable_base_regs (); + ++iter) + { + const region *base_reg = (*iter); + unset_dynamic_extents (base_reg); + } } /* Traverse the regions in this model, determining what regions are @@ -1972,6 +1998,41 @@ region_model::check_for_writable_region (const region* dest_reg, } } +/* Get the capacity of REG in bytes. */ + +const svalue * +region_model::get_capacity (const region *reg) const +{ + switch (reg->get_kind ()) + { + default: + break; + case RK_DECL: + { + const decl_region *decl_reg = as_a (reg); + tree decl = decl_reg->get_decl (); + if (TREE_CODE (decl) == SSA_NAME) + { + tree type = TREE_TYPE (decl); + tree size = TYPE_SIZE (type); + return get_rvalue (size, NULL); + } + else + { + tree size = decl_init_size (decl, false); + if (size) + return get_rvalue (size, NULL); + } + } + break; + } + + if (const svalue *recorded = get_dynamic_extents (reg)) + return recorded; + + return m_mgr->get_or_create_unknown_svalue (sizetype); +} + /* Set the value of the region given by LHS_REG to the value given by RHS_SVAL. */ @@ -2241,6 +2302,12 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, if (ctxt) ctxt->on_condition (lhs, op, rhs); + /* If we have ®ION == NULL, then drop dynamic extents for REGION (for + the case where REGION is heap-allocated and thus could be NULL). */ + if (op == EQ_EXPR && zerop (rhs)) + if (const region_svalue *region_sval = lhs_sval->dyn_cast_region_svalue ()) + unset_dynamic_extents (region_sval->get_pointee ()); + return true; } @@ -3146,7 +3213,8 @@ region_model::get_frame_at_index (int index) const /* Unbind svalues for any regions in REG and below. Find any pointers to such regions; convert them to - poisoned values of kind PKIND. */ + poisoned values of kind PKIND. + Also purge any dynamic extents. */ void region_model::unbind_region_and_descendents (const region *reg, @@ -3167,6 +3235,15 @@ region_model::unbind_region_and_descendents (const region *reg, /* Find any pointers to REG or its descendents; convert to poisoned. */ poison_any_pointers_to_descendents (reg, pkind); + + /* Purge dynamic extents of any base regions in REG and below + (e.g. VLAs and alloca stack regions). */ + for (auto iter : m_dynamic_extents) + { + const region *iter_reg = iter.first; + if (iter_reg->descendent_of_p (reg)) + unset_dynamic_extents (iter_reg); + } } /* Implementation of BindingVisitor. @@ -3241,6 +3318,10 @@ region_model::can_merge_with_p (const region_model &other_model, &m)) return false; + if (!m_dynamic_extents.can_merge_with_p (other_model.m_dynamic_extents, + &out_model->m_dynamic_extents)) + return false; + /* Merge constraints. */ constraint_manager::merge (*m_constraints, *other_model.m_constraints, @@ -3322,7 +3403,8 @@ const region * region_model::create_region_for_heap_alloc (const svalue *size_in_bytes) { const region *reg = m_mgr->create_region_for_heap_alloc (); - record_dynamic_extents (reg, size_in_bytes); + assert_compat_types (size_in_bytes->get_type (), size_type_node); + set_dynamic_extents (reg, size_in_bytes); return reg; } @@ -3333,18 +3415,38 @@ const region * region_model::create_region_for_alloca (const svalue *size_in_bytes) { const region *reg = m_mgr->create_region_for_alloca (m_current_frame); - record_dynamic_extents (reg, size_in_bytes); + assert_compat_types (size_in_bytes->get_type (), size_type_node); + set_dynamic_extents (reg, size_in_bytes); return reg; } -/* Placeholder hook for recording that the size of REG is SIZE_IN_BYTES. - Currently does nothing. */ +/* Record that the size of REG is SIZE_IN_BYTES. */ void -region_model:: -record_dynamic_extents (const region *reg ATTRIBUTE_UNUSED, - const svalue *size_in_bytes ATTRIBUTE_UNUSED) +region_model::set_dynamic_extents (const region *reg, + const svalue *size_in_bytes) +{ + assert_compat_types (size_in_bytes->get_type (), size_type_node); + m_dynamic_extents.put (reg, size_in_bytes); +} + +/* Get the recording of REG in bytes, or NULL if no dynamic size was + recorded. */ + +const svalue * +region_model::get_dynamic_extents (const region *reg) const { + if (const svalue * const *slot = m_dynamic_extents.get (reg)) + return *slot; + return NULL; +} + +/* Unset any recorded dynamic size of REG. */ + +void +region_model::unset_dynamic_extents (const region *reg) +{ + m_dynamic_extents.remove (reg); } /* struct model_merger. */ @@ -4644,7 +4746,7 @@ test_state_merging () { test_region_model_context ctxt; region_model model0 (&mgr); - tree size = build_int_cst (integer_type_node, 1024); + tree size = build_int_cst (size_type_node, 1024); const svalue *size_sval = mgr.get_or_create_constant_svalue (size); const region *new_reg = model0.create_region_for_heap_alloc (size_sval); const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg); @@ -5034,7 +5136,7 @@ test_malloc_constraints () tree null_ptr = build_int_cst (ptr_type_node, 0); const svalue *size_in_bytes - = mgr.get_or_create_unknown_svalue (integer_type_node); + = mgr.get_or_create_unknown_svalue (size_type_node); const region *reg = model.create_region_for_heap_alloc (size_in_bytes); const svalue *sval = mgr.get_ptr_svalue (ptr_type_node, reg); model.set_value (model.get_lvalue (p, NULL), sval, NULL); @@ -5259,7 +5361,7 @@ test_malloc () const region *reg = model.create_region_for_heap_alloc (size_sval); const svalue *ptr = mgr.get_ptr_svalue (int_star, reg); model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt); - // TODO: verify dynamic extents + ASSERT_EQ (model.get_capacity (reg), size_sval); } /* Verify that alloca works. */ @@ -5294,7 +5396,7 @@ test_alloca () ASSERT_EQ (reg->get_parent_region (), frame_reg); const svalue *ptr = mgr.get_ptr_svalue (int_star, reg); model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt); - // TODO: verify dynamic extents + ASSERT_EQ (model.get_capacity (reg), size_sval); /* Verify that the pointers to the alloca region are replaced by poisoned values when the frame is popped. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 0afcb86..8b669df 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -163,6 +163,8 @@ public: m_hash_map.remove (reg); } + bool is_empty () const { return m_hash_map.is_empty (); } + void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const; void dump (bool simple) const; @@ -450,12 +452,16 @@ private: a tree of regions, along with their associated values. The representation is graph-like because values can be pointers to regions. - It also stores a constraint_manager, capturing relationships between - the values. */ + It also stores: + - a constraint_manager, capturing relationships between the values, and + - dynamic extents, mapping dynamically-allocated regions to svalues (their + capacities). */ class region_model { public: + typedef region_to_value_map dynamic_extents_t; + region_model (region_model_manager *mgr); region_model (const region_model &other); ~region_model (); @@ -495,6 +501,8 @@ class region_model bool impl_call_alloca (const call_details &cd); void impl_call_analyzer_describe (const gcall *call, region_model_context *ctxt); + void impl_call_analyzer_dump_capacity (const gcall *call, + region_model_context *ctxt); void impl_call_analyzer_eval (const gcall *call, region_model_context *ctxt); bool impl_call_builtin_expect (const call_details &cd); @@ -606,6 +614,16 @@ class region_model store *get_store () { return &m_store; } const store *get_store () const { return &m_store; } + const dynamic_extents_t & + get_dynamic_extents () const + { + return m_dynamic_extents; + } + const svalue *get_dynamic_extents (const region *reg) const; + void set_dynamic_extents (const region *reg, + const svalue *size_in_bytes); + void unset_dynamic_extents (const region *reg); + region_model_manager *get_manager () const { return m_mgr; } void unbind_region_and_descendents (const region *reg, @@ -629,6 +647,8 @@ class region_model void loop_replay_fixup (const region_model *dst_state); + const svalue *get_capacity (const region *reg) const; + private: const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const; const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const; @@ -676,9 +696,6 @@ class region_model void on_top_level_param (tree param, region_model_context *ctxt); - void record_dynamic_extents (const region *reg, - const svalue *size_in_bytes); - bool called_from_main_p () const; const svalue *get_initial_value_for_global (const region *reg) const; @@ -693,6 +710,12 @@ class region_model constraint_manager *m_constraints; // TODO: embed, rather than dynalloc? const frame_region *m_current_frame; + + /* Map from base region to size in bytes, for tracking the sizes of + dynamically-allocated regions. + This is part of the region_model rather than the region to allow for + memory regions to be resized (e.g. by realloc). */ + dynamic_extents_t m_dynamic_extents; }; /* Some region_model activity could lead to warnings (e.g. attempts to use an -- cgit v1.1 From ec3fafa9ec7d16b9d89076efd3bac1d1af0502b8 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 15 Jun 2021 17:53:34 -0400 Subject: analyzer: fix bitfield endianness issues [PR99212,PR101082] Looks like my patch for PR analyzer/99212 implicitly assumed little-endian, which the following patch fixes. Fixes bitfields-1.c on: - armeb-none-linux-gnueabihf - cris-elf - powerpc64-darwin - s390-linux-gnu gcc/analyzer/ChangeLog: PR analyzer/99212 PR analyzer/101082 * engine.cc: Include "target.h". (impl_run_checkers): Log BITS_BIG_ENDIAN, BYTES_BIG_ENDIAN, and WORDS_BIG_ENDIAN. * region-model-manager.cc (region_model_manager::maybe_fold_binop): Move support for masking via ARG0 & CST into... (region_model_manager::maybe_undo_optimize_bit_field_compare): ...this new function. Flatten by converting from nested conditionals to a series of early return statements to reject failures. Reject if type is not unsigned_char_type_node. Handle BYTES_BIG_ENDIAN when determining which bits are bound in the binding_map. * region-model.h (region_model_manager::maybe_undo_optimize_bit_field_compare): New decl. * store.cc (bit_range::dump): New function. * store.h (bit_range::dump): New decl. Signed-off-by: David Malcolm --- gcc/analyzer/engine.cc | 8 +++ gcc/analyzer/region-model-manager.cc | 94 ++++++++++++++++++++++-------------- gcc/analyzer/region-model.h | 3 ++ gcc/analyzer/store.cc | 12 +++++ gcc/analyzer/store.h | 1 + 5 files changed, 83 insertions(+), 35 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index df04b0b..529965a 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -64,6 +64,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/bar-chart.h" #include #include "plugin.h" +#include "target.h" /* For an overview, see gcc/doc/analyzer.texi. */ @@ -4845,6 +4846,13 @@ impl_run_checkers (logger *logger) { LOG_SCOPE (logger); + if (logger) + { + logger->log ("BITS_BIG_ENDIAN: %i", BITS_BIG_ENDIAN ? 1 : 0); + logger->log ("BYTES_BIG_ENDIAN: %i", BYTES_BIG_ENDIAN ? 1 : 0); + logger->log ("WORDS_BIG_ENDIAN: %i", WORDS_BIG_ENDIAN ? 1 : 0); + } + /* If using LTO, ensure that the cgraph nodes have function bodies. */ cgraph_node *node; FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 0ca0c8a..621eff0 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -431,6 +431,60 @@ region_model_manager::get_or_create_cast (tree type, const svalue *arg) return get_or_create_unaryop (type, op, arg); } +/* Subroutine of region_model_manager::maybe_fold_binop for handling + (TYPE)(COMPOUND_SVAL BIT_AND_EXPR CST) that may have been generated by + optimize_bit_field_compare, where CST is from ARG1. + + Support masking out bits from a compound_svalue for comparing a bitfield + against a value, as generated by optimize_bit_field_compare for + BITFIELD == VALUE. + + If COMPOUND_SVAL has a value for the appropriate bits, return it, + shifted accordingly. + Otherwise return NULL. */ + +const svalue * +region_model_manager:: +maybe_undo_optimize_bit_field_compare (tree type, + const compound_svalue *compound_sval, + tree cst, + const svalue *arg1) +{ + if (type != unsigned_char_type_node) + return NULL; + + const binding_map &map = compound_sval->get_map (); + unsigned HOST_WIDE_INT mask = TREE_INT_CST_LOW (cst); + /* If "mask" is a contiguous range of set bits, see if the + compound_sval has a value for those bits. */ + bit_range bits (0, 0); + if (!bit_range::from_mask (mask, &bits)) + return NULL; + + bit_range bound_bits (bits); + if (BYTES_BIG_ENDIAN) + bound_bits = bit_range (BITS_PER_UNIT - bits.get_next_bit_offset (), + bits.m_size_in_bits); + const concrete_binding *conc + = get_store_manager ()->get_concrete_binding (bound_bits, BK_direct); + const svalue *sval = map.get (conc); + if (!sval) + return NULL; + + /* We have a value; + shift it by the correct number of bits. */ + const svalue *lhs = get_or_create_cast (type, sval); + HOST_WIDE_INT bit_offset = bits.get_start_bit_offset ().to_shwi (); + tree shift_amt = build_int_cst (type, bit_offset); + const svalue *shift_sval = get_or_create_constant_svalue (shift_amt); + const svalue *shifted_sval = get_or_create_binop (type, LSHIFT_EXPR, + lhs, shift_sval); + /* Reapply the mask (needed for negative + signed bitfields). */ + return get_or_create_binop (type, BIT_AND_EXPR, + shifted_sval, arg1); +} + /* Subroutine of region_model_manager::get_or_create_binop. Attempt to fold the inputs and return a simpler svalue *. Otherwise, return NULL. */ @@ -485,43 +539,13 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, /* "(ARG0 & 0)" -> "0". */ return get_or_create_constant_svalue (build_int_cst (type, 0)); - /* Support masking out bits from a compound_svalue, as this - is generated when accessing bitfields. */ if (const compound_svalue *compound_sval = arg0->dyn_cast_compound_svalue ()) - { - const binding_map &map = compound_sval->get_map (); - unsigned HOST_WIDE_INT mask = TREE_INT_CST_LOW (cst1); - /* If "mask" is a contiguous range of set bits, see if the - compound_sval has a value for those bits. */ - bit_range bits (0, 0); - if (bit_range::from_mask (mask, &bits)) - { - const concrete_binding *conc - = get_store_manager ()->get_concrete_binding (bits, - BK_direct); - if (const svalue *sval = map.get (conc)) - { - /* We have a value; - shift it by the correct number of bits. */ - const svalue *lhs = get_or_create_cast (type, sval); - HOST_WIDE_INT bit_offset - = bits.get_start_bit_offset ().to_shwi (); - tree shift_amt = build_int_cst (type, bit_offset); - const svalue *shift_sval - = get_or_create_constant_svalue (shift_amt); - const svalue *shifted_sval - = get_or_create_binop (type, - LSHIFT_EXPR, - lhs, shift_sval); - /* Reapply the mask (needed for negative - signed bitfields). */ - return get_or_create_binop (type, - BIT_AND_EXPR, - shifted_sval, arg1); - } - } - } + if (const svalue *sval + = maybe_undo_optimize_bit_field_compare (type, + compound_sval, + cst1, arg1)) + return sval; } break; case TRUTH_ANDIF_EXPR: diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 8b669df..7b12d35 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -320,6 +320,9 @@ private: const svalue *maybe_fold_sub_svalue (tree type, const svalue *parent_svalue, const region *subregion); + const svalue *maybe_undo_optimize_bit_field_compare (tree type, + const compound_svalue *compound_sval, + tree cst, const svalue *arg1); unsigned m_next_region_id; root_region m_root_region; diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index 699de94..d75fb2c 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -249,6 +249,18 @@ bit_range::dump_to_pp (pretty_printer *pp) const pp_wide_int (pp, get_next_bit_offset (), SIGNED); } +/* Dump this object to stderr. */ + +DEBUG_FUNCTION void +bit_range::dump () const +{ + pretty_printer pp; + pp.buffer->stream = stderr; + dump_to_pp (&pp); + pp_newline (&pp); + pp_flush (&pp); +} + int bit_range::cmp (const bit_range &br1, const bit_range &br2) { diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index 7bd2824..ca9ff69 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -275,6 +275,7 @@ struct bit_range {} void dump_to_pp (pretty_printer *pp) const; + void dump () const; bit_offset_t get_start_bit_offset () const { -- cgit v1.1 From ede6c3568f383f62df7bf9234212ee80763fdf6b Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 16 Jun 2021 00:17:05 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 400175a..b8f7a2b 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,93 @@ +2021-06-15 David Malcolm + + PR analyzer/99212 + PR analyzer/101082 + * engine.cc: Include "target.h". + (impl_run_checkers): Log BITS_BIG_ENDIAN, BYTES_BIG_ENDIAN, and + WORDS_BIG_ENDIAN. + * region-model-manager.cc + (region_model_manager::maybe_fold_binop): Move support for masking + via ARG0 & CST into... + (region_model_manager::maybe_undo_optimize_bit_field_compare): + ...this new function. Flatten by converting from nested + conditionals to a series of early return statements to reject + failures. Reject if type is not unsigned_char_type_node. + Handle BYTES_BIG_ENDIAN when determining which bits are bound + in the binding_map. + * region-model.h + (region_model_manager::maybe_undo_optimize_bit_field_compare): + New decl. + * store.cc (bit_range::dump): New function. + * store.h (bit_range::dump): New decl. + +2021-06-15 David Malcolm + + * engine.cc (exploded_node::on_stmt): Handle __analyzer_dump_capacity. + (exploded_node::on_stmt): Drop m_sm_changes from on_stmt_flags. + (state_change_requires_new_enode_p): New function... + (exploded_graph::process_node): Call it, rather than querying + flags.m_sm_changes, so that dynamic-extent differences can also + trigger the splitting of nodes. + * exploded-graph.h (struct on_stmt_flags): Drop field m_sm_changes. + * program-state.cc (program_state::detect_leaks): Purge dead + heap-allocated regions from dynamic extents. + (selftest::test_program_state_1): Fix type of "size_in_bytes". + (selftest::test_program_state_merging): Likewise. + * region-model-impl-calls.cc + (region_model::impl_call_analyzer_dump_capacity): New. + (region_model::impl_call_free): Remove dynamic extents from the + freed region. + * region-model-reachability.h + (reachable_regions::begin_mutable_base_regs): New. + (reachable_regions::end_mutable_base_regs): New. + * region-model.cc: Include "tree-object-size.h". + (region_model::region_model): Support new field m_dynamic_extents. + (region_model::operator=): Likewise. + (region_model::operator==): Likewise. + (region_model::dump_to_pp): Dump sizes of dynamic regions. + (region_model::handle_unrecognized_call): Purge dynamic extents + from any regions that have escaped mutably:. + (region_model::get_capacity): New function. + (region_model::add_constraint): Unset dynamic extents when a + heap-allocated region's address is NULL. + (region_model::unbind_region_and_descendents): Purge dynamic + extents of unbound regions. + (region_model::can_merge_with_p): Call + m_dynamic_extents.can_merge_with_p. + (region_model::create_region_for_heap_alloc): Assert that + size_in_bytes's type is compatible with size_type_node. Update + for renaming of record_dynamic_extents to set_dynamic_extents. + (region_model::create_region_for_alloca): Likewise. + (region_model::record_dynamic_extents): Rename to... + (region_model::set_dynamic_extents): ...this. Assert that + size_in_bytes's type is compatible with size_type_node. Add it + to the m_dynamic_extents map. + (region_model::get_dynamic_extents): New. + (region_model::unset_dynamic_extents): New. + (selftest::test_state_merging): Fix type of "size". + (selftest::test_malloc_constraints): Likewise. + (selftest::test_malloc): Verify dynamic extents. + (selftest::test_alloca): Likewise. + * region-model.h (region_to_value_map::is_empty): New. + (region_model::dynamic_extents_t): New typedef. + (region_model::impl_call_analyzer_dump_capacity): New decl. + (region_model::get_dynamic_extents): New function. + (region_model::get_dynamic_extents): New decl. + (region_model::set_dynamic_extents): New decl. + (region_model::unset_dynamic_extents): New decl. + (region_model::get_capacity): New decl. + (region_model::record_dynamic_extents): Rename to set_dynamic_extents. + (region_model::m_dynamic_extents): New field. + +2021-06-15 David Malcolm + + * region-model.cc (region_to_value_map::operator=): New. + (region_to_value_map::operator==): New. + (region_to_value_map::dump_to_pp): New. + (region_to_value_map::dump): New. + (region_to_value_map::can_merge_with_p): New. + * region-model.h (class region_to_value_map): New class. + 2021-06-13 Trevor Saunders * call-string.cc (call_string::call_string): Use range based for -- cgit v1.1 From 86606d2ab731a4b8dbbe1e5318a1920210abd65d Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 18 Jun 2021 11:18:10 -0400 Subject: analyzer: refactor custom_event, introducing precanned_custom_event class I have followup work where a custom event's description would be better handled via a vfunc rather that a precanned string, hence this refactoring to make it easy to add custom_event subclasses. gcc/analyzer/ChangeLog: * checker-path.cc (class custom_event): Make abstract to allow for custom vfuncs, splitting existing implementation into... (class precanned_custom_event): New subclass. (custom_event::get_desc): Move to... (precanned_custom_event::get_desc): ...subclass. * checker-path.h (class custom_event): Make abstract to allow for custom vfuncs, splitting existing implementation into... (class precanned_custom_event): New subclass. * diagnostic-manager.cc (diagnostic_manager::add_events_for_eedge): Use precanned_custom_event. * engine.cc (stale_jmp_buf::maybe_add_custom_events_for_superedge): Likewise. * sm-signal.cc (signal_delivery_edge_info_t::add_events_to_path): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/checker-path.cc | 6 +++--- gcc/analyzer/checker-path.h | 22 +++++++++++++++++----- gcc/analyzer/diagnostic-manager.cc | 2 +- gcc/analyzer/engine.cc | 2 +- gcc/analyzer/sm-signal.cc | 7 ++++--- 5 files changed, 26 insertions(+), 13 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index e6f838b..e10c8e2 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -162,14 +162,14 @@ debug_event::get_desc (bool) const return label_text::borrow (m_desc); } -/* class custom_event : public checker_event. */ +/* class precanned_custom_event : public custom_event. */ /* Implementation of diagnostic_event::get_desc vfunc for - custom_event. + precanned_custom_event. Use the saved string as the event's description. */ label_text -custom_event::get_desc (bool) const +precanned_custom_event::get_desc (bool) const { return label_text::borrow (m_desc); } diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index f76bb94..1843c4b 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -56,6 +56,7 @@ extern const char *event_kind_to_string (enum event_kind ek); checker_event debug_event (EK_DEBUG) custom_event (EK_CUSTOM) + precanned_custom_event statement_event (EK_STMT) function_entry_event (EK_FUNCTION_ENTRY) state_change_event (EK_STATE_CHANGE) @@ -144,19 +145,30 @@ private: char *m_desc; }; -/* A concrete event subclass for custom events. These are not filtered, +/* An abstract event subclass for custom events. These are not filtered, as they are likely to be pertinent to the diagnostic. */ class custom_event : public checker_event { +protected: + custom_event (location_t loc, tree fndecl, int depth) + : checker_event (EK_CUSTOM, loc, fndecl, depth) + { + } +}; + +/* A concrete custom_event subclass with a precanned message. */ + +class precanned_custom_event : public custom_event +{ public: - custom_event (location_t loc, tree fndecl, int depth, - const char *desc) - : checker_event (EK_CUSTOM, loc, fndecl, depth), + precanned_custom_event (location_t loc, tree fndecl, int depth, + const char *desc) + : custom_event (loc, fndecl, depth), m_desc (xstrdup (desc)) { } - ~custom_event () + ~precanned_custom_event () { free (m_desc); } diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 443ff05..7eb4ed8 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1587,7 +1587,7 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb, "this path would have been rejected as infeasible" " at this edge: "); pb.get_feasibility_problem ()->dump_to_pp (&pp); - emission_path->add_event (new custom_event + emission_path->add_event (new precanned_custom_event (dst_point.get_location (), dst_point.get_fndecl (), dst_stack_depth, diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 529965a..f322fdb 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1395,7 +1395,7 @@ public: { /* Compare with diagnostic_manager::add_events_for_superedge. */ const int src_stack_depth = src_point.get_stack_depth (); - m_stack_pop_event = new custom_event + m_stack_pop_event = new precanned_custom_event (src_point.get_location (), src_point.get_fndecl (), src_stack_depth, diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc index d7e7e7c..42be809 100644 --- a/gcc/analyzer/sm-signal.cc +++ b/gcc/analyzer/sm-signal.cc @@ -238,9 +238,10 @@ public: FINAL OVERRIDE { emission_path->add_event - (new custom_event (UNKNOWN_LOCATION, NULL_TREE, 0, - "later on," - " when the signal is delivered to the process")); + (new precanned_custom_event + (UNKNOWN_LOCATION, NULL_TREE, 0, + "later on," + " when the signal is delivered to the process")); } }; -- cgit v1.1 From 1aff29d42601927a416a484d6c0fa37a25faae79 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 18 Jun 2021 11:19:30 -0400 Subject: analyzer: add region_model_manager::get_or_create_int_cst gcc/analyzer/ChangeLog: * region-model-manager.cc (region_model_manager::get_or_create_int_cst): New. (region_model_manager::maybe_undo_optimize_bit_field_compare): Use it to simplify away a local tree. * region-model.cc (region_model::on_setjmp): Likewise. (region_model::on_longjmp): Likewise. * region-model.h (region_model_manager::get_or_create_int_cst): New decl. * store.cc (binding_cluster::zero_fill_region): Use it to simplify away a local tree. Signed-off-by: David Malcolm --- gcc/analyzer/region-model-manager.cc | 14 ++++++++++++-- gcc/analyzer/region-model.cc | 11 +++++------ gcc/analyzer/region-model.h | 1 + gcc/analyzer/store.cc | 4 ++-- 4 files changed, 20 insertions(+), 10 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 621eff0..1ee6663 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -210,6 +210,17 @@ region_model_manager::get_or_create_constant_svalue (tree cst_expr) return cst_sval; } +/* Return the svalue * for a constant_svalue for the INTEGER_CST + for VAL of type TYPE, creating it if necessary. */ + +const svalue * +region_model_manager::get_or_create_int_cst (tree type, poly_int64 val) +{ + gcc_assert (type); + tree tree_cst = build_int_cst (type, val); + return get_or_create_constant_svalue (tree_cst); +} + /* Return the svalue * for a unknown_svalue for TYPE (which can be NULL), creating it if necessary. The unknown_svalue instances are reused, based on pointer equality @@ -475,8 +486,7 @@ maybe_undo_optimize_bit_field_compare (tree type, shift it by the correct number of bits. */ const svalue *lhs = get_or_create_cast (type, sval); HOST_WIDE_INT bit_offset = bits.get_start_bit_offset ().to_shwi (); - tree shift_amt = build_int_cst (type, bit_offset); - const svalue *shift_sval = get_or_create_constant_svalue (shift_amt); + const svalue *shift_sval = get_or_create_int_cst (type, bit_offset); const svalue *shifted_sval = get_or_create_binop (type, LSHIFT_EXPR, lhs, shift_sval); /* Reapply the mask (needed for negative diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index e02a897..462fe6d 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1259,8 +1259,8 @@ region_model::on_setjmp (const gcall *call, const exploded_node *enode, /* Direct calls to setjmp return 0. */ if (tree lhs = gimple_call_lhs (call)) { - tree zero = build_int_cst (TREE_TYPE (lhs), 0); - const svalue *new_sval = m_mgr->get_or_create_constant_svalue (zero); + const svalue *new_sval + = m_mgr->get_or_create_int_cst (TREE_TYPE (lhs), 0); const region *lhs_reg = get_lvalue (lhs, ctxt); set_value (lhs_reg, new_sval, ctxt); } @@ -1291,15 +1291,14 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call, if (tree lhs = gimple_call_lhs (setjmp_call)) { /* Passing 0 as the val to longjmp leads to setjmp returning 1. */ - tree t_zero = build_int_cst (TREE_TYPE (fake_retval), 0); - const svalue *zero_sval = m_mgr->get_or_create_constant_svalue (t_zero); + const svalue *zero_sval + = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 0); tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval); /* If we have 0, use 1. */ if (eq_zero.is_true ()) { - tree t_one = build_int_cst (TREE_TYPE (fake_retval), 1); const svalue *one_sval - = m_mgr->get_or_create_constant_svalue (t_one); + = m_mgr->get_or_create_int_cst (TREE_TYPE (fake_retval), 1); fake_retval_sval = one_sval; } else diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 7b12d35..a4b584d 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -238,6 +238,7 @@ public: /* svalue consolidation. */ const svalue *get_or_create_constant_svalue (tree cst_expr); + const svalue *get_or_create_int_cst (tree type, poly_int64); const svalue *get_or_create_unknown_svalue (tree type); const svalue *get_or_create_setjmp_svalue (const setjmp_record &r, tree type); diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index d75fb2c..b643b63 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -1043,8 +1043,8 @@ binding_cluster::zero_fill_region (store_manager *mgr, const region *reg) /* Add a default binding to zero. */ region_model_manager *sval_mgr = mgr->get_svalue_manager (); - tree cst_zero = build_int_cst (integer_type_node, 0); - const svalue *cst_sval = sval_mgr->get_or_create_constant_svalue (cst_zero); + const svalue *cst_sval + = sval_mgr->get_or_create_int_cst (integer_type_node, 0); const svalue *bound_sval = cst_sval; if (reg->get_type ()) bound_sval = sval_mgr->get_or_create_unaryop (reg->get_type (), NOP_EXPR, -- cgit v1.1 From 3bb85b868722e69aef0d37858c0dc3c88d92a0eb Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 18 Jun 2021 13:24:19 -0400 Subject: analyzer: fix issue with symbolic reads with concrete bindings gcc/analyzer/ChangeLog: * store.cc (binding_cluster::get_any_binding): Make symbolic reads from a cluster with concrete bindings return unknown. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/symbolic-7.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/store.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index b643b63..3203703 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -1177,6 +1177,16 @@ binding_cluster::get_any_binding (store_manager *mgr, return rmm_mgr->get_or_create_unknown_svalue (reg->get_type ()); } + /* Alternatively, if this is a symbolic read and the cluster has any bindings, + then we don't know if we're reading those values or not, so the result + is also "UNKNOWN". */ + if (reg->get_offset ().symbolic_p () + && m_map.elements () > 0) + { + region_model_manager *rmm_mgr = mgr->get_svalue_manager (); + return rmm_mgr->get_or_create_unknown_svalue (reg->get_type ()); + } + if (const svalue *compound_sval = maybe_get_compound_binding (mgr, reg)) return compound_sval; -- cgit v1.1 From c5581d4842efff98060c6caf270c6f6c55e9888a Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sat, 19 Jun 2021 00:16:33 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index b8f7a2b..84fa208 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,38 @@ +2021-06-18 David Malcolm + + * store.cc (binding_cluster::get_any_binding): Make symbolic reads + from a cluster with concrete bindings return unknown. + +2021-06-18 David Malcolm + + * region-model-manager.cc + (region_model_manager::get_or_create_int_cst): New. + (region_model_manager::maybe_undo_optimize_bit_field_compare): Use + it to simplify away a local tree. + * region-model.cc (region_model::on_setjmp): Likewise. + (region_model::on_longjmp): Likewise. + * region-model.h (region_model_manager::get_or_create_int_cst): + New decl. + * store.cc (binding_cluster::zero_fill_region): Use it to simplify + away a local tree. + +2021-06-18 David Malcolm + + * checker-path.cc (class custom_event): Make abstract to allow for + custom vfuncs, splitting existing implementation into... + (class precanned_custom_event): New subclass. + (custom_event::get_desc): Move to... + (precanned_custom_event::get_desc): ...subclass. + * checker-path.h (class custom_event): Make abstract to allow for + custom vfuncs, splitting existing implementation into... + (class precanned_custom_event): New subclass. + * diagnostic-manager.cc (diagnostic_manager::add_events_for_eedge): + Use precanned_custom_event. + * engine.cc + (stale_jmp_buf::maybe_add_custom_events_for_superedge): Likewise. + * sm-signal.cc (signal_delivery_edge_info_t::add_events_to_path): + Likewise. + 2021-06-15 David Malcolm PR analyzer/99212 -- cgit v1.1 From ea4e32181d7a36055b57421abd0ced4735654cf6 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 22 Jun 2021 13:44:57 -0400 Subject: analyzer: fix ICE on malloc/alloca param type mismatch [PR101143] gcc/analyzer/ChangeLog: PR analyzer/101143 * region-model.cc (compat_types_p): New function. (region_model::create_region_for_heap_alloc): Convert assertion to an error check. (region_model::create_region_for_alloca): Likewise. gcc/testsuite/ChangeLog: PR analyzer/101143 * gcc.dg/analyzer/pr101143.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 462fe6d..ee11e82 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1443,6 +1443,17 @@ assert_compat_types (tree src_type, tree dst_type) } } +/* Return true if SRC_TYPE can be converted to DST_TYPE as a no-op. */ + +static bool +compat_types_p (tree src_type, tree dst_type) +{ + if (src_type && dst_type && !VOID_TYPE_P (dst_type)) + if (!(useless_type_conversion_p (src_type, dst_type))) + return false; + return true; +} + /* Get the region for PV within this region_model, emitting any diagnostics to CTXT. */ @@ -3402,8 +3413,8 @@ const region * region_model::create_region_for_heap_alloc (const svalue *size_in_bytes) { const region *reg = m_mgr->create_region_for_heap_alloc (); - assert_compat_types (size_in_bytes->get_type (), size_type_node); - set_dynamic_extents (reg, size_in_bytes); + if (compat_types_p (size_in_bytes->get_type (), size_type_node)) + set_dynamic_extents (reg, size_in_bytes); return reg; } @@ -3414,8 +3425,8 @@ const region * region_model::create_region_for_alloca (const svalue *size_in_bytes) { const region *reg = m_mgr->create_region_for_alloca (m_current_frame); - assert_compat_types (size_in_bytes->get_type (), size_type_node); - set_dynamic_extents (reg, size_in_bytes); + if (compat_types_p (size_in_bytes->get_type (), size_type_node)) + set_dynamic_extents (reg, size_in_bytes); return reg; } -- cgit v1.1 From 419af06a35933fb1bb7c87fe9c7306755afce9a0 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 23 Jun 2021 00:16:28 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 84fa208..3630b2e 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,11 @@ +2021-06-22 David Malcolm + + PR analyzer/101143 + * region-model.cc (compat_types_p): New function. + (region_model::create_region_for_heap_alloc): Convert assertion to + an error check. + (region_model::create_region_for_alloca): Likewise. + 2021-06-18 David Malcolm * store.cc (binding_cluster::get_any_binding): Make symbolic reads -- cgit v1.1 From 7c6b354b92b38f31cd2399fbdbc9d6f837881480 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 28 Jun 2021 19:18:06 -0400 Subject: analyzer: introduce byte_range and use to simplify dumps gcc/analyzer/ChangeLog: * analyzer.h (byte_offset_t): New typedef. * store.cc (bit_range::dump_to_pp): Dump as a byte range if possible. (bit_range::as_byte_range): New. (byte_range::dump_to_pp): New. * store.h (class byte_range): New forward decl. (struct bit_range): Add comment. (bit_range::as_byte_range): New decl. (struct byte_range): New. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.h | 1 + gcc/analyzer/store.cc | 54 +++++++++++++++++++++++++++++++++++++++++++------ gcc/analyzer/store.h | 25 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 525eb06..f06b68c 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -142,6 +142,7 @@ public: typedef offset_int bit_offset_t; typedef offset_int bit_size_t; +typedef offset_int byte_offset_t; typedef offset_int byte_size_t; extern bool int_size_in_bits (const_tree type, bit_size_t *out); diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index 3203703..d5f8798 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -241,12 +241,18 @@ binding_key::cmp (const binding_key *k1, const binding_key *k2) void bit_range::dump_to_pp (pretty_printer *pp) const { - pp_string (pp, "start: "); - pp_wide_int (pp, m_start_bit_offset, SIGNED); - pp_string (pp, ", size: "); - pp_wide_int (pp, m_size_in_bits, SIGNED); - pp_string (pp, ", next: "); - pp_wide_int (pp, get_next_bit_offset (), SIGNED); + byte_range bytes (0, 0); + if (as_byte_range (&bytes)) + bytes.dump_to_pp (pp); + else + { + pp_string (pp, "start: "); + pp_wide_int (pp, m_start_bit_offset, SIGNED); + pp_string (pp, ", size: "); + pp_wide_int (pp, m_size_in_bits, SIGNED); + pp_string (pp, ", next: "); + pp_wide_int (pp, get_next_bit_offset (), SIGNED); + } } /* Dump this object to stderr. */ @@ -329,6 +335,42 @@ bit_range::from_mask (unsigned HOST_WIDE_INT mask, bit_range *out) return true; } +/* Attempt to convert this bit_range to a byte_range. + Return true if it is possible, writing the result to *OUT. + Otherwise return false. */ + +bool +bit_range::as_byte_range (byte_range *out) const +{ + if (m_start_bit_offset % BITS_PER_UNIT == 0 + && m_size_in_bits % BITS_PER_UNIT == 0) + { + out->m_start_byte_offset = m_start_bit_offset / BITS_PER_UNIT; + out->m_size_in_bytes = m_size_in_bits / BITS_PER_UNIT; + return true; + } + return false; +} + +/* Dump this object to PP. */ + +void +byte_range::dump_to_pp (pretty_printer *pp) const +{ + if (m_size_in_bytes == 1) + { + pp_string (pp, "byte "); + pp_wide_int (pp, m_start_byte_offset, SIGNED); + } + else + { + pp_string (pp, "bytes "); + pp_wide_int (pp, m_start_byte_offset, SIGNED); + pp_string (pp, "-"); + pp_wide_int (pp, get_last_byte_offset (), SIGNED); + } +} + /* class concrete_binding : public binding_key. */ /* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */ diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index ca9ff69..e0c60e1 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -196,6 +196,7 @@ private: hash_set m_mutable_at_unknown_call_svals; }; +class byte_range; class concrete_binding; /* An enum for discriminating between "direct" vs "default" levels of @@ -267,6 +268,8 @@ private: enum binding_kind m_kind; }; +/* A concrete range of bits. */ + struct bit_range { bit_range (bit_offset_t start_bit_offset, bit_size_t size_in_bits) @@ -308,10 +311,32 @@ struct bit_range static bool from_mask (unsigned HOST_WIDE_INT mask, bit_range *out); + bool as_byte_range (byte_range *out) const; + bit_offset_t m_start_bit_offset; bit_size_t m_size_in_bits; }; +/* A concrete range of bytes. */ + +struct byte_range +{ + byte_range (byte_offset_t start_byte_offset, byte_size_t size_in_bytes) + : m_start_byte_offset (start_byte_offset), + m_size_in_bytes (size_in_bytes) + {} + + void dump_to_pp (pretty_printer *pp) const; + + byte_offset_t get_last_byte_offset () const + { + return m_start_byte_offset + m_size_in_bytes - 1; + } + + byte_offset_t m_start_byte_offset; + byte_size_t m_size_in_bytes; +}; + /* Concrete subclass of binding_key, for describing a concrete range of bits within the binding_map (e.g. "bits 8-15"). */ -- cgit v1.1 From c8abc2058e96dd12454078d66be9982dfebfd154 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 29 Jun 2021 00:16:42 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 3630b2e..cc0eb11 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,15 @@ +2021-06-28 David Malcolm + + * analyzer.h (byte_offset_t): New typedef. + * store.cc (bit_range::dump_to_pp): Dump as a byte range if + possible. + (bit_range::as_byte_range): New. + (byte_range::dump_to_pp): New. + * store.h (class byte_range): New forward decl. + (struct bit_range): Add comment. + (bit_range::as_byte_range): New decl. + (struct byte_range): New. + 2021-06-22 David Malcolm PR analyzer/101143 -- cgit v1.1 From e61ffa201403e3814a43b176883e176716b1492f Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 30 Jun 2021 09:39:04 -0400 Subject: analyzer: eliminate enum binding_key [PR95006] I rewrote the way the analyzer's region_model tracks the state of memory in GCC 11 (in 808f4dfeb3a95f50f15e71148e5c1067f90a126d), which introduced a store with a binding_map class, mapping binding keys to symbolic values. The GCC 11 implementation of binding keys has an enum binding_kind, which can be "default" vs "direct"; the idea being that direct bindings take priority over default bindings, where the latter could be used to represent e.g. a zero-fill of a buffer, and the former expresses those subregions that have since been touched. This doesn't work well: it doesn't express the idea of filling different subregions with different values, or a memset that only touches part of a buffer, leading to numerous XFAILs in the memset test cases (and elsewhere). As preparatory work towards tracking uninitialized values, this patch eliminates the enum binding_kind, so that all bindings have equal weight; the order in which they happen is all that matters. If a write happens which partially overwrites an existing binding, the new code can partially overwrite a binding, potentially punching a hole so that an existing binding is split into two parts. The patch adds some new classes: - a new "bits_within_svalue" symbolic value to support extracting parts of an existing value when its binding is partially clobbered - a new "repeated_svalue" symbolic value to better express filling a region with repeated copies of a symbolic value (e.g. constant zero) - a new "sized_region" region to express accessing a subregion with a symbolic size in bytes and it rewrites e.g. how memset is implemented, so that we can precisely track which bits in a region have not been touched. That said, the patch doesn't actually implement "uninitialized" values; I'm saving that for a followup. gcc/analyzer/ChangeLog: PR analyzer/95006 * analyzer.h (class repeated_svalue): New forward decl. (class bits_within_svalue): New forward decl. (class sized_region): New forward decl. (get_field_at_bit_offset): New forward decl. * engine.cc (exploded_graph::get_or_create_node): Validate the merged state. (exploded_graph::maybe_process_run_of_before_supernode_enodes): Validate the states at each stage. * program-state.cc (program_state::validate): Validate m_region_model. * region-model-impl-calls.cc (region_model::impl_call_memset): Replace special-case logic for handling constant sizes with a call to fill_region of a sized_region with the given fill value. * region-model-manager.cc (maybe_undo_optimize_bit_field_compare): Drop DK_direct. (region_model_manager::maybe_fold_sub_svalue): Fold element-based subregions of an initial value into initial values of an element. Fold subvalues of repeated svalues. (region_model_manager::maybe_fold_repeated_svalue): New. (region_model_manager::get_or_create_repeated_svalue): New. (get_bit_range_for_field): New. (get_byte_range_for_field): New. (get_field_at_byte_range): New. (region_model_manager::maybe_fold_bits_within_svalue): New. (region_model_manager::get_or_create_bits_within): New. (region_model_manager::get_sized_region): New. (region_model_manager::log_stats): Update for addition of m_repeated_values_map, m_bits_within_values_map, and m_sized_regions. * region-model.cc (region_model::validate): New. (region_model::on_assignment): Drop enum binding_kind. (region_model::get_initial_value_for_global): Likewise. (region_model::get_rvalue_for_bits): Replace body with call to get_or_create_bits_within. (region_model::get_capacity): Handle RK_SIZED. (region_model::set_value): Drop enum binding_kind. (region_model::fill_region): New. (region_model::get_representative_path_var_1): Handle RK_SIZED. * region-model.h (visitor::visit_repeated_svalue): New. (visitor::visit_bits_within_svalue): New. (region_model_manager::get_or_create_repeated_svalue): New decl. (region_model_manager::get_or_create_bits_within): New decl. (region_model_manager::get_sized_region): New decl. (region_model_manager::maybe_fold_repeated_svalue): New decl. (region_model_manager::maybe_fold_bits_within_svalue): New decl. (region_model_manager::repeated_values_map_t): New typedef. (region_model_manager::m_repeated_values_map): New field. (region_model_manager::bits_within_values_map_t): New typedef. (region_model_manager::m_bits_within_values_map): New field. (region_model_manager::m_sized_regions): New field. (region_model::fill_region): New decl. * region.cc (region::get_base_region): Handle RK_SIZED. (region::base_region_p): Likewise. (region::get_byte_size_sval): New. (get_field_at_bit_offset): Make non-static. (region::calc_offset): Move implementation of cases to get_relative_concrete_offset vfunc implementations. Handle RK_SIZED. (region::get_relative_concrete_offset): New. (decl_region::get_svalue_for_initializer): Drop enum binding_kind. (field_region::get_relative_concrete_offset): New, from region::calc_offset. (element_region::get_relative_concrete_offset): Likewise. (offset_region::get_relative_concrete_offset): Likewise. (sized_region::accept): New. (sized_region::dump_to_pp): New. (sized_region::get_byte_size): New. (sized_region::get_bit_size): New. * region.h (enum region_kind): Add RK_SIZED. (region::dyn_cast_sized_region): New. (region::get_byte_size): Make virtual. (region::get_bit_size): Likewise. (region::get_byte_size_sval): New decl. (region::get_relative_concrete_offset): New decl. (field_region::get_relative_concrete_offset): New decl. (element_region::get_relative_concrete_offset): Likewise. (offset_region::get_relative_concrete_offset): Likewise. (class sized_region): New. * store.cc (binding_kind_to_string): Delete. (binding_key::make): Drop enum binding_kind. (binding_key::dump_to_pp): Delete. (binding_key::cmp_ptrs): Drop enum binding_kind. (bit_range::contains_p): New. (byte_range::dump): New. (byte_range::contains_p): New. (byte_range::cmp): New. (concrete_binding::dump_to_pp): Drop enum binding_kind. (concrete_binding::cmp_ptr_ptr): Likewise. (symbolic_binding::dump_to_pp): Likewise. (symbolic_binding::cmp_ptr_ptr): Likewise. (binding_map::apply_ctor_val_to_range): Likewise. (binding_map::apply_ctor_pair_to_child_region): Likewise. (binding_map::get_overlapping_bindings): New. (binding_map::remove_overlapping_bindings): New. (binding_cluster::validate): New. (binding_cluster::bind): Drop enum binding_kind. (binding_cluster::bind_compound_sval): Likewise. (binding_cluster::purge_region): Likewise. (binding_cluster::zero_fill_region): Reimplement in terms of... (binding_cluster::fill_region): New. (binding_cluster::mark_region_as_unknown): Drop enum binding_kind. (binding_cluster::get_binding): Likewise. (binding_cluster::get_binding_recursive): Likewise. (binding_cluster::get_any_binding): Likewise. (binding_cluster::maybe_get_compound_binding): Reimplement. (binding_cluster::get_overlapping_bindings): Delete. (binding_cluster::remove_overlapping_bindings): Reimplement in terms of binding_map::remove_overlapping_bindings. (binding_cluster::can_merge_p): Update for removal of enum binding_kind. (binding_cluster::on_unknown_fncall): Drop enum binding_kind. (binding_cluster::maybe_get_simple_value): Likewise. (store_manager::get_concrete_binding): Likewise. (store_manager::get_symbolic_binding): Likewise. (store::validate): New. (store::set_value): Drop enum binding_kind. (store::zero_fill_region): Reimplement in terms of... (store::fill_region): New. (selftest::test_binding_key_overlap): Drop enum binding_kind. * store.h (enum binding_kind): Delete. (binding_kind_to_string): Delete decl. (binding_key::make): Drop enum binding_kind. (binding_key::dump_to_pp): Make pure virtual. (binding_key::get_kind): Delete. (binding_key::mark_deleted): Delete. (binding_key::mark_empty): Delete. (binding_key::is_deleted): Delete. (binding_key::is_empty): Delete. (binding_key::binding_key): Delete. (binding_key::impl_hash): Delete. (binding_key::impl_eq): Delete. (binding_key::m_kind): Delete. (bit_range::get_last_bit_offset): New. (bit_range::contains_p): New. (byte_range::contains_p): New. (byte_range::operator==): New. (byte_range::get_start_byte_offset): New. (byte_range::get_next_byte_offset): New. (byte_range::get_last_byte_offset): New. (byte_range::as_bit_range): New. (byte_range::cmp): New. (concrete_binding::concrete_binding): Drop enum binding_kind. (concrete_binding::hash): Likewise. (concrete_binding::operator==): Likewise. (concrete_binding::mark_deleted): New. (concrete_binding::mark_empty): New. (concrete_binding::is_deleted): New. (concrete_binding::is_empty): New. (default_hash_traits::empty_zero_p): Make false. (symbolic_binding::symbolic_binding): Drop enum binding_kind. (symbolic_binding::hash): Likewise. (symbolic_binding::operator==): Likewise. (symbolic_binding::mark_deleted): New. (symbolic_binding::mark_empty): New. (symbolic_binding::is_deleted): New. (symbolic_binding::is_empty): New. (binding_map::remove_overlapping_bindings): New decl. (binding_map::get_overlapping_bindings): New decl. (binding_cluster::validate): New decl. (binding_cluster::bind): Drop enum binding_kind. (binding_cluster::fill_region): New decl. (binding_cluster::get_binding): Drop enum binding_kind. (binding_cluster::get_binding_recursive): Likewise. (binding_cluster::get_overlapping_bindings): Delete. (store::validate): New decl. (store::set_value): Drop enum binding_kind. (store::fill_region): New decl. (store_manager::get_concrete_binding): Drop enum binding_kind. (store_manager::get_symbolic_binding): Likewise. * svalue.cc (svalue::cmp_ptr): Handle SK_REPEATED and SK_BITS_WITHIN. (svalue::extract_bit_range): New. (svalue::maybe_fold_bits_within): New. (constant_svalue::maybe_fold_bits_within): New. (unknown_svalue::maybe_fold_bits_within): New. (unaryop_svalue::maybe_fold_bits_within): New. (repeated_svalue::repeated_svalue): New. (repeated_svalue::dump_to_pp): New. (repeated_svalue::accept): New. (repeated_svalue::all_zeroes_p): New. (repeated_svalue::maybe_fold_bits_within): New. (bits_within_svalue::bits_within_svalue): New. (bits_within_svalue::dump_to_pp): New. (bits_within_svalue::maybe_fold_bits_within): New. (bits_within_svalue::accept): New. (bits_within_svalue::implicitly_live_p): New. (compound_svalue::maybe_fold_bits_within): New. * svalue.h (enum svalue_kind): Add SK_REPEATED and SK_BITS_WITHIN. (svalue::dyn_cast_repeated_svalue): New. (svalue::dyn_cast_bits_within_svalue): New. (svalue::extract_bit_range): New decl. (svalue::maybe_fold_bits_within): New vfunc decl. (region_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (region_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (constant_svalue::maybe_fold_bits_within): New. (unknown_svalue::maybe_fold_bits_within): New. (poisoned_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (poisoned_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (setjmp_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (setjmp_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (unaryop_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (unaryop_svalue::key_t::is_empty): Likewise. (unaryop_svalue::maybe_fold_bits_within): New. (default_hash_traits::empty_zero_p): Make false. (binop_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (binop_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (sub_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (sub_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (class repeated_svalue): New. (is_a_helper ::test): New. (struct default_hash_traits): New. (class bits_within_svalue): New. (is_a_helper ::test): New. (struct default_hash_traits): New. (widening_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (widening_svalue::key_t::is_empty): Likewise. (default_hash_traits::empty_zero_p): Make false. (compound_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. (compound_svalue::key_t::is_empty): Likewise. (compound_svalue::maybe_fold_bits_within): New. (default_hash_traits::empty_zero_p): Make false. gcc/testsuite/ChangeLog: PR analyzer/95006 * gcc.dg/analyzer/clobbers-1.c: New test. * gcc.dg/analyzer/clobbers-2.c: New test. * gcc.dg/analyzer/data-model-1.c (test_26): Mark xfail as fixed. (test_28): Likewise. (test_52): Likewise. Add coverage for end of buffer. * gcc.dg/analyzer/explode-1.c: Add leak warning. * gcc.dg/analyzer/memset-1.c (test_3): Mark xfail as fixed. (test_4): Use char. Mark xfail as fixed. (test_6b): New. (test_7): Mark xfail as fixed. Add coverage for start of buffer. (test_8): New. (test_9): New. * gcc.dg/analyzer/memset-CVE-2017-18549-1.c: New test. * gcc.dg/analyzer/symbolic-8.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.h | 5 + gcc/analyzer/engine.cc | 5 + gcc/analyzer/program-state.cc | 1 + gcc/analyzer/region-model-impl-calls.cc | 39 +- gcc/analyzer/region-model-manager.cc | 313 ++++++++++++++- gcc/analyzer/region-model.cc | 72 ++-- gcc/analyzer/region-model.h | 27 ++ gcc/analyzer/region.cc | 230 ++++++++--- gcc/analyzer/region.h | 125 +++++- gcc/analyzer/store.cc | 653 +++++++++++++++++++++----------- gcc/analyzer/store.h | 157 ++++---- gcc/analyzer/svalue.cc | 381 +++++++++++++++++++ gcc/analyzer/svalue.h | 262 +++++++++++-- 13 files changed, 1808 insertions(+), 462 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index f06b68c..02830e4 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -46,6 +46,8 @@ class svalue; class unaryop_svalue; class binop_svalue; class sub_svalue; + class repeated_svalue; + class bits_within_svalue; class unmergeable_svalue; class placeholder_svalue; class widening_svalue; @@ -60,6 +62,7 @@ class region; class symbolic_region; class element_region; class offset_region; + class sized_region; class cast_region; class field_region; class string_region; @@ -147,6 +150,8 @@ typedef offset_int byte_size_t; extern bool int_size_in_bits (const_tree type, bit_size_t *out); +extern tree get_field_at_bit_offset (tree record_type, bit_offset_t bit_offset); + /* The location of a region expressesd as an offset relative to a base region. */ diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index f322fdb..4456d9b 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -2275,6 +2275,7 @@ exploded_graph::get_or_create_node (const program_point &point, if (pruned_state.can_merge_with_p (existing_state, point, &merged_state)) { + merged_state.validate (m_ext_state); if (logger) logger->log ("merging new state with that of EN: %i", existing_enode->m_index); @@ -2794,6 +2795,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) items.quick_push (it); const program_state &state = iter_enode->get_state (); program_state *next_state = &it->m_processed_state; + next_state->validate (m_ext_state); const program_point &iter_point = iter_enode->get_point (); if (const superedge *iter_sedge = iter_point.get_from_edge ()) { @@ -2807,6 +2809,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) next_state->m_region_model->update_for_phis (snode, last_cfg_superedge, &ctxt); } + next_state->validate (m_ext_state); } /* Attempt to partition the items into a set of merged states. @@ -2823,10 +2826,12 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) unsigned iter_merger_idx; FOR_EACH_VEC_ELT (merged_states, iter_merger_idx, merged_state) { + merged_state->validate (m_ext_state); program_state merge (m_ext_state); if (it_state.can_merge_with_p (*merged_state, next_point, &merge)) { *merged_state = merge; + merged_state->validate (m_ext_state); it->m_merger_idx = iter_merger_idx; if (logger) logger->log ("reusing merger state %i for item %i (EN: %i)", diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 67dd785..6d60c04 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1142,6 +1142,7 @@ program_state::validate (const extrinsic_state &ext_state) const #endif gcc_assert (m_checker_states.length () == ext_state.get_num_checkers ()); + m_region_model->validate (); } static void diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 099520a..466d397 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -389,36 +389,15 @@ region_model::impl_call_memset (const call_details &cd) const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0), cd.get_ctxt ()); - if (tree num_bytes = num_bytes_sval->maybe_get_constant ()) - { - /* "memset" of zero size is a no-op. */ - if (zerop (num_bytes)) - return true; - - /* Set with known amount. */ - byte_size_t reg_size; - if (dest_reg->get_byte_size (®_size)) - { - /* Check for an exact size match. */ - if (reg_size == wi::to_offset (num_bytes)) - { - if (tree cst = fill_value_sval->maybe_get_constant ()) - { - if (zerop (cst)) - { - zero_fill_region (dest_reg); - return true; - } - } - } - } - } - - check_for_writable_region (dest_reg, cd.get_ctxt ()); - - /* Otherwise, mark region's contents as unknown. */ - mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); - return false; + const svalue *fill_value_u8 + = m_mgr->get_or_create_cast (unsigned_char_type_node, fill_value_sval); + + const region *sized_dest_reg = m_mgr->get_sized_region (dest_reg, + NULL_TREE, + num_bytes_sval); + check_for_writable_region (sized_dest_reg, cd.get_ctxt ()); + fill_region (sized_dest_reg, fill_value_u8); + return true; } /* Handle the on_call_pre part of "operator new". */ diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 1ee6663..55acb90 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -477,7 +477,7 @@ maybe_undo_optimize_bit_field_compare (tree type, bound_bits = bit_range (BITS_PER_UNIT - bits.get_next_bit_offset (), bits.m_size_in_bits); const concrete_binding *conc - = get_store_manager ()->get_concrete_binding (bound_bits, BK_direct); + = get_store_manager ()->get_concrete_binding (bound_bits); const svalue *sval = map.get (conc); if (!sval) return NULL; @@ -686,13 +686,13 @@ region_model_manager::maybe_fold_sub_svalue (tree type, return get_or_create_cast (type, char_sval); } - /* SUB(INIT(r)).FIELD -> INIT(r.FIELD) - i.e. - Subvalue(InitialValue(R1), FieldRegion(R2, F)) - -> InitialValue(FieldRegion(R1, F)). */ if (const initial_svalue *init_sval - = parent_svalue->dyn_cast_initial_svalue ()) + = parent_svalue->dyn_cast_initial_svalue ()) { + /* SUB(INIT(r)).FIELD -> INIT(r.FIELD) + i.e. + Subvalue(InitialValue(R1), FieldRegion(R2, F)) + -> InitialValue(FieldRegion(R1, F)). */ if (const field_region *field_reg = subregion->dyn_cast_field_region ()) { const region *field_reg_new @@ -700,8 +700,24 @@ region_model_manager::maybe_fold_sub_svalue (tree type, field_reg->get_field ()); return get_or_create_initial_value (field_reg_new); } + /* SUB(INIT(r)[ELEMENT] -> INIT(e[ELEMENT]) + i.e. + Subvalue(InitialValue(R1), ElementRegion(R2, IDX)) + -> InitialValue(ElementRegion(R1, IDX)). */ + if (const element_region *element_reg = subregion->dyn_cast_element_region ()) + { + const region *element_reg_new + = get_element_region (init_sval->get_region (), + element_reg->get_type (), + element_reg->get_index ()); + return get_or_create_initial_value (element_reg_new); + } } + if (const repeated_svalue *repeated_sval + = parent_svalue->dyn_cast_repeated_svalue ()) + return get_or_create_cast (type, repeated_sval->get_inner_svalue ()); + return NULL; } @@ -727,6 +743,255 @@ region_model_manager::get_or_create_sub_svalue (tree type, return sub_sval; } +/* Subroutine of region_model_manager::get_or_create_repeated_svalue. + Return a folded svalue, or NULL. */ + +const svalue * +region_model_manager::maybe_fold_repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue) +{ + /* If INNER_SVALUE is the same size as OUTER_SIZE, + turn into simply a cast. */ + if (tree cst_outer_num_bytes = outer_size->maybe_get_constant ()) + { + HOST_WIDE_INT num_bytes_inner_svalue + = int_size_in_bytes (inner_svalue->get_type ()); + if (num_bytes_inner_svalue != -1) + if (num_bytes_inner_svalue + == (HOST_WIDE_INT)tree_to_uhwi (cst_outer_num_bytes)) + { + if (type) + return get_or_create_cast (type, inner_svalue); + else + return inner_svalue; + } + } + + /* Handle zero-fill of a specific type. */ + if (tree cst = inner_svalue->maybe_get_constant ()) + if (zerop (cst) && type) + return get_or_create_cast (type, inner_svalue); + + return NULL; +} + +/* Return the svalue * of type TYPE in which INNER_SVALUE is repeated + enough times to be of size OUTER_SIZE, creating it if necessary. + e.g. for filling buffers with a constant value. */ + +const svalue * +region_model_manager::get_or_create_repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue) +{ + if (const svalue *folded + = maybe_fold_repeated_svalue (type, outer_size, inner_svalue)) + return folded; + + repeated_svalue::key_t key (type, outer_size, inner_svalue); + if (repeated_svalue **slot = m_repeated_values_map.get (key)) + return *slot; + repeated_svalue *repeated_sval + = new repeated_svalue (type, outer_size, inner_svalue); + RETURN_UNKNOWN_IF_TOO_COMPLEX (repeated_sval); + m_repeated_values_map.put (key, repeated_sval); + return repeated_sval; +} + +/* Attempt to get the bit_range for FIELD within a RECORD_TYPE. + Return true and write the result to OUT if successful. + Return false otherwise. */ + +static bool +get_bit_range_for_field (tree field, bit_range *out) +{ + bit_size_t bit_size; + if (!int_size_in_bits (TREE_TYPE (field), &bit_size)) + return false; + int field_bit_offset = int_bit_position (field); + *out = bit_range (field_bit_offset, bit_size); + return true; +} + +/* Attempt to get the byte_range for FIELD within a RECORD_TYPE. + Return true and write the result to OUT if successful. + Return false otherwise. */ + +static bool +get_byte_range_for_field (tree field, byte_range *out) +{ + bit_range field_bits (0, 0); + if (!get_bit_range_for_field (field, &field_bits)) + return false; + return field_bits.as_byte_range (out); +} + +/* Attempt to determine if there is a specific field within RECORD_TYPE + at BYTES. If so, return it, and write the location of BYTES relative + to the field to *OUT_RANGE_WITHIN_FIELD. + Otherwise, return NULL_TREE. + For example, given: + struct foo { uint32 a; uint32; b}; + and + bytes = {bytes 6-7} (of foo) + we have bytes 3-4 of field b. */ + +static tree +get_field_at_byte_range (tree record_type, const byte_range &bytes, + byte_range *out_range_within_field) +{ + bit_offset_t bit_offset = bytes.m_start_byte_offset * BITS_PER_UNIT; + + tree field = get_field_at_bit_offset (record_type, bit_offset); + if (!field) + return NULL_TREE; + + byte_range field_bytes (0,0); + if (!get_byte_range_for_field (field, &field_bytes)) + return NULL_TREE; + + /* Is BYTES fully within field_bytes? */ + byte_range bytes_within_field (0,0); + if (!field_bytes.contains_p (bytes, &bytes_within_field)) + return NULL_TREE; + + *out_range_within_field = bytes_within_field; + return field; +} + +/* Subroutine of region_model_manager::get_or_create_bits_within. + Return a folded svalue, or NULL. */ + +const svalue * +region_model_manager::maybe_fold_bits_within_svalue (tree type, + const bit_range &bits, + const svalue *inner_svalue) +{ + tree inner_type = inner_svalue->get_type (); + /* Fold: + BITS_WITHIN ((0, sizeof (VAL), VAL)) + to: + CAST(TYPE, VAL). */ + if (bits.m_start_bit_offset == 0 && inner_type) + { + bit_size_t inner_type_size; + if (int_size_in_bits (inner_type, &inner_type_size)) + if (inner_type_size == bits.m_size_in_bits) + { + if (type) + return get_or_create_cast (type, inner_svalue); + else + return inner_svalue; + } + } + + /* Kind-specific folding. */ + if (const svalue *sval + = inner_svalue->maybe_fold_bits_within (type, bits, this)) + return sval; + + byte_range bytes (0,0); + if (bits.as_byte_range (&bytes) && inner_type) + switch (TREE_CODE (inner_type)) + { + default: + break; + case ARRAY_TYPE: + { + /* Fold: + BITS_WITHIN (range, KIND(REG)) + to: + BITS_WITHIN (range - offsetof(ELEMENT), KIND(REG.ELEMENT)) + if range1 is a byte-range fully within one ELEMENT. */ + tree element_type = TREE_TYPE (inner_type); + HOST_WIDE_INT element_byte_size + = int_size_in_bytes (element_type); + if (element_byte_size > 0) + { + HOST_WIDE_INT start_idx + = (bytes.get_start_byte_offset ().to_shwi () + / element_byte_size); + HOST_WIDE_INT last_idx + = (bytes.get_last_byte_offset ().to_shwi () + / element_byte_size); + if (start_idx == last_idx) + { + if (const initial_svalue *initial_sval + = inner_svalue->dyn_cast_initial_svalue ()) + { + bit_offset_t start_of_element + = start_idx * element_byte_size * BITS_PER_UNIT; + bit_range bits_within_element + (bits.m_start_bit_offset - start_of_element, + bits.m_size_in_bits); + const svalue *idx_sval + = get_or_create_int_cst (integer_type_node, start_idx); + const region *element_reg = + get_element_region (initial_sval->get_region (), + element_type, idx_sval); + const svalue *element_reg_sval + = get_or_create_initial_value (element_reg); + return get_or_create_bits_within (type, + bits_within_element, + element_reg_sval); + } + } + } + } + break; + case RECORD_TYPE: + { + /* Fold: + BYTES_WITHIN (range, KIND(REG)) + to: + BYTES_WITHIN (range - offsetof(FIELD), KIND(REG.FIELD)) + if range1 is fully within FIELD. */ + byte_range bytes_within_field (0, 0); + if (tree field = get_field_at_byte_range (inner_type, bytes, + &bytes_within_field)) + { + if (const initial_svalue *initial_sval + = inner_svalue->dyn_cast_initial_svalue ()) + { + const region *field_reg = + get_field_region (initial_sval->get_region (), field); + const svalue *initial_reg_sval + = get_or_create_initial_value (field_reg); + return get_or_create_bits_within + (type, + bytes_within_field.as_bit_range (), + initial_reg_sval); + } + } + } + break; + } + return NULL; +} + +/* Return the svalue * of type TYPE for extracting BITS from INNER_SVALUE, + creating it if necessary. */ + +const svalue * +region_model_manager::get_or_create_bits_within (tree type, + const bit_range &bits, + const svalue *inner_svalue) +{ + if (const svalue *folded + = maybe_fold_bits_within_svalue (type, bits, inner_svalue)) + return folded; + + bits_within_svalue::key_t key (type, bits, inner_svalue); + if (bits_within_svalue **slot = m_bits_within_values_map.get (key)) + return *slot; + bits_within_svalue *bits_within_sval + = new bits_within_svalue (type, bits, inner_svalue); + RETURN_UNKNOWN_IF_TOO_COMPLEX (bits_within_sval); + m_bits_within_values_map.put (key, bits_within_sval); + return bits_within_sval; +} + /* Return the svalue * that decorates ARG as being unmergeable, creating it if necessary. */ @@ -966,6 +1231,38 @@ region_model_manager::get_offset_region (const region *parent, return offset_reg; } +/* Return the region that describes accessing the subregion of type + TYPE of size BYTE_SIZE_SVAL within PARENT, creating it if necessary. */ + +const region * +region_model_manager::get_sized_region (const region *parent, + tree type, + const svalue *byte_size_sval) +{ + if (byte_size_sval->get_type () != size_type_node) + byte_size_sval = get_or_create_cast (size_type_node, byte_size_sval); + + /* If PARENT is already that size, return it. */ + const svalue *parent_byte_size_sval = parent->get_byte_size_sval (this); + if (tree parent_size_cst = parent_byte_size_sval->maybe_get_constant ()) + if (tree size_cst = byte_size_sval->maybe_get_constant ()) + { + tree comparison + = fold_binary (EQ_EXPR, boolean_type_node, parent_size_cst, size_cst); + if (comparison == boolean_true_node) + return parent; + } + + sized_region::key_t key (parent, type, byte_size_sval); + if (sized_region *reg = m_sized_regions.get (key)) + return reg; + + sized_region *sized_reg + = new sized_region (alloc_region_id (), parent, type, byte_size_sval); + m_sized_regions.put (key, sized_reg); + return sized_reg; +} + /* Return the region that describes accessing PARENT_REGION as if it were of type TYPE, creating it if necessary. */ @@ -1180,6 +1477,9 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const log_uniq_map (logger, show_objs, "unaryop_svalue", m_unaryop_values_map); log_uniq_map (logger, show_objs, "binop_svalue", m_binop_values_map); log_uniq_map (logger, show_objs, "sub_svalue", m_sub_values_map); + log_uniq_map (logger, show_objs, "repeated_svalue", m_repeated_values_map); + log_uniq_map (logger, show_objs, "bits_within_svalue", + m_bits_within_values_map); log_uniq_map (logger, show_objs, "unmergeable_svalue", m_unmergeable_values_map); log_uniq_map (logger, show_objs, "widening_svalue", m_widening_values_map); @@ -1198,6 +1498,7 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const log_uniq_map (logger, show_objs, "field_region", m_field_regions); log_uniq_map (logger, show_objs, "element_region", m_element_regions); log_uniq_map (logger, show_objs, "offset_region", m_offset_regions); + log_uniq_map (logger, show_objs, "sized_region", m_sized_regions); log_uniq_map (logger, show_objs, "cast_region", m_cast_regions); log_uniq_map (logger, show_objs, "frame_region", m_frame_regions); log_uniq_map (logger, show_objs, "symbolic_region", m_symbolic_regions); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index ee11e82..4fb6bc9 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -393,6 +393,14 @@ region_model::debug () const dump (true); } +/* Assert that this object is valid. */ + +void +region_model::validate () const +{ + m_store.validate (); +} + /* Canonicalize the store and constraints, to maximize the chance of equality between region_model instances. */ @@ -847,11 +855,9 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt) case STRING_CST: { /* e.g. "struct s2 x = {{'A', 'B', 'C', 'D'}};". */ - /* Add a default binding, rather than a direct one, so that array - access will "inherit" the individual chars. */ const svalue *rhs_sval = get_rvalue (rhs1, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - BK_default, ctxt ? ctxt->get_uncertainty () : NULL); + ctxt ? ctxt->get_uncertainty () : NULL); } break; } @@ -1662,8 +1668,7 @@ region_model::get_initial_value_for_global (const region *reg) const { /* Get the value for REG within base_reg_init. */ binding_cluster c (base_reg); - c.bind (m_mgr->get_store_manager (), base_reg, base_reg_init, - BK_direct); + c.bind (m_mgr->get_store_manager (), base_reg, base_reg_init); const svalue *sval = c.get_any_binding (m_mgr->get_store_manager (), reg); if (sval) @@ -1854,45 +1859,7 @@ region_model::get_rvalue_for_bits (tree type, const bit_range &bits) const { const svalue *sval = get_store_value (reg); - if (const compound_svalue *compound_sval = sval->dyn_cast_compound_svalue ()) - { - const binding_map &map = compound_sval->get_map (); - binding_map result_map; - for (auto iter : map) - { - const binding_key *key = iter.first; - if (const concrete_binding *conc_key - = key->dyn_cast_concrete_binding ()) - { - /* Ignore concrete bindings outside BITS. */ - if (!conc_key->get_bit_range ().intersects_p (bits)) - continue; - if ((conc_key->get_start_bit_offset () - < bits.get_start_bit_offset ()) - || (conc_key->get_next_bit_offset () - > bits.get_next_bit_offset ())) - { - /* If we have any concrete keys that aren't fully within BITS, - then bail out. */ - return m_mgr->get_or_create_unknown_svalue (type); - } - const concrete_binding *offset_conc_key - = m_mgr->get_store_manager ()->get_concrete_binding - (conc_key->get_start_bit_offset () - - bits.get_start_bit_offset (), - conc_key->get_size_in_bits (), - conc_key->get_kind ()); - const svalue *sval = iter.second; - result_map.put (offset_conc_key, sval); - } - else - /* If we have any symbolic keys we can't get it as bits. */ - return m_mgr->get_or_create_unknown_svalue (type); - } - return m_mgr->get_or_create_compound_svalue (type, result_map); - } - - return m_mgr->get_or_create_unknown_svalue (type); + return m_mgr->get_or_create_bits_within (type, bits, sval); } /* A subclass of pending_diagnostic for complaining about writes to @@ -2035,6 +2002,10 @@ region_model::get_capacity (const region *reg) const } } break; + case RK_SIZED: + /* Look through sized regions to get at the capacity + of the underlying regions. */ + return get_capacity (reg->get_parent_region ()); } if (const svalue *recorded = get_dynamic_extents (reg)) @@ -2056,7 +2027,7 @@ region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, check_for_writable_region (lhs_reg, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - BK_direct, ctxt ? ctxt->get_uncertainty () : NULL); + ctxt ? ctxt->get_uncertainty () : NULL); } /* Set the value of the region given by LHS to the value given by RHS. */ @@ -2087,6 +2058,14 @@ region_model::purge_region (const region *reg) m_store.purge_region (m_mgr->get_store_manager(), reg); } +/* Fill REG with SVAL. */ + +void +region_model::fill_region (const region *reg, const svalue *sval) +{ + m_store.fill_region (m_mgr->get_store_manager(), reg, sval); +} + /* Zero-fill REG. */ void @@ -2711,6 +2690,9 @@ region_model::get_representative_path_var_1 (const region *reg, parent_pv.m_stack_depth); } + case RK_SIZED: + return path_var (NULL_TREE, 0); + case RK_CAST: { path_var parent_pv diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index a4b584d..b42852b 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -212,6 +212,8 @@ public: virtual void visit_unaryop_svalue (const unaryop_svalue *) {} virtual void visit_binop_svalue (const binop_svalue *) {} virtual void visit_sub_svalue (const sub_svalue *) {} + virtual void visit_repeated_svalue (const repeated_svalue *) {} + virtual void visit_bits_within_svalue (const bits_within_svalue *) {} virtual void visit_unmergeable_svalue (const unmergeable_svalue *) {} virtual void visit_placeholder_svalue (const placeholder_svalue *) {} virtual void visit_widening_svalue (const widening_svalue *) {} @@ -255,6 +257,12 @@ public: const svalue *get_or_create_sub_svalue (tree type, const svalue *parent_svalue, const region *subregion); + const svalue *get_or_create_repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue); + const svalue *get_or_create_bits_within (tree type, + const bit_range &bits, + const svalue *inner_svalue); const svalue *get_or_create_unmergeable (const svalue *arg); const svalue *get_or_create_widening_svalue (tree type, const program_point &point, @@ -286,6 +294,9 @@ public: const region *get_offset_region (const region *parent, tree type, const svalue *byte_offset); + const region *get_sized_region (const region *parent, + tree type, + const svalue *byte_size_sval); const region *get_cast_region (const region *original_region, tree type); const frame_region *get_frame_region (const frame_region *calling_frame, @@ -321,6 +332,12 @@ private: const svalue *maybe_fold_sub_svalue (tree type, const svalue *parent_svalue, const region *subregion); + const svalue *maybe_fold_repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue); + const svalue *maybe_fold_bits_within_svalue (tree type, + const bit_range &bits, + const svalue *inner_svalue); const svalue *maybe_undo_optimize_bit_field_compare (tree type, const compound_svalue *compound_sval, tree cst, const svalue *arg1); @@ -362,6 +379,14 @@ private: typedef hash_map sub_values_map_t; sub_values_map_t m_sub_values_map; + typedef hash_map repeated_values_map_t; + repeated_values_map_t m_repeated_values_map; + + typedef hash_map bits_within_values_map_t; + bits_within_values_map_t m_bits_within_values_map; + typedef hash_map unmergeable_values_map_t; unmergeable_values_map_t m_unmergeable_values_map; @@ -402,6 +427,7 @@ private: consolidation_map m_field_regions; consolidation_map m_element_regions; consolidation_map m_offset_regions; + consolidation_map m_sized_regions; consolidation_map m_cast_regions; consolidation_map m_frame_regions; consolidation_map m_symbolic_regions; @@ -575,6 +601,7 @@ class region_model void set_value (tree lhs, tree rhs, region_model_context *ctxt); void clobber_region (const region *reg); void purge_region (const region *reg); + void fill_region (const region *reg, const svalue *sval); void zero_fill_region (const region *reg); void mark_region_as_unknown (const region *reg, uncertainty_t *uncertainty); diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 5f246df..4633717 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -98,6 +98,7 @@ region::get_base_region () const case RK_FIELD: case RK_ELEMENT: case RK_OFFSET: + case RK_SIZED: iter = iter->get_parent_region (); continue; case RK_CAST: @@ -121,6 +122,7 @@ region::base_region_p () const case RK_FIELD: case RK_ELEMENT: case RK_OFFSET: + case RK_SIZED: case RK_CAST: return false; @@ -188,7 +190,8 @@ region::get_offset () const return *m_cached_offset; } -/* If the size of this region (in bytes) is known statically, write it to *OUT +/* Base class implementation of region::get_byte_size vfunc. + If the size of this region (in bytes) is known statically, write it to *OUT and return true. Otherwise return false. */ @@ -208,8 +211,29 @@ region::get_byte_size (byte_size_t *out) const return true; } -/* If the size of TYPE (in bits) is constant, write it to *OUT - and return true. +/* Base implementation of region::get_byte_size_sval vfunc. */ + +const svalue * +region::get_byte_size_sval (region_model_manager *mgr) const +{ + tree type = get_type (); + + /* Bail out e.g. for heap-allocated regions. */ + if (!type) + return mgr->get_or_create_unknown_svalue (size_type_node); + + HOST_WIDE_INT bytes = int_size_in_bytes (type); + if (bytes == -1) + return mgr->get_or_create_unknown_svalue (size_type_node); + + tree byte_size = size_in_bytes (type); + if (TREE_TYPE (byte_size) != size_type_node) + byte_size = fold_build1 (NOP_EXPR, size_type_node, byte_size); + return mgr->get_or_create_constant_svalue (byte_size); +} + +/* Attempt to get the size of TYPE in bits. + If successful, return true and write the size to *OUT. Otherwise return false. */ bool @@ -249,7 +273,7 @@ region::get_bit_size (bit_size_t *out) const /* Get the field within RECORD_TYPE at BIT_OFFSET. */ -static tree +tree get_field_at_bit_offset (tree record_type, bit_offset_t bit_offset) { gcc_assert (TREE_CODE (record_type) == RECORD_TYPE); @@ -375,18 +399,10 @@ region::calc_offset () const = (const field_region *)iter_region; iter_region = iter_region->get_parent_region (); - /* Compare with e.g. gimple-fold.c's - fold_nonarray_ctor_reference. */ - tree field = field_reg->get_field (); - tree byte_offset = DECL_FIELD_OFFSET (field); - if (TREE_CODE (byte_offset) != INTEGER_CST) + bit_offset_t rel_bit_offset; + if (!field_reg->get_relative_concrete_offset (&rel_bit_offset)) return region_offset::make_symbolic (iter_region); - tree field_offset = DECL_FIELD_BIT_OFFSET (field); - /* Compute bit offset of the field. */ - offset_int bitoffset - = (wi::to_offset (field_offset) - + (wi::to_offset (byte_offset) << LOG2_BITS_PER_UNIT)); - accum_bit_offset += bitoffset; + accum_bit_offset += rel_bit_offset; } continue; @@ -396,28 +412,10 @@ region::calc_offset () const = (const element_region *)iter_region; iter_region = iter_region->get_parent_region (); - if (tree idx_cst - = element_reg->get_index ()->maybe_get_constant ()) - { - gcc_assert (TREE_CODE (idx_cst) == INTEGER_CST); - - tree elem_type = element_reg->get_type (); - offset_int element_idx = wi::to_offset (idx_cst); - - /* First, use int_size_in_bytes, to reject the case where we - have an incomplete type, or a non-constant value. */ - HOST_WIDE_INT hwi_byte_size = int_size_in_bytes (elem_type); - if (hwi_byte_size > 0) - { - offset_int element_bit_size - = hwi_byte_size << LOG2_BITS_PER_UNIT; - offset_int element_bit_offset - = element_idx * element_bit_size; - accum_bit_offset += element_bit_offset; - continue; - } - } - return region_offset::make_symbolic (iter_region); + bit_offset_t rel_bit_offset; + if (!element_reg->get_relative_concrete_offset (&rel_bit_offset)) + return region_offset::make_symbolic (iter_region); + accum_bit_offset += rel_bit_offset; } continue; @@ -427,22 +425,17 @@ region::calc_offset () const = (const offset_region *)iter_region; iter_region = iter_region->get_parent_region (); - if (tree byte_offset_cst - = offset_reg->get_byte_offset ()->maybe_get_constant ()) - { - gcc_assert (TREE_CODE (byte_offset_cst) == INTEGER_CST); - /* Use a signed value for the byte offset, to handle - negative offsets. */ - HOST_WIDE_INT byte_offset - = wi::to_offset (byte_offset_cst).to_shwi (); - HOST_WIDE_INT bit_offset = byte_offset * BITS_PER_UNIT; - accum_bit_offset += bit_offset; - } - else + bit_offset_t rel_bit_offset; + if (!offset_reg->get_relative_concrete_offset (&rel_bit_offset)) return region_offset::make_symbolic (iter_region); + accum_bit_offset += rel_bit_offset; } continue; + case RK_SIZED: + iter_region = iter_region->get_parent_region (); + continue; + case RK_CAST: { const cast_region *cast_reg @@ -458,6 +451,14 @@ region::calc_offset () const return region_offset::make_concrete (iter_region, accum_bit_offset); } +/* Base implementation of region::get_relative_concrete_offset vfunc. */ + +bool +region::get_relative_concrete_offset (bit_offset_t *) const +{ + return false; +} + /* Copy from SRC_REG to DST_REG, using CTXT for any issues that occur. */ void @@ -984,7 +985,7 @@ decl_region::get_svalue_for_initializer (region_model_manager *mgr) const which can fail if we have a region with unknown size (e.g. "extern const char arr[];"). */ const binding_key *binding - = binding_key::make (mgr->get_store_manager (), this, BK_direct); + = binding_key::make (mgr->get_store_manager (), this); if (binding->symbolic_p ()) return NULL; @@ -1030,6 +1031,26 @@ field_region::dump_to_pp (pretty_printer *pp, bool simple) const } } +/* Implementation of region::get_relative_concrete_offset vfunc + for field_region. */ + +bool +field_region::get_relative_concrete_offset (bit_offset_t *out) const +{ + /* Compare with e.g. gimple-fold.c's + fold_nonarray_ctor_reference. */ + tree byte_offset = DECL_FIELD_OFFSET (m_field); + if (TREE_CODE (byte_offset) != INTEGER_CST) + return false; + tree field_offset = DECL_FIELD_BIT_OFFSET (m_field); + /* Compute bit offset of the field. */ + offset_int bitoffset + = (wi::to_offset (field_offset) + + (wi::to_offset (byte_offset) << LOG2_BITS_PER_UNIT)); + *out = bitoffset; + return true; +} + /* class element_region : public region. */ /* Implementation of region::accept vfunc for element_region. */ @@ -1067,6 +1088,35 @@ element_region::dump_to_pp (pretty_printer *pp, bool simple) const } } +/* Implementation of region::get_relative_concrete_offset vfunc + for element_region. */ + +bool +element_region::get_relative_concrete_offset (bit_offset_t *out) const +{ + if (tree idx_cst = m_index->maybe_get_constant ()) + { + gcc_assert (TREE_CODE (idx_cst) == INTEGER_CST); + + tree elem_type = get_type (); + offset_int element_idx = wi::to_offset (idx_cst); + + /* First, use int_size_in_bytes, to reject the case where we + have an incomplete type, or a non-constant value. */ + HOST_WIDE_INT hwi_byte_size = int_size_in_bytes (elem_type); + if (hwi_byte_size > 0) + { + offset_int element_bit_size + = hwi_byte_size << LOG2_BITS_PER_UNIT; + offset_int element_bit_offset + = element_idx * element_bit_size; + *out = element_bit_offset; + return true; + } + } + return false; +} + /* class offset_region : public region. */ /* Implementation of region::accept vfunc for offset_region. */ @@ -1103,6 +1153,86 @@ offset_region::dump_to_pp (pretty_printer *pp, bool simple) const } } +/* Implementation of region::get_relative_concrete_offset vfunc + for offset_region. */ + +bool +offset_region::get_relative_concrete_offset (bit_offset_t *out) const +{ + if (tree byte_offset_cst = m_byte_offset->maybe_get_constant ()) + { + gcc_assert (TREE_CODE (byte_offset_cst) == INTEGER_CST); + /* Use a signed value for the byte offset, to handle + negative offsets. */ + HOST_WIDE_INT byte_offset + = wi::to_offset (byte_offset_cst).to_shwi (); + HOST_WIDE_INT bit_offset = byte_offset * BITS_PER_UNIT; + *out = bit_offset; + return true; + } + return false; +} + +/* class sized_region : public region. */ + +/* Implementation of region::accept vfunc for sized_region. */ + +void +sized_region::accept (visitor *v) const +{ + region::accept (v); + m_byte_size_sval->accept (v); +} + +/* Implementation of region::dump_to_pp vfunc for sized_region. */ + +void +sized_region::dump_to_pp (pretty_printer *pp, bool simple) const +{ + if (simple) + { + pp_string (pp, "SIZED_REG("); + get_parent_region ()->dump_to_pp (pp, simple); + pp_string (pp, ", "); + m_byte_size_sval->dump_to_pp (pp, simple); + pp_string (pp, ")"); + } + else + { + pp_string (pp, "sized_region("); + get_parent_region ()->dump_to_pp (pp, simple); + pp_string (pp, ", "); + m_byte_size_sval->dump_to_pp (pp, simple); + pp_printf (pp, ")"); + } +} + +/* Implementation of region::get_byte_size vfunc for sized_region. */ + +bool +sized_region::get_byte_size (byte_size_t *out) const +{ + if (tree cst = m_byte_size_sval->maybe_get_constant ()) + { + gcc_assert (TREE_CODE (cst) == INTEGER_CST); + *out = tree_to_uhwi (cst); + return true; + } + return false; +} + +/* Implementation of region::get_bit_size vfunc for sized_region. */ + +bool +sized_region::get_bit_size (bit_size_t *out) const +{ + byte_size_t byte_size; + if (!get_byte_size (&byte_size)) + return false; + *out = byte_size * BITS_PER_UNIT; + return true; +} + /* class cast_region : public region. */ /* Implementation of region::accept vfunc for cast_region. */ diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index 175a82a..353d5c4 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -43,6 +43,7 @@ enum region_kind RK_FIELD, RK_ELEMENT, RK_OFFSET, + RK_SIZED, RK_CAST, RK_HEAP_ALLOCATED, RK_ALLOCA, @@ -70,6 +71,7 @@ enum region_kind field_region (RK_FIELD) element_region (RK_ELEMENT) offset_region (RK_OFFSET) + sized_region (RK_SIZED) cast_region (RK_CAST) heap_allocated_region (RK_HEAP_ALLOCATED) alloca_region (RK_ALLOCA) @@ -107,6 +109,8 @@ public: dyn_cast_element_region () const { return NULL; } virtual const offset_region * dyn_cast_offset_region () const { return NULL; } + virtual const sized_region * + dyn_cast_sized_region () const { return NULL; } virtual const cast_region * dyn_cast_cast_region () const { return NULL; } virtual const string_region * @@ -138,8 +142,25 @@ public: static int cmp_ptr_ptr (const void *, const void *); region_offset get_offset () const; - bool get_byte_size (byte_size_t *out) const; - bool get_bit_size (bit_size_t *out) const; + + /* Attempt to get the size of this region as a concrete number of bytes. + If successful, return true and write the size to *OUT. + Otherwise return false. */ + virtual bool get_byte_size (byte_size_t *out) const; + + /* Attempt to get the size of this region as a concrete number of bits. + If successful, return true and write the size to *OUT. + Otherwise return false. */ + virtual bool get_bit_size (bit_size_t *out) const; + + /* Get a symbolic value describing the size of this region in bytes + (which could be "unknown"). */ + virtual const svalue *get_byte_size_sval (region_model_manager *mgr) const; + + /* Attempt to get the offset in bits of this region relative to its parent. + If successful, return true and write to *OUT. + Otherwise return false. */ + virtual bool get_relative_concrete_offset (bit_offset_t *out) const; void get_subregions_for_binding (region_model_manager *mgr, @@ -670,6 +691,8 @@ public: tree get_field () const { return m_field; } + bool get_relative_concrete_offset (bit_offset_t *out) const FINAL OVERRIDE; + private: tree m_field; }; @@ -751,6 +774,9 @@ public: const svalue *get_index () const { return m_index; } + virtual bool + get_relative_concrete_offset (bit_offset_t *out) const FINAL OVERRIDE; + private: const svalue *m_index; }; @@ -833,6 +859,8 @@ public: const svalue *get_byte_offset () const { return m_byte_offset; } + bool get_relative_concrete_offset (bit_offset_t *out) const FINAL OVERRIDE; + private: const svalue *m_byte_offset; }; @@ -855,6 +883,99 @@ template <> struct default_hash_traits namespace ana { +/* A region that is size BYTES_SIZE_SVAL in size within its parent + region (or possibly larger, which would lead to an overflow. */ + +class sized_region : public region +{ +public: + /* A support class for uniquifying instances of sized_region. */ + struct key_t + { + key_t (const region *parent, tree element_type, + const svalue *byte_size_sval) + : m_parent (parent), m_element_type (element_type), + m_byte_size_sval (byte_size_sval) + { + gcc_assert (byte_size_sval); + } + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_parent); + hstate.add_ptr (m_element_type); + hstate.add_ptr (m_byte_size_sval); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + return (m_parent == other.m_parent + && m_element_type == other.m_element_type + && m_byte_size_sval == other.m_byte_size_sval); + } + + void mark_deleted () { m_byte_size_sval = reinterpret_cast (1); } + void mark_empty () { m_byte_size_sval = NULL; } + bool is_deleted () const + { + return m_byte_size_sval == reinterpret_cast (1); + } + bool is_empty () const { return m_byte_size_sval == NULL; } + + const region *m_parent; + tree m_element_type; + const svalue *m_byte_size_sval; + const svalue *m_end_offset; + }; + + sized_region (unsigned id, const region *parent, tree type, + const svalue *byte_size_sval) + : region (complexity::from_pair (parent, byte_size_sval), + id, parent, type), + m_byte_size_sval (byte_size_sval) + {} + + enum region_kind get_kind () const FINAL OVERRIDE { return RK_SIZED; } + const sized_region * + dyn_cast_sized_region () const FINAL OVERRIDE { return this; } + + void accept (visitor *v) const FINAL OVERRIDE; + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + + bool get_byte_size (byte_size_t *out) const FINAL OVERRIDE; + bool get_bit_size (bit_size_t *out) const FINAL OVERRIDE; + + const svalue * + get_byte_size_sval (region_model_manager *) const FINAL OVERRIDE + { + return m_byte_size_sval; + } + +private: + const svalue *m_byte_size_sval; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::test (const region *reg) +{ + return reg->get_kind () == RK_SIZED; +} + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = true; +}; + +namespace ana { + /* A region that views another region using a different type. */ class cast_region : public region diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index d5f8798..a65c741 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -118,52 +118,25 @@ uncertainty_t::dump (bool simple) const pp_flush (&pp); } -/* Get a human-readable string for KIND for dumps. */ - -const char *binding_kind_to_string (enum binding_kind kind) -{ - switch (kind) - { - default: - case BK_empty: - case BK_deleted: - /* We shouldn't be attempting to print the hash kinds. */ - gcc_unreachable (); - case BK_direct: - return "direct"; - case BK_default: - return "default"; - } -} - /* class binding_key. */ const binding_key * -binding_key::make (store_manager *mgr, const region *r, - enum binding_kind kind) +binding_key::make (store_manager *mgr, const region *r) { region_offset offset = r->get_offset (); if (offset.symbolic_p ()) - return mgr->get_symbolic_binding (r, kind); + return mgr->get_symbolic_binding (r); else { bit_size_t bit_size; if (r->get_bit_size (&bit_size)) return mgr->get_concrete_binding (offset.get_bit_offset (), - bit_size, kind); + bit_size); else - return mgr->get_symbolic_binding (r, kind); + return mgr->get_symbolic_binding (r); } } -/* Base class implementation of binding_key::dump_to_pp vfunc. */ - -void -binding_key::dump_to_pp (pretty_printer *pp, bool /*simple*/) const -{ - pp_printf (pp, "kind: %s", binding_kind_to_string (m_kind)); -} - /* Dump this binding_key to stderr. */ DEBUG_FUNCTION void @@ -204,11 +177,6 @@ binding_key::cmp_ptrs (const void *p1, const void *p2) int binding_key::cmp (const binding_key *k1, const binding_key *k2) { - enum binding_kind kind1 = k1->get_kind (); - enum binding_kind kind2 = k2->get_kind (); - if (kind1 != kind2) - return (int)kind1 - (int)kind2; - int concrete1 = k1->concrete_p (); int concrete2 = k2->concrete_p (); if (int concrete_cmp = concrete1 - concrete2) @@ -236,7 +204,7 @@ binding_key::cmp (const binding_key *k1, const binding_key *k2) } } -/* struct struct bit_range. */ +/* struct bit_range. */ void bit_range::dump_to_pp (pretty_printer *pp) const @@ -267,6 +235,24 @@ bit_range::dump () const pp_flush (&pp); } +/* If OTHER is a subset of this, return true and write + to *OUT the relative range of OTHER within this. + Otherwise return false. */ + +bool +bit_range::contains_p (const bit_range &other, bit_range *out) const +{ + if (contains_p (other.get_start_bit_offset ()) + && contains_p (other.get_last_bit_offset ())) + { + out->m_start_bit_offset = other.m_start_bit_offset - m_start_bit_offset; + out->m_size_in_bits = other.m_size_in_bits; + return true; + } + else + return false; +} + int bit_range::cmp (const bit_range &br1, const bit_range &br2) { @@ -371,15 +357,57 @@ byte_range::dump_to_pp (pretty_printer *pp) const } } +/* Dump this object to stderr. */ + +DEBUG_FUNCTION void +byte_range::dump () const +{ + pretty_printer pp; + pp.buffer->stream = stderr; + dump_to_pp (&pp); + pp_newline (&pp); + pp_flush (&pp); +} + +/* If OTHER is a subset of this, return true and write + to *OUT the relative range of OTHER within this. + Otherwise return false. */ + +bool +byte_range::contains_p (const byte_range &other, byte_range *out) const +{ + if (contains_p (other.get_start_byte_offset ()) + && contains_p (other.get_last_byte_offset ())) + { + out->m_start_byte_offset = other.m_start_byte_offset - m_start_byte_offset; + out->m_size_in_bytes = other.m_size_in_bytes; + return true; + } + else + return false; +} + +/* qsort comparator for byte ranges. */ + +int +byte_range::cmp (const byte_range &br1, const byte_range &br2) +{ + /* Order first by offset. */ + if (int start_cmp = wi::cmps (br1.m_start_byte_offset, + br2.m_start_byte_offset)) + return start_cmp; + + /* ...then by size. */ + return wi::cmpu (br1.m_size_in_bytes, br2.m_size_in_bytes); +} + /* class concrete_binding : public binding_key. */ /* Implementation of binding_key::dump_to_pp vfunc for concrete_binding. */ void -concrete_binding::dump_to_pp (pretty_printer *pp, bool simple) const +concrete_binding::dump_to_pp (pretty_printer *pp, bool) const { - binding_key::dump_to_pp (pp, simple); - pp_string (pp, ", "); m_bit_range.dump_to_pp (pp); } @@ -402,9 +430,6 @@ concrete_binding::cmp_ptr_ptr (const void *p1, const void *p2) const concrete_binding *b1 = *(const concrete_binding * const *)p1; const concrete_binding *b2 = *(const concrete_binding * const *)p2; - if (int kind_cmp = b1->get_kind () - b2->get_kind ()) - return kind_cmp; - return bit_range::cmp (b1->m_bit_range, b2->m_bit_range); } @@ -413,8 +438,8 @@ concrete_binding::cmp_ptr_ptr (const void *p1, const void *p2) void symbolic_binding::dump_to_pp (pretty_printer *pp, bool simple) const { - binding_key::dump_to_pp (pp, simple); - pp_string (pp, ", region: "); + //binding_key::dump_to_pp (pp, simple); + pp_string (pp, "region: "); m_region->dump_to_pp (pp, simple); } @@ -426,9 +451,6 @@ symbolic_binding::cmp_ptr_ptr (const void *p1, const void *p2) const symbolic_binding *b1 = *(const symbolic_binding * const *)p1; const symbolic_binding *b2 = *(const symbolic_binding * const *)p2; - if (int kind_cmp = b1->get_kind () - b2->get_kind ()) - return kind_cmp; - return region::cmp_ids (b1->get_region (), b2->get_region ()); } @@ -777,8 +799,7 @@ binding_map::apply_ctor_val_to_range (const region *parent_reg, return false; bit_offset_t start_bit_offset = min_offset.get_bit_offset (); store_manager *smgr = mgr->get_store_manager (); - const binding_key *max_element_key - = binding_key::make (smgr, max_element, BK_direct); + const binding_key *max_element_key = binding_key::make (smgr, max_element); if (max_element_key->symbolic_p ()) return false; const concrete_binding *max_element_ckey @@ -786,8 +807,7 @@ binding_map::apply_ctor_val_to_range (const region *parent_reg, bit_size_t range_size_in_bits = max_element_ckey->get_next_bit_offset () - start_bit_offset; const concrete_binding *range_key - = smgr->get_concrete_binding (start_bit_offset, range_size_in_bits, - BK_direct); + = smgr->get_concrete_binding (start_bit_offset, range_size_in_bits); if (range_key->symbolic_p ()) return false; @@ -819,8 +839,7 @@ binding_map::apply_ctor_pair_to_child_region (const region *parent_reg, { const svalue *sval = get_svalue_for_ctor_val (val, mgr); const binding_key *k - = binding_key::make (mgr->get_store_manager (), child_reg, - BK_direct); + = binding_key::make (mgr->get_store_manager (), child_reg); /* Handle the case where we have an unknown size for child_reg (e.g. due to it being a trailing field with incomplete array type. */ @@ -844,7 +863,7 @@ binding_map::apply_ctor_pair_to_child_region (const region *parent_reg, - parent_base_offset.get_bit_offset ()); /* Create a concrete key for the child within the parent. */ k = mgr->get_store_manager ()->get_concrete_binding - (child_parent_offset, sval_bit_size, BK_direct); + (child_parent_offset, sval_bit_size); } gcc_assert (k->concrete_p ()); put (k, sval); @@ -852,6 +871,166 @@ binding_map::apply_ctor_pair_to_child_region (const region *parent_reg, } } +/* Populate OUT with all bindings within this map that overlap KEY. */ + +void +binding_map::get_overlapping_bindings (const binding_key *key, + auto_vec *out) +{ + for (auto iter : *this) + { + const binding_key *iter_key = iter.first; + if (const concrete_binding *ckey + = key->dyn_cast_concrete_binding ()) + { + if (const concrete_binding *iter_ckey + = iter_key->dyn_cast_concrete_binding ()) + { + if (ckey->overlaps_p (*iter_ckey)) + out->safe_push (iter_key); + } + else + { + /* Assume overlap. */ + out->safe_push (iter_key); + } + } + else + { + /* Assume overlap. */ + out->safe_push (iter_key); + } + } +} + +/* Remove, truncate, and/or split any bindings within this map that + overlap DROP_KEY. + + For example, if we have: + + +------------------------------------+ + | old binding | + +------------------------------------+ + + which is to be overwritten with: + + .......+----------------------+....... + .......| new binding |....... + .......+----------------------+....... + + this function "cuts a hole" out of the old binding: + + +------+......................+------+ + |prefix| hole for new binding |suffix| + +------+......................+------+ + + into which the new binding can be added without + overlapping the prefix or suffix. + + The prefix and suffix (if added) will be bound to the pertinent + parts of the value of the old binding. + + For example, given: + struct s5 + { + char arr[8]; + }; + void test_5 (struct s5 *p) + { + struct s5 f = *p; + f.arr[3] = 42; + } + then after the "f = *p;" we have: + cluster for: f: INIT_VAL((*INIT_VAL(p_33(D)))) + and at the "f.arr[3] = 42;" we remove the bindings overlapping + "f.arr[3]", replacing it with a prefix (bytes 0-2) and suffix (bytes 4-7) + giving: + cluster for: f + key: {bytes 0-2} + value: {BITS_WITHIN(bytes 0-2, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))} + key: {bytes 4-7} + value: {BITS_WITHIN(bytes 4-7, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))} + punching a hole into which the new value can be written at byte 3: + cluster for: f + key: {bytes 0-2} + value: {BITS_WITHIN(bytes 0-2, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))} + key: {byte 3} + value: 'char' {(char)42} + key: {bytes 4-7} + value: {BITS_WITHIN(bytes 4-7, inner_val: INIT_VAL((*INIT_VAL(p_33(D))).arr))} + + If UNCERTAINTY is non-NULL, use it to record any svalues that + were removed, as being maybe-bound. */ + +void +binding_map::remove_overlapping_bindings (store_manager *mgr, + const binding_key *drop_key, + uncertainty_t *uncertainty) +{ + auto_vec bindings; + get_overlapping_bindings (drop_key, &bindings); + + unsigned i; + const binding_key *iter_binding; + FOR_EACH_VEC_ELT (bindings, i, iter_binding) + { + const svalue *old_sval = get (iter_binding); + if (uncertainty) + uncertainty->on_maybe_bound_sval (old_sval); + + /* Begin by removing the old binding. */ + m_map.remove (iter_binding); + + /* Now potentially add the prefix and suffix. */ + if (const concrete_binding *drop_ckey + = drop_key->dyn_cast_concrete_binding ()) + if (const concrete_binding *iter_ckey + = iter_binding->dyn_cast_concrete_binding ()) + { + gcc_assert (drop_ckey->overlaps_p (*iter_ckey)); + + const bit_range &drop_bits = drop_ckey->get_bit_range (); + const bit_range &iter_bits = iter_ckey->get_bit_range (); + + if (iter_bits.get_start_bit_offset () + < drop_bits.get_start_bit_offset ()) + { + /* We have a truncated prefix. */ + bit_range prefix_bits (iter_bits.get_start_bit_offset (), + (drop_bits.get_start_bit_offset () + - iter_bits.get_start_bit_offset ())); + const concrete_binding *prefix_key + = mgr->get_concrete_binding (prefix_bits); + bit_range rel_prefix (0, prefix_bits.m_size_in_bits); + const svalue *prefix_sval + = old_sval->extract_bit_range (NULL_TREE, + rel_prefix, + mgr->get_svalue_manager ()); + m_map.put (prefix_key, prefix_sval); + } + + if (iter_bits.get_next_bit_offset () + > drop_bits.get_next_bit_offset ()) + { + /* We have a truncated suffix. */ + bit_range suffix_bits (drop_bits.get_next_bit_offset (), + (iter_bits.get_next_bit_offset () + - drop_bits.get_next_bit_offset ())); + const concrete_binding *suffix_key + = mgr->get_concrete_binding (suffix_bits); + bit_range rel_suffix (drop_bits.get_next_bit_offset () + - iter_bits.get_start_bit_offset (), + suffix_bits.m_size_in_bits); + const svalue *suffix_sval + = old_sval->extract_bit_range (NULL_TREE, + rel_suffix, + mgr->get_svalue_manager ()); + m_map.put (suffix_key, suffix_sval); + } + } + } +} + /* class binding_cluster. */ /* binding_cluster's copy ctor. */ @@ -964,6 +1143,27 @@ binding_cluster::dump (bool simple) const pp_flush (&pp); } +/* Assert that this object is valid. */ + +void +binding_cluster::validate () const +{ + int num_symbolic = 0; + int num_concrete = 0; + for (auto iter : m_map) + { + if (iter.first->symbolic_p ()) + num_symbolic++; + else + num_concrete++; + } + /* We shouldn't have more than one symbolic key per cluster + (or one would have clobbered the other). */ + gcc_assert (num_symbolic < 2); + /* We can't have both concrete and symbolic keys. */ + gcc_assert (num_concrete == 0 || num_symbolic == 0); +} + /* Return a new json::object of the form {"escaped": true/false, "touched": true/false, @@ -986,8 +1186,7 @@ binding_cluster::to_json () const void binding_cluster::bind (store_manager *mgr, - const region *reg, const svalue *sval, - binding_kind kind) + const region *reg, const svalue *sval) { if (const compound_svalue *compound_sval = sval->dyn_cast_compound_svalue ()) @@ -996,7 +1195,7 @@ binding_cluster::bind (store_manager *mgr, return; } - const binding_key *binding = binding_key::make (mgr, reg, kind); + const binding_key *binding = binding_key::make (mgr, reg); bind_key (binding, sval); } @@ -1045,8 +1244,7 @@ binding_cluster::bind_compound_sval (store_manager *mgr, + reg_offset.get_bit_offset ()); const concrete_binding *effective_concrete_key = mgr->get_concrete_binding (effective_start, - concrete_key->get_size_in_bits (), - iter_key->get_kind ()); + concrete_key->get_size_in_bits ()); bind_key (effective_concrete_key, iter_sval); } else @@ -1069,31 +1267,35 @@ binding_cluster::purge_region (store_manager *mgr, const region *reg) { gcc_assert (reg->get_kind () == RK_DECL); const binding_key *binding - = binding_key::make (mgr, const_cast (reg), - BK_direct); + = binding_key::make (mgr, const_cast (reg)); m_map.remove (binding); } -/* Mark REG within this cluster as being filled with zeroes. - Remove all bindings, add a default binding to zero, and clear the - TOUCHED flag. */ +/* Clobber REG and fill it with repeated copies of SVAL. */ void -binding_cluster::zero_fill_region (store_manager *mgr, const region *reg) +binding_cluster::fill_region (store_manager *mgr, + const region *reg, + const svalue *sval) { clobber_region (mgr, reg); - /* Add a default binding to zero. */ region_model_manager *sval_mgr = mgr->get_svalue_manager (); - const svalue *cst_sval - = sval_mgr->get_or_create_int_cst (integer_type_node, 0); - const svalue *bound_sval = cst_sval; - if (reg->get_type ()) - bound_sval = sval_mgr->get_or_create_unaryop (reg->get_type (), NOP_EXPR, - cst_sval); - bind (mgr, reg, bound_sval, BK_default); + const svalue *byte_size_sval = reg->get_byte_size_sval (sval_mgr); + const svalue *fill_sval + = sval_mgr->get_or_create_repeated_svalue (reg->get_type (), + byte_size_sval, sval); + bind (mgr, reg, fill_sval); +} + +/* Clobber REG within this cluster and fill it with zeroes. */ - m_touched = false; +void +binding_cluster::zero_fill_region (store_manager *mgr, const region *reg) +{ + region_model_manager *sval_mgr = mgr->get_svalue_manager (); + const svalue *zero_sval = sval_mgr->get_or_create_int_cst (char_type_node, 0); + fill_region (mgr, reg, zero_sval); } /* Mark REG within this cluster as being unknown. @@ -1111,7 +1313,7 @@ binding_cluster::mark_region_as_unknown (store_manager *mgr, region_model_manager *sval_mgr = mgr->get_svalue_manager (); const svalue *sval = sval_mgr->get_or_create_unknown_svalue (reg->get_type ()); - bind (mgr, reg, sval, BK_default); + bind (mgr, reg, sval); } /* Get any SVAL bound to REG within this cluster via kind KIND, @@ -1119,10 +1321,9 @@ binding_cluster::mark_region_as_unknown (store_manager *mgr, const svalue * binding_cluster::get_binding (store_manager *mgr, - const region *reg, - binding_kind kind) const + const region *reg) const { - const binding_key *reg_binding = binding_key::make (mgr, reg, kind); + const binding_key *reg_binding = binding_key::make (mgr, reg); const svalue *sval = m_map.get (reg_binding); if (sval) { @@ -1140,7 +1341,7 @@ binding_cluster::get_binding (store_manager *mgr, while (const region *parent_reg = reg->get_parent_region ()) { const binding_key *parent_reg_binding - = binding_key::make (mgr, parent_reg, kind); + = binding_key::make (mgr, parent_reg); if (parent_reg_binding == reg_binding && sval->get_type () && reg->get_type () @@ -1161,7 +1362,7 @@ binding_cluster::get_binding (store_manager *mgr, FOR_EACH_VEC_ELT_REVERSE (regions, i, iter_reg) { region_model_manager *rmm_mgr = mgr->get_svalue_manager (); - sval = rmm_mgr->get_or_create_sub_svalue (reg->get_type (), + sval = rmm_mgr->get_or_create_sub_svalue (iter_reg->get_type (), sval, iter_reg); } } @@ -1169,21 +1370,20 @@ binding_cluster::get_binding (store_manager *mgr, return sval; } -/* Get any SVAL bound to REG within this cluster via kind KIND, +/* Get any SVAL bound to REG within this cluster, either directly for REG, or recursively checking for bindings within parent regions and extracting subvalues if need be. */ const svalue * binding_cluster::get_binding_recursive (store_manager *mgr, - const region *reg, - enum binding_kind kind) const + const region *reg) const { - if (const svalue *sval = get_binding (mgr, reg, kind)) + if (const svalue *sval = get_binding (mgr, reg)) return sval; if (reg != m_base_region) if (const region *parent_reg = reg->get_parent_region ()) if (const svalue *parent_sval - = get_binding_recursive (mgr, parent_reg, kind)) + = get_binding_recursive (mgr, parent_reg)) { /* Extract child svalue from parent svalue. */ region_model_manager *rmm_mgr = mgr->get_svalue_manager (); @@ -1199,18 +1399,11 @@ const svalue * binding_cluster::get_any_binding (store_manager *mgr, const region *reg) const { - /* Look for a "direct" binding. */ + /* Look for a direct binding. */ if (const svalue *direct_sval - = get_binding_recursive (mgr, reg, BK_direct)) + = get_binding_recursive (mgr, reg)) return direct_sval; - /* Look for a "default" binding, but not if there's been a symbolic - write. */ - if (!m_touched) - if (const svalue *default_sval - = get_binding_recursive (mgr, reg, BK_default)) - return default_sval; - /* If this cluster has been touched by a symbolic write, then the content of any subregion not currently specifically bound is "UNKNOWN". */ if (m_touched) @@ -1251,8 +1444,6 @@ const svalue * binding_cluster::maybe_get_compound_binding (store_manager *mgr, const region *reg) const { - binding_map map; - region_offset cluster_offset = m_base_region->get_offset (); if (cluster_offset.symbolic_p ()) return NULL; @@ -1260,6 +1451,36 @@ binding_cluster::maybe_get_compound_binding (store_manager *mgr, if (reg_offset.symbolic_p ()) return NULL; + region_model_manager *sval_mgr = mgr->get_svalue_manager (); + + /* We will a build the result map in two parts: + (a) result_map, holding the concrete keys from this cluster, + + (b) default_map, holding the initial values for the region + (e.g. uninitialized, initializer values, or zero), unless this + cluster has been touched. + + We will populate (a), and as we do, clobber (b), trimming and + splitting its bindings as necessary. + Finally, we will merge (b) into (a), giving a concrete map + that merges both the initial values and the bound values from + the binding_cluster. + Doing it this way reduces N for the O(N^2) intersection-finding, + perhaps we should have a spatial-organized data structure for + concrete keys, though. */ + + binding_map result_map; + binding_map default_map; + + /* Set up default values in default_map. */ + const svalue *default_sval; + if (m_touched) + default_sval = sval_mgr->get_or_create_unknown_svalue (reg->get_type ()); + else + default_sval = sval_mgr->get_or_create_initial_value (reg); + const binding_key *default_key = binding_key::make (mgr, reg); + default_map.put (default_key, default_sval); + for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter) { const binding_key *key = (*iter).first; @@ -1268,78 +1489,77 @@ binding_cluster::maybe_get_compound_binding (store_manager *mgr, if (const concrete_binding *concrete_key = key->dyn_cast_concrete_binding ()) { - /* Skip bindings that are outside the bit range of REG. */ - if (concrete_key->get_start_bit_offset () - < reg_offset.get_bit_offset ()) - continue; - bit_size_t reg_bit_size; - if (reg->get_bit_size (®_bit_size)) - if (concrete_key->get_start_bit_offset () - >= reg_offset.get_bit_offset () + reg_bit_size) - continue; - - /* Get offset of KEY relative to REG, rather than to - the cluster. */ - bit_offset_t relative_start - = (concrete_key->get_start_bit_offset () - - reg_offset.get_bit_offset ()); - const concrete_binding *offset_concrete_key - = mgr->get_concrete_binding (relative_start, - concrete_key->get_size_in_bits (), - key->get_kind ()); - map.put (offset_concrete_key, sval); - } - else - return NULL; - } + const bit_range &bound_range = concrete_key->get_bit_range (); - if (map.elements () == 0) - return NULL; + bit_size_t reg_bit_size; + if (!reg->get_bit_size (®_bit_size)) + return NULL; - region_model_manager *sval_mgr = mgr->get_svalue_manager (); - return sval_mgr->get_or_create_compound_svalue (reg->get_type (), map); -} + bit_range reg_range (reg_offset.get_bit_offset (), + reg_bit_size); + /* Skip bindings that are outside the bit range of REG. */ + if (!bound_range.intersects_p (reg_range)) + continue; -/* Populate OUT with all bindings within this cluster that overlap REG. */ + /* We shouldn't have an exact match; that should have been + handled already. */ + gcc_assert (!(reg_range == bound_range)); -void -binding_cluster::get_overlapping_bindings (store_manager *mgr, - const region *reg, - auto_vec *out) -{ - const binding_key *binding - = binding_key::make (mgr, reg, BK_direct); - for (map_t::iterator iter = m_map.begin (); - iter != m_map.end (); ++iter) - { - const binding_key *iter_key = (*iter).first; - if (const concrete_binding *ckey - = binding->dyn_cast_concrete_binding ()) - { - if (const concrete_binding *iter_ckey - = iter_key->dyn_cast_concrete_binding ()) + bit_range subrange (0, 0); + if (reg_range.contains_p (bound_range, &subrange)) { - if (ckey->overlaps_p (*iter_ckey)) - out->safe_push (iter_key); + /* We have a bound range fully within REG. + Add it to map, offsetting accordingly. */ + + /* Get offset of KEY relative to REG, rather than to + the cluster. */ + const concrete_binding *offset_concrete_key + = mgr->get_concrete_binding (subrange); + result_map.put (offset_concrete_key, sval); + + /* Clobber default_map, removing/trimming/spliting where + it overlaps with offset_concrete_key. */ + default_map.remove_overlapping_bindings (mgr, + offset_concrete_key, + NULL); + } + else if (bound_range.contains_p (reg_range, &subrange)) + { + /* REG is fully within the bound range, but + is not equal to it; we're extracting a subvalue. */ + return sval->extract_bit_range (reg->get_type (), + subrange, + mgr->get_svalue_manager ()); } else { - /* Assume overlap. */ - out->safe_push (iter_key); + /* REG and the bound range partially overlap. + We don't handle this case yet. */ + return NULL; } } else - { - /* Assume overlap. */ - out->safe_push (iter_key); - } + /* Can't handle symbolic bindings. */ + return NULL; + } + + if (result_map.elements () == 0) + return NULL; + + /* Merge any bindings from default_map into result_map. */ + for (auto iter : default_map) + { + const binding_key *key = iter.first; + const svalue *sval = iter.second; + result_map.put (key, sval); } + + return sval_mgr->get_or_create_compound_svalue (reg->get_type (), result_map); } -/* Remove any bindings within this cluster that overlap REG, - but retain default bindings that overlap but aren't fully covered - by REG. +/* Remove, truncate, and/or split any bindings within this map that + overlap REG. If UNCERTAINTY is non-NULL, use it to record any svalues that were removed, as being maybe-bound. */ @@ -1348,26 +1568,9 @@ binding_cluster::remove_overlapping_bindings (store_manager *mgr, const region *reg, uncertainty_t *uncertainty) { - auto_vec bindings; - get_overlapping_bindings (mgr, reg, &bindings); + const binding_key *reg_binding = binding_key::make (mgr, reg); - unsigned i; - const binding_key *iter_binding; - FOR_EACH_VEC_ELT (bindings, i, iter_binding) - { - /* Don't remove default bindings, unless the default binding - is fully covered by REG. */ - if (iter_binding->get_kind () == BK_default) - { - const binding_key *reg_binding - = binding_key::make (mgr, reg, BK_default); - if (reg_binding != iter_binding) - continue; - } - if (uncertainty) - uncertainty->on_maybe_bound_sval (m_map.get (iter_binding)); - m_map.remove (iter_binding); - } + m_map.remove_overlapping_bindings (mgr, reg_binding, uncertainty); } /* Attempt to merge CLUSTER_A and CLUSTER_B into OUT_CLUSTER, using @@ -1428,6 +1631,8 @@ binding_cluster::can_merge_p (const binding_cluster *cluster_a, const binding_key *key_b = (*iter_b).first; keys.add (key_b); } + int num_symbolic_keys = 0; + int num_concrete_keys = 0; for (hash_set::iterator iter = keys.begin (); iter != keys.end (); ++iter) { @@ -1435,6 +1640,11 @@ binding_cluster::can_merge_p (const binding_cluster *cluster_a, const svalue *sval_a = cluster_a->get_any_value (key); const svalue *sval_b = cluster_b->get_any_value (key); + if (key->symbolic_p ()) + num_symbolic_keys++; + else + num_concrete_keys++; + if (sval_a == sval_b) { gcc_assert (sval_a); @@ -1463,29 +1673,15 @@ binding_cluster::can_merge_p (const binding_cluster *cluster_a, out_cluster->m_map.put (key, unknown_sval); } - /* Handle the case where we get a default binding from one and a direct - binding from the other. */ - auto_vec duplicate_keys; - for (map_t::iterator iter = out_cluster->m_map.begin (); - iter != out_cluster->m_map.end (); ++iter) - { - const concrete_binding *ckey - = (*iter).first->dyn_cast_concrete_binding (); - if (!ckey) - continue; - if (ckey->get_kind () != BK_direct) - continue; - const concrete_binding *def_ckey - = mgr->get_concrete_binding (ckey->get_start_bit_offset (), - ckey->get_size_in_bits (), - BK_default); - if (out_cluster->m_map.get (def_ckey)) - duplicate_keys.safe_push (def_ckey); + /* We can only have at most one symbolic key per cluster, + and if we do, we can't have any concrete keys. + If this happens, mark the cluster as touched, with no keys. */ + if (num_symbolic_keys >= 2 + || (num_concrete_keys > 0 && num_symbolic_keys > 0)) + { + out_cluster->m_touched = true; + out_cluster->m_map.empty (); } - unsigned i; - const concrete_binding *key; - FOR_EACH_VEC_ELT (duplicate_keys, i, key) - out_cluster->m_map.remove (key); /* We don't handle other kinds of overlaps yet. */ @@ -1558,7 +1754,7 @@ binding_cluster::on_unknown_fncall (const gcall *call, const svalue *sval = mgr->get_svalue_manager ()->get_or_create_conjured_svalue (m_base_region->get_type (), call, m_base_region); - bind (mgr, m_base_region, sval, BK_direct); + bind (mgr, m_base_region, sval); m_touched = true; } @@ -1665,7 +1861,7 @@ binding_cluster::maybe_get_simple_value (store_manager *mgr) const if (m_map.elements () != 1) return NULL; - const binding_key *key = binding_key::make (mgr, m_base_region, BK_direct); + const binding_key *key = binding_key::make (mgr, m_base_region); return get_any_value (key); } @@ -1675,10 +1871,9 @@ binding_cluster::maybe_get_simple_value (store_manager *mgr) const const concrete_binding * store_manager::get_concrete_binding (bit_offset_t start_bit_offset, - bit_offset_t size_in_bits, - enum binding_kind kind) + bit_offset_t size_in_bits) { - concrete_binding b (start_bit_offset, size_in_bits, kind); + concrete_binding b (start_bit_offset, size_in_bits); if (concrete_binding *existing = m_concrete_binding_key_mgr.get (b)) return existing; @@ -1688,10 +1883,9 @@ store_manager::get_concrete_binding (bit_offset_t start_bit_offset, } const symbolic_binding * -store_manager::get_symbolic_binding (const region *reg, - enum binding_kind kind) +store_manager::get_symbolic_binding (const region *reg) { - symbolic_binding b (reg, kind); + symbolic_binding b (reg); if (symbolic_binding *existing = m_symbolic_binding_key_mgr.get (b)) return existing; @@ -1952,6 +2146,15 @@ store::dump (bool simple) const pp_flush (&pp); } +/* Assert that this object is valid. */ + +void +store::validate () const +{ + for (auto iter : m_cluster_map) + iter.second->validate (); +} + /* Return a new json::object of the form {PARENT_REGION_DESC: {BASE_REGION_DESC: object for binding_map, ... for each cluster within parent region}, @@ -2027,7 +2230,7 @@ store::get_any_binding (store_manager *mgr, const region *reg) const void store::set_value (store_manager *mgr, const region *lhs_reg, - const svalue *rhs_sval, enum binding_kind kind, + const svalue *rhs_sval, uncertainty_t *uncertainty) { remove_overlapping_bindings (mgr, lhs_reg); @@ -2054,7 +2257,7 @@ store::set_value (store_manager *mgr, const region *lhs_reg, else { lhs_cluster = get_or_create_cluster (lhs_base_reg); - lhs_cluster->bind (mgr, lhs_reg, rhs_sval, kind); + lhs_cluster->bind (mgr, lhs_reg, rhs_sval); } /* Bindings to a cluster can affect other clusters if a symbolic @@ -2209,16 +2412,26 @@ store::purge_region (store_manager *mgr, const region *reg) } } -/* Zero-fill REG. */ +/* Fill REG with SVAL. */ void -store::zero_fill_region (store_manager *mgr, const region *reg) +store::fill_region (store_manager *mgr, const region *reg, const svalue *sval) { const region *base_reg = reg->get_base_region (); if (base_reg->symbolic_for_unknown_ptr_p ()) return; binding_cluster *cluster = get_or_create_cluster (base_reg); - cluster->zero_fill_region (mgr, reg); + cluster->fill_region (mgr, reg, sval); +} + +/* Zero-fill REG. */ + +void +store::zero_fill_region (store_manager *mgr, const region *reg) +{ + region_model_manager *sval_mgr = mgr->get_svalue_manager (); + const svalue *zero_sval = sval_mgr->get_or_create_int_cst (char_type_node, 0); + fill_region (mgr, reg, zero_sval); } /* Mark REG as having unknown content. */ @@ -2740,26 +2953,18 @@ test_binding_key_overlap () store_manager mgr (NULL); /* Various 8-bit bindings. */ - const concrete_binding *cb_0_7 - = mgr.get_concrete_binding (0, 8, BK_direct); - const concrete_binding *cb_8_15 - = mgr.get_concrete_binding (8, 8, BK_direct); - const concrete_binding *cb_16_23 - = mgr.get_concrete_binding (16, 8, BK_direct); - const concrete_binding *cb_24_31 - = mgr.get_concrete_binding (24, 8, BK_direct); + const concrete_binding *cb_0_7 = mgr.get_concrete_binding (0, 8); + const concrete_binding *cb_8_15 = mgr.get_concrete_binding (8, 8); + const concrete_binding *cb_16_23 = mgr.get_concrete_binding (16, 8); + const concrete_binding *cb_24_31 = mgr.get_concrete_binding (24, 8); /* 16-bit bindings. */ - const concrete_binding *cb_0_15 - = mgr.get_concrete_binding (0, 16, BK_direct); - const concrete_binding *cb_8_23 - = mgr.get_concrete_binding (8, 16, BK_direct); - const concrete_binding *cb_16_31 - = mgr.get_concrete_binding (16, 16, BK_direct); + const concrete_binding *cb_0_15 = mgr.get_concrete_binding (0, 16); + const concrete_binding *cb_8_23 = mgr.get_concrete_binding (8, 16); + const concrete_binding *cb_16_31 = mgr.get_concrete_binding (16, 16); /* 32-bit binding. */ - const concrete_binding *cb_0_31 - = mgr.get_concrete_binding (0, 32, BK_direct); + const concrete_binding *cb_0_31 = mgr.get_concrete_binding (0, 32); /* Everything should self-overlap. */ ASSERT_OVERLAP (cb_0_7, cb_0_7); diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index e0c60e1..2ac2923 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -199,29 +199,6 @@ private: class byte_range; class concrete_binding; -/* An enum for discriminating between "direct" vs "default" levels of - mapping. */ - -enum binding_kind -{ - /* Special-case value for hash support. - This is the initial entry, so that hash traits can have - empty_zero_p = true. */ - BK_empty = 0, - - /* Special-case value for hash support. */ - BK_deleted, - - /* The normal kind of mapping. */ - BK_direct, - - /* A lower-priority kind of mapping, for use when inheriting - default values from a parent region. */ - BK_default -}; - -extern const char *binding_kind_to_string (enum binding_kind kind); - /* Abstract base class for describing ranges of bits within a binding_map that can have svalues bound to them. */ @@ -232,10 +209,9 @@ public: virtual bool concrete_p () const = 0; bool symbolic_p () const { return !concrete_p (); } - static const binding_key *make (store_manager *mgr, const region *r, - enum binding_kind kind); + static const binding_key *make (store_manager *mgr, const region *r); - virtual void dump_to_pp (pretty_printer *pp, bool simple) const; + virtual void dump_to_pp (pretty_printer *pp, bool simple) const = 0; void dump (bool simple) const; label_text get_desc (bool simple=true) const; @@ -244,28 +220,6 @@ public: virtual const concrete_binding *dyn_cast_concrete_binding () const { return NULL; } - - enum binding_kind get_kind () const { return m_kind; } - - void mark_deleted () { m_kind = BK_deleted; } - void mark_empty () { m_kind = BK_empty; } - bool is_deleted () const { return m_kind == BK_deleted; } - bool is_empty () const { return m_kind == BK_empty; } - -protected: - binding_key (enum binding_kind kind) : m_kind (kind) {} - - hashval_t impl_hash () const - { - return m_kind; - } - bool impl_eq (const binding_key &other) const - { - return m_kind == other.m_kind; - } - -private: - enum binding_kind m_kind; }; /* A concrete range of bits. */ @@ -288,6 +242,10 @@ struct bit_range { return m_start_bit_offset + m_size_in_bits; } + bit_offset_t get_last_bit_offset () const + { + return get_next_bit_offset () - 1; + } bool contains_p (bit_offset_t offset) const { @@ -295,6 +253,8 @@ struct bit_range && offset < get_next_bit_offset ()); } + bool contains_p (const bit_range &other, bit_range *out) const; + bool operator== (const bit_range &other) const { return (m_start_bit_offset == other.m_start_bit_offset @@ -327,12 +287,42 @@ struct byte_range {} void dump_to_pp (pretty_printer *pp) const; + void dump () const; + + bool contains_p (byte_offset_t offset) const + { + return (offset >= get_start_byte_offset () + && offset < get_next_byte_offset ()); + } + bool contains_p (const byte_range &other, byte_range *out) const; + + bool operator== (const byte_range &other) const + { + return (m_start_byte_offset == other.m_start_byte_offset + && m_size_in_bytes == other.m_size_in_bytes); + } + byte_offset_t get_start_byte_offset () const + { + return m_start_byte_offset; + } + byte_offset_t get_next_byte_offset () const + { + return m_start_byte_offset + m_size_in_bytes; + } byte_offset_t get_last_byte_offset () const { return m_start_byte_offset + m_size_in_bytes - 1; } + bit_range as_bit_range () const + { + return bit_range (m_start_byte_offset * BITS_PER_UNIT, + m_size_in_bytes * BITS_PER_UNIT); + } + + static int cmp (const byte_range &br1, const byte_range &br2); + byte_offset_t m_start_byte_offset; byte_size_t m_size_in_bytes; }; @@ -346,10 +336,8 @@ public: /* This class is its own key for the purposes of consolidation. */ typedef concrete_binding key_t; - concrete_binding (bit_offset_t start_bit_offset, bit_size_t size_in_bits, - enum binding_kind kind) - : binding_key (kind), - m_bit_range (start_bit_offset, size_in_bits) + concrete_binding (bit_offset_t start_bit_offset, bit_size_t size_in_bits) + : m_bit_range (start_bit_offset, size_in_bits) {} bool concrete_p () const FINAL OVERRIDE { return true; } @@ -358,12 +346,10 @@ public: inchash::hash hstate; hstate.add_wide_int (m_bit_range.m_start_bit_offset); hstate.add_wide_int (m_bit_range.m_size_in_bits); - return hstate.end () ^ binding_key::impl_hash (); + return hstate.end (); } bool operator== (const concrete_binding &other) const { - if (!binding_key::impl_eq (other)) - return false; return m_bit_range == other.m_bit_range; } @@ -392,6 +378,11 @@ public: static int cmp_ptr_ptr (const void *, const void *); + void mark_deleted () { m_bit_range.m_start_bit_offset = -1; } + void mark_empty () { m_bit_range.m_start_bit_offset = -2; } + bool is_deleted () const { return m_bit_range.m_start_bit_offset == -1; } + bool is_empty () const { return m_bit_range.m_start_bit_offset == -2; } + private: bit_range m_bit_range; }; @@ -401,7 +392,7 @@ private: template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -415,21 +406,16 @@ public: /* This class is its own key for the purposes of consolidation. */ typedef symbolic_binding key_t; - symbolic_binding (const region *region, enum binding_kind kind) - : binding_key (kind), - m_region (region) - {} + symbolic_binding (const region *region) : m_region (region) {} bool concrete_p () const FINAL OVERRIDE { return false; } hashval_t hash () const { - return (binding_key::impl_hash () ^ (intptr_t)m_region); + return (intptr_t)m_region; } bool operator== (const symbolic_binding &other) const { - if (!binding_key::impl_eq (other)) - return false; - return (m_region == other.m_region); + return m_region == other.m_region; } void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; @@ -438,6 +424,12 @@ public: static int cmp_ptr_ptr (const void *, const void *); + void mark_deleted () { m_region = reinterpret_cast (1); } + void mark_empty () { m_region = NULL; } + bool is_deleted () const + { return m_region == reinterpret_cast (1); } + bool is_empty () const { return m_region == NULL; } + private: const region *m_region; }; @@ -504,7 +496,13 @@ public: static int cmp (const binding_map &map1, const binding_map &map2); + void remove_overlapping_bindings (store_manager *mgr, + const binding_key *drop_key, + uncertainty_t *uncertainty); + private: + void get_overlapping_bindings (const binding_key *key, + auto_vec *out); bool apply_ctor_val_to_range (const region *parent_reg, region_model_manager *mgr, tree min_index, tree max_index, @@ -553,22 +551,22 @@ public: void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const; void dump (bool simple) const; + void validate () const; + json::object *to_json () const; - void bind (store_manager *mgr, const region *, const svalue *, - binding_kind kind); + void bind (store_manager *mgr, const region *, const svalue *); void clobber_region (store_manager *mgr, const region *reg); void purge_region (store_manager *mgr, const region *reg); + void fill_region (store_manager *mgr, const region *reg, const svalue *sval); void zero_fill_region (store_manager *mgr, const region *reg); void mark_region_as_unknown (store_manager *mgr, const region *reg, uncertainty_t *uncertainty); - const svalue *get_binding (store_manager *mgr, const region *reg, - binding_kind kind) const; + const svalue *get_binding (store_manager *mgr, const region *reg) const; const svalue *get_binding_recursive (store_manager *mgr, - const region *reg, - enum binding_kind kind) const; + const region *reg) const; const svalue *get_any_binding (store_manager *mgr, const region *reg) const; const svalue *maybe_get_compound_binding (store_manager *mgr, @@ -630,8 +628,6 @@ public: private: const svalue *get_any_value (const binding_key *key) const; - void get_overlapping_bindings (store_manager *mgr, const region *reg, - auto_vec *out); void bind_compound_sval (store_manager *mgr, const region *reg, const compound_svalue *compound_sval); @@ -684,6 +680,8 @@ public: void dump (bool simple) const; void summarize_to_pp (pretty_printer *pp, bool simple) const; + void validate () const; + json::object *to_json () const; const svalue *get_any_binding (store_manager *mgr, const region *reg) const; @@ -691,10 +689,11 @@ public: bool called_unknown_fn_p () const { return m_called_unknown_fn; } void set_value (store_manager *mgr, const region *lhs_reg, - const svalue *rhs_sval, enum binding_kind kind, + const svalue *rhs_sval, uncertainty_t *uncertainty); void clobber_region (store_manager *mgr, const region *reg); void purge_region (store_manager *mgr, const region *reg); + void fill_region (store_manager *mgr, const region *reg, const svalue *sval); void zero_fill_region (store_manager *mgr, const region *reg); void mark_region_as_unknown (store_manager *mgr, const region *reg, uncertainty_t *uncertainty); @@ -773,19 +772,15 @@ public: /* binding consolidation. */ const concrete_binding * get_concrete_binding (bit_offset_t start_bit_offset, - bit_offset_t size_in_bits, - enum binding_kind kind); + bit_offset_t size_in_bits); const concrete_binding * - get_concrete_binding (const bit_range &bits, - enum binding_kind kind) + get_concrete_binding (const bit_range &bits) { return get_concrete_binding (bits.get_start_bit_offset (), - bits.m_size_in_bits, - kind); + bits.m_size_in_bits); } const symbolic_binding * - get_symbolic_binding (const region *region, - enum binding_kind kind); + get_symbolic_binding (const region *region); region_model_manager *get_svalue_manager () const { diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index a16563d..6c8afef 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -415,6 +415,27 @@ svalue::cmp_ptr (const svalue *sval1, const svalue *sval2) sub_sval2->get_subregion ()); } break; + case SK_REPEATED: + { + const repeated_svalue *repeated_sval1 = (const repeated_svalue *)sval1; + const repeated_svalue *repeated_sval2 = (const repeated_svalue *)sval2; + return svalue::cmp_ptr (repeated_sval1->get_inner_svalue (), + repeated_sval2->get_inner_svalue ()); + } + break; + case SK_BITS_WITHIN: + { + const bits_within_svalue *bits_within_sval1 + = (const bits_within_svalue *)sval1; + const bits_within_svalue *bits_within_sval2 + = (const bits_within_svalue *)sval2; + if (int cmp = bit_range::cmp (bits_within_sval1->get_bits (), + bits_within_sval2->get_bits ())) + return cmp; + return svalue::cmp_ptr (bits_within_sval1->get_inner_svalue (), + bits_within_sval2->get_inner_svalue ()); + } + break; case SK_UNMERGEABLE: { const unmergeable_svalue *unmergeable_sval1 @@ -515,6 +536,27 @@ svalue::involves_p (const svalue *other) const return v.found_p (); } +/* Extract SUBRANGE from this value, of type TYPE. */ + +const svalue * +svalue::extract_bit_range (tree type, + const bit_range &subrange, + region_model_manager *mgr) const +{ + return mgr->get_or_create_bits_within (type, subrange, this); +} + +/* Base implementation of svalue::maybe_fold_bits_within vfunc. */ + +const svalue * +svalue::maybe_fold_bits_within (tree, + const bit_range &, + region_model_manager *) const +{ + /* By default, don't fold. */ + return NULL; +} + /* class region_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for region_svalue. */ @@ -680,6 +722,26 @@ constant_svalue::eval_condition (const constant_svalue *lhs, return tristate::TS_UNKNOWN; } +/* Implementation of svalue::maybe_fold_bits_within vfunc + for constant_svalue. */ + +const svalue * +constant_svalue::maybe_fold_bits_within (tree type, + const bit_range &, + region_model_manager *mgr) const +{ + /* Bits within an all-zero value are also all zero. */ + if (zerop (m_cst_expr)) + { + if (type) + return mgr->get_or_create_cast (type, this); + else + return this; + } + /* Otherwise, don't fold. */ + return NULL; +} + /* class unknown_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for unknown_svalue. */ @@ -711,6 +773,18 @@ unknown_svalue::accept (visitor *v) const v->visit_unknown_svalue (this); } +/* Implementation of svalue::maybe_fold_bits_within vfunc + for unknown_svalue. */ + +const svalue * +unknown_svalue::maybe_fold_bits_within (tree type, + const bit_range &, + region_model_manager *mgr) const +{ + /* Bits within an unknown_svalue are themselves unknown. */ + return mgr->get_or_create_unknown_svalue (type); +} + /* Get a string for KIND for use in debug dumps. */ const char * @@ -892,6 +966,34 @@ unaryop_svalue::implicitly_live_p (const svalue_set *live_svalues, return get_arg ()->live_p (live_svalues, model); } +/* Implementation of svalue::maybe_fold_bits_within vfunc + for unaryop_svalue. */ + +const svalue * +unaryop_svalue::maybe_fold_bits_within (tree type, + const bit_range &, + region_model_manager *mgr) const +{ + switch (m_op) + { + default: + break; + case NOP_EXPR: + /* A cast of zero is zero. */ + if (tree cst = m_arg->maybe_get_constant ()) + if (zerop (cst)) + { + if (type) + return mgr->get_or_create_cast (type, this); + else + return this; + } + break; + } + /* Otherwise, don't fold. */ + return NULL; +} + /* class binop_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for binop_svalue. */ @@ -995,6 +1097,216 @@ sub_svalue::implicitly_live_p (const svalue_set *live_svalues, return get_parent ()->live_p (live_svalues, model); } +/* class repeated_svalue : public svalue. */ + +/* repeated_svalue'c ctor. */ + +repeated_svalue::repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue) +: svalue (complexity::from_pair (outer_size, inner_svalue), type), + m_outer_size (outer_size), + m_inner_svalue (inner_svalue) +{ +} + +/* Implementation of svalue::dump_to_pp vfunc for repeated_svalue. */ + +void +repeated_svalue::dump_to_pp (pretty_printer *pp, bool simple) const +{ + if (simple) + { + pp_string (pp, "REPEATED("); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } + pp_string (pp, "outer_size: "); + m_outer_size->dump_to_pp (pp, simple); + pp_string (pp, ", inner_val: "); + m_inner_svalue->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } + else + { + pp_string (pp, "repeated_svalue ("); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } + pp_string (pp, "outer_size: "); + m_outer_size->dump_to_pp (pp, simple); + pp_string (pp, ", inner_val: "); + m_inner_svalue->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } +} + +/* Implementation of svalue::accept vfunc for repeated_svalue. */ + +void +repeated_svalue::accept (visitor *v) const +{ + v->visit_repeated_svalue (this); + m_inner_svalue->accept (v); +} + +/* Return true if this value is known to be all zeroes. */ + +bool +repeated_svalue::all_zeroes_p () const +{ + if (tree cst = m_inner_svalue->maybe_get_constant ()) + if (zerop (cst)) + return true; + return false; +} + +/* Implementation of svalue::maybe_fold_bits_within vfunc + for repeated_svalue. */ + +const svalue * +repeated_svalue::maybe_fold_bits_within (tree type, + const bit_range &bits, + region_model_manager *mgr) const +{ + const svalue *innermost_sval = m_inner_svalue; + /* Fold + BITS_WITHIN (range, REPEATED_SVALUE (ZERO)) + to: + REPEATED_SVALUE (ZERO). */ + if (all_zeroes_p ()) + { + byte_range bytes (0,0); + if (bits.as_byte_range (&bytes)) + { + const svalue *byte_size + = mgr->get_or_create_int_cst (size_type_node, + bytes.m_size_in_bytes.to_uhwi ()); + return mgr->get_or_create_repeated_svalue (type, byte_size, + innermost_sval); + } + } + + /* Fold: + BITS_WITHIN (range, REPEATED_SVALUE (INNERMOST_SVALUE)) + to: + BITS_WITHIN (range - offset, INNERMOST_SVALUE) + if range is fully within one instance of INNERMOST_SVALUE. */ + if (tree innermost_type = innermost_sval->get_type ()) + { + bit_size_t element_bit_size; + if (int_size_in_bits (innermost_type, &element_bit_size) + && element_bit_size > 0) + { + HOST_WIDE_INT start_idx + = (bits.get_start_bit_offset () + / element_bit_size).to_shwi (); + HOST_WIDE_INT last_idx + = (bits.get_last_bit_offset () + / element_bit_size).to_shwi (); + if (start_idx == last_idx) + { + bit_offset_t start_of_element + = start_idx * element_bit_size; + bit_range range_within_element + (bits.m_start_bit_offset - start_of_element, + bits.m_size_in_bits); + return mgr->get_or_create_bits_within (type, + range_within_element, + innermost_sval); + } + } + } + + return NULL; +} + +/* class bits_within_svalue : public svalue. */ + +/* bits_within_svalue'c ctor. */ + +bits_within_svalue::bits_within_svalue (tree type, + const bit_range &bits, + const svalue *inner_svalue) +: svalue (complexity (inner_svalue), type), + m_bits (bits), + m_inner_svalue (inner_svalue) +{ +} + +/* Implementation of svalue::dump_to_pp vfunc for bits_within_svalue. */ + +void +bits_within_svalue::dump_to_pp (pretty_printer *pp, bool simple) const +{ + if (simple) + { + pp_string (pp, "BITS_WITHIN("); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } + m_bits.dump_to_pp (pp); + pp_string (pp, ", inner_val: "); + m_inner_svalue->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } + else + { + pp_string (pp, "bits_within_svalue ("); + if (get_type ()) + { + print_quoted_type (pp, get_type ()); + pp_string (pp, ", "); + } + m_bits.dump_to_pp (pp); + pp_string (pp, ", inner_val: "); + m_inner_svalue->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } +} + +/* Implementation of svalue::maybe_fold_bits_within vfunc + for bits_within_svalue. */ + +const svalue * +bits_within_svalue::maybe_fold_bits_within (tree type, + const bit_range &bits, + region_model_manager *mgr) const +{ + /* Fold: + BITS_WITHIN (range1, BITS_WITHIN (range2, VAL)) + to: + BITS_WITHIN (range1 in range 2, VAL). */ + bit_range offset_bits (m_bits.get_start_bit_offset () + + bits.m_start_bit_offset, + bits.m_size_in_bits); + return mgr->get_or_create_bits_within (type, offset_bits, m_inner_svalue); +} + +/* Implementation of svalue::accept vfunc for bits_within_svalue. */ + +void +bits_within_svalue::accept (visitor *v) const +{ + v->visit_bits_within_svalue (this); + m_inner_svalue->accept (v); +} + +/* Implementation of svalue::implicitly_live_p vfunc for bits_within_svalue. */ + +bool +bits_within_svalue::implicitly_live_p (const svalue_set *live_svalues, + const region_model *model) const +{ + return m_inner_svalue->live_p (live_svalues, model); +} + /* class widening_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for widening_svalue. */ @@ -1291,6 +1603,75 @@ compound_svalue::calc_complexity (const binding_map &map) return complexity (num_child_nodes + 1, max_child_depth + 1); } +/* Implementation of svalue::maybe_fold_bits_within vfunc + for compound_svalue. */ + +const svalue * +compound_svalue::maybe_fold_bits_within (tree type, + const bit_range &bits, + region_model_manager *mgr) const +{ + binding_map result_map; + for (auto iter : m_map) + { + const binding_key *key = iter.first; + if (const concrete_binding *conc_key + = key->dyn_cast_concrete_binding ()) + { + /* Ignore concrete bindings outside BITS. */ + if (!conc_key->get_bit_range ().intersects_p (bits)) + continue; + + const svalue *sval = iter.second; + /* Get the position of conc_key relative to BITS. */ + bit_range result_location (conc_key->get_start_bit_offset () + - bits.get_start_bit_offset (), + conc_key->get_size_in_bits ()); + /* If conc_key starts after BITS, trim off leading bits + from the svalue and adjust binding location. */ + if (result_location.m_start_bit_offset < 0) + { + bit_size_t leading_bits_to_drop + = -result_location.m_start_bit_offset; + result_location = bit_range + (0, result_location.m_size_in_bits - leading_bits_to_drop); + bit_range bits_within_sval (leading_bits_to_drop, + result_location.m_size_in_bits); + /* Trim off leading bits from iter_sval. */ + sval = mgr->get_or_create_bits_within (NULL_TREE, + bits_within_sval, + sval); + } + /* If conc_key finishes after BITS, trim off trailing bits + from the svalue and adjust binding location. */ + if (conc_key->get_next_bit_offset () + > bits.get_next_bit_offset ()) + { + bit_size_t trailing_bits_to_drop + = (conc_key->get_next_bit_offset () + - bits.get_next_bit_offset ()); + result_location = bit_range + (result_location.m_start_bit_offset, + result_location.m_size_in_bits - trailing_bits_to_drop); + bit_range bits_within_sval (0, + result_location.m_size_in_bits); + /* Trim off leading bits from iter_sval. */ + sval = mgr->get_or_create_bits_within (NULL_TREE, + bits_within_sval, + sval); + } + const concrete_binding *offset_conc_key + = mgr->get_store_manager ()->get_concrete_binding + (result_location); + result_map.put (offset_conc_key, sval); + } + else + /* If we have any symbolic keys we can't get it as bits. */ + return NULL; + } + return mgr->get_or_create_compound_svalue (type, result_map); +} + /* class conjured_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for conjured_svalue. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index d9e34aa..3965a5f 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -41,6 +41,8 @@ enum svalue_kind SK_UNARYOP, SK_BINOP, SK_SUB, + SK_REPEATED, + SK_BITS_WITHIN, SK_UNMERGEABLE, SK_PLACEHOLDER, SK_WIDENING, @@ -63,6 +65,9 @@ enum svalue_kind unaryop_svalue (SK_UNARYOP): unary operation on another svalue binop_svalue (SK_BINOP): binary operation on two svalues sub_svalue (SK_SUB): the result of accessing a subregion + repeated_svalue (SK_REPEATED): repeating an svalue to fill a larger region + bits_within_svalue (SK_BITS_WITHIN): a range of bits/bytes within a larger + svalue unmergeable_svalue (SK_UNMERGEABLE): a value that is so interesting from a control-flow perspective that it can inhibit state-merging placeholder_svalue (SK_PLACEHOLDER): for use in selftests. @@ -107,6 +112,10 @@ public: dyn_cast_binop_svalue () const { return NULL; } virtual const sub_svalue * dyn_cast_sub_svalue () const { return NULL; } + virtual const repeated_svalue * + dyn_cast_repeated_svalue () const { return NULL; } + virtual const bits_within_svalue * + dyn_cast_bits_within_svalue () const { return NULL; } virtual const unmergeable_svalue * dyn_cast_unmergeable_svalue () const { return NULL; } virtual const widening_svalue * @@ -138,6 +147,16 @@ public: bool involves_p (const svalue *other) const; + const svalue * + extract_bit_range (tree type, + const bit_range &subrange, + region_model_manager *mgr) const; + + virtual const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const; + protected: svalue (complexity c, tree type) : m_complexity (c), m_type (type) @@ -175,9 +194,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; const region *m_reg; @@ -222,7 +241,7 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -253,6 +272,11 @@ public: enum tree_code op, const constant_svalue *rhs); + const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const FINAL OVERRIDE; + private: tree m_cst_expr; }; @@ -285,6 +309,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; }; /* An enum describing a particular kind of "poisoned" value. */ @@ -327,9 +356,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } enum poison_kind m_kind; tree m_type; @@ -364,7 +393,7 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -426,9 +455,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } setjmp_record m_record; tree m_type; @@ -467,7 +496,7 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -548,9 +577,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; enum tree_code m_op; @@ -574,6 +603,11 @@ public: enum tree_code get_op () const { return m_op; } const svalue *get_arg () const { return m_arg; } + const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const FINAL OVERRIDE; + private: enum tree_code m_op; const svalue *m_arg; @@ -592,7 +626,7 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -630,9 +664,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; enum tree_code m_op; @@ -683,7 +717,7 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -719,9 +753,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; const svalue *m_parent_svalue; @@ -762,7 +796,182 @@ is_a_helper ::test (const svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; +}; + +namespace ana { + +/* Concrete subclass of svalue representing repeating an inner svalue + (possibly not a whole number of times) to fill a larger region of + type TYPE of size OUTER_SIZE bytes. */ + +class repeated_svalue : public svalue +{ +public: + /* A support class for uniquifying instances of repeated_svalue. */ + struct key_t + { + key_t (tree type, + const svalue *outer_size, + const svalue *inner_svalue) + : m_type (type), m_outer_size (outer_size), m_inner_svalue (inner_svalue) + {} + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_type); + hstate.add_ptr (m_outer_size); + hstate.add_ptr (m_inner_svalue); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + return (m_type == other.m_type + && m_outer_size == other.m_outer_size + && m_inner_svalue == other.m_inner_svalue); + } + + void mark_deleted () { m_type = reinterpret_cast (1); } + void mark_empty () { m_type = reinterpret_cast (2); } + bool is_deleted () const { return m_type == reinterpret_cast (1); } + bool is_empty () const { return m_type == reinterpret_cast (2); } + + tree m_type; + const svalue *m_outer_size; + const svalue *m_inner_svalue; + }; + repeated_svalue (tree type, + const svalue *outer_size, + const svalue *inner_svalue); + + enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_REPEATED; } + const repeated_svalue *dyn_cast_repeated_svalue () const FINAL OVERRIDE + { + return this; + } + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + void accept (visitor *v) const FINAL OVERRIDE; + + const svalue *get_outer_size () const { return m_outer_size; } + const svalue *get_inner_svalue () const { return m_inner_svalue; } + + bool all_zeroes_p () const; + + const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const FINAL OVERRIDE; + + private: + const svalue *m_outer_size; + const svalue *m_inner_svalue; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::test (const svalue *sval) +{ + return sval->get_kind () == SK_REPEATED; +} + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = false; +}; + +namespace ana { + +/* A range of bits/bytes within another svalue + e.g. bytes 5-39 of INITIAL_SVALUE(R). + These can be generated for prefixes and suffixes when part of a binding + is clobbered, so that we don't lose too much information. */ + +class bits_within_svalue : public svalue +{ +public: + /* A support class for uniquifying instances of bits_within_svalue. */ + struct key_t + { + key_t (tree type, + const bit_range &bits, + const svalue *inner_svalue) + : m_type (type), m_bits (bits), m_inner_svalue (inner_svalue) + {} + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_type); + hstate.add_ptr (m_inner_svalue); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + return (m_type == other.m_type + && m_bits == other.m_bits + && m_inner_svalue == other.m_inner_svalue); + } + + void mark_deleted () { m_type = reinterpret_cast (1); } + void mark_empty () { m_type = reinterpret_cast (2); } + bool is_deleted () const { return m_type == reinterpret_cast (1); } + bool is_empty () const { return m_type == reinterpret_cast (2); } + + tree m_type; + bit_range m_bits; + const svalue *m_inner_svalue; + }; + bits_within_svalue (tree type, + const bit_range &bits, + const svalue *inner_svalue); + + enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_BITS_WITHIN; } + const bits_within_svalue * + dyn_cast_bits_within_svalue () const FINAL OVERRIDE + { + return this; + } + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + void accept (visitor *v) const FINAL OVERRIDE; + bool implicitly_live_p (const svalue_set *, + const region_model *) const FINAL OVERRIDE; + + const bit_range &get_bits () const { return m_bits; } + const svalue *get_inner_svalue () const { return m_inner_svalue; } + + const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const FINAL OVERRIDE; + + private: + const bit_range m_bits; + const svalue *m_inner_svalue; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::test (const svalue *sval) +{ + return sval->get_kind () == SK_BITS_WITHIN; +} + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = false; }; namespace ana { @@ -888,9 +1097,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; function_point m_point; @@ -952,7 +1161,7 @@ is_a_helper ::test (svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { @@ -1000,9 +1209,9 @@ public: } void mark_deleted () { m_type = reinterpret_cast (1); } - void mark_empty () { m_type = NULL_TREE; } + void mark_empty () { m_type = reinterpret_cast (2); } bool is_deleted () const { return m_type == reinterpret_cast (1); } - bool is_empty () const { return m_type == NULL_TREE; } + bool is_empty () const { return m_type == reinterpret_cast (2); } tree m_type; const binding_map *m_map_ptr; @@ -1029,6 +1238,11 @@ public: return key_t (get_type (), &m_map); } + const svalue * + maybe_fold_bits_within (tree type, + const bit_range &subrange, + region_model_manager *mgr) const FINAL OVERRIDE; + private: static complexity calc_complexity (const binding_map &map); @@ -1048,7 +1262,7 @@ is_a_helper ::test (svalue *sval) template <> struct default_hash_traits : public member_function_hash_traits { - static const bool empty_zero_p = true; + static const bool empty_zero_p = false; }; namespace ana { -- cgit v1.1 From 25b6bfea5f14da53116f2d3efe2446de89b9bc03 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 1 Jul 2021 00:16:41 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index cc0eb11..bb9f4d3 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,240 @@ +2021-06-30 David Malcolm + + PR analyzer/95006 + * analyzer.h (class repeated_svalue): New forward decl. + (class bits_within_svalue): New forward decl. + (class sized_region): New forward decl. + (get_field_at_bit_offset): New forward decl. + * engine.cc (exploded_graph::get_or_create_node): Validate the + merged state. + (exploded_graph::maybe_process_run_of_before_supernode_enodes): + Validate the states at each stage. + * program-state.cc (program_state::validate): Validate + m_region_model. + * region-model-impl-calls.cc (region_model::impl_call_memset): + Replace special-case logic for handling constant sizes with + a call to fill_region of a sized_region with the given fill value. + * region-model-manager.cc (maybe_undo_optimize_bit_field_compare): + Drop DK_direct. + (region_model_manager::maybe_fold_sub_svalue): Fold element-based + subregions of an initial value into initial values of an element. + Fold subvalues of repeated svalues. + (region_model_manager::maybe_fold_repeated_svalue): New. + (region_model_manager::get_or_create_repeated_svalue): New. + (get_bit_range_for_field): New. + (get_byte_range_for_field): New. + (get_field_at_byte_range): New. + (region_model_manager::maybe_fold_bits_within_svalue): New. + (region_model_manager::get_or_create_bits_within): New. + (region_model_manager::get_sized_region): New. + (region_model_manager::log_stats): Update for addition of + m_repeated_values_map, m_bits_within_values_map, and + m_sized_regions. + * region-model.cc (region_model::validate): New. + (region_model::on_assignment): Drop enum binding_kind. + (region_model::get_initial_value_for_global): Likewise. + (region_model::get_rvalue_for_bits): Replace body with call to + get_or_create_bits_within. + (region_model::get_capacity): Handle RK_SIZED. + (region_model::set_value): Drop enum binding_kind. + (region_model::fill_region): New. + (region_model::get_representative_path_var_1): Handle RK_SIZED. + * region-model.h (visitor::visit_repeated_svalue): New. + (visitor::visit_bits_within_svalue): New. + (region_model_manager::get_or_create_repeated_svalue): New decl. + (region_model_manager::get_or_create_bits_within): New decl. + (region_model_manager::get_sized_region): New decl. + (region_model_manager::maybe_fold_repeated_svalue): New decl. + (region_model_manager::maybe_fold_bits_within_svalue): New decl. + (region_model_manager::repeated_values_map_t): New typedef. + (region_model_manager::m_repeated_values_map): New field. + (region_model_manager::bits_within_values_map_t): New typedef. + (region_model_manager::m_bits_within_values_map): New field. + (region_model_manager::m_sized_regions): New field. + (region_model::fill_region): New decl. + * region.cc (region::get_base_region): Handle RK_SIZED. + (region::base_region_p): Likewise. + (region::get_byte_size_sval): New. + (get_field_at_bit_offset): Make non-static. + (region::calc_offset): Move implementation of cases to + get_relative_concrete_offset vfunc implementations. Handle + RK_SIZED. + (region::get_relative_concrete_offset): New. + (decl_region::get_svalue_for_initializer): Drop enum binding_kind. + (field_region::get_relative_concrete_offset): New, from + region::calc_offset. + (element_region::get_relative_concrete_offset): Likewise. + (offset_region::get_relative_concrete_offset): Likewise. + (sized_region::accept): New. + (sized_region::dump_to_pp): New. + (sized_region::get_byte_size): New. + (sized_region::get_bit_size): New. + * region.h (enum region_kind): Add RK_SIZED. + (region::dyn_cast_sized_region): New. + (region::get_byte_size): Make virtual. + (region::get_bit_size): Likewise. + (region::get_byte_size_sval): New decl. + (region::get_relative_concrete_offset): New decl. + (field_region::get_relative_concrete_offset): New decl. + (element_region::get_relative_concrete_offset): Likewise. + (offset_region::get_relative_concrete_offset): Likewise. + (class sized_region): New. + * store.cc (binding_kind_to_string): Delete. + (binding_key::make): Drop enum binding_kind. + (binding_key::dump_to_pp): Delete. + (binding_key::cmp_ptrs): Drop enum binding_kind. + (bit_range::contains_p): New. + (byte_range::dump): New. + (byte_range::contains_p): New. + (byte_range::cmp): New. + (concrete_binding::dump_to_pp): Drop enum binding_kind. + (concrete_binding::cmp_ptr_ptr): Likewise. + (symbolic_binding::dump_to_pp): Likewise. + (symbolic_binding::cmp_ptr_ptr): Likewise. + (binding_map::apply_ctor_val_to_range): Likewise. + (binding_map::apply_ctor_pair_to_child_region): Likewise. + (binding_map::get_overlapping_bindings): New. + (binding_map::remove_overlapping_bindings): New. + (binding_cluster::validate): New. + (binding_cluster::bind): Drop enum binding_kind. + (binding_cluster::bind_compound_sval): Likewise. + (binding_cluster::purge_region): Likewise. + (binding_cluster::zero_fill_region): Reimplement in terms of... + (binding_cluster::fill_region): New. + (binding_cluster::mark_region_as_unknown): Drop enum binding_kind. + (binding_cluster::get_binding): Likewise. + (binding_cluster::get_binding_recursive): Likewise. + (binding_cluster::get_any_binding): Likewise. + (binding_cluster::maybe_get_compound_binding): Reimplement. + (binding_cluster::get_overlapping_bindings): Delete. + (binding_cluster::remove_overlapping_bindings): Reimplement in + terms of binding_map::remove_overlapping_bindings. + (binding_cluster::can_merge_p): Update for removal of + enum binding_kind. + (binding_cluster::on_unknown_fncall): Drop enum binding_kind. + (binding_cluster::maybe_get_simple_value): Likewise. + (store_manager::get_concrete_binding): Likewise. + (store_manager::get_symbolic_binding): Likewise. + (store::validate): New. + (store::set_value): Drop enum binding_kind. + (store::zero_fill_region): Reimplement in terms of... + (store::fill_region): New. + (selftest::test_binding_key_overlap): Drop enum binding_kind. + * store.h (enum binding_kind): Delete. + (binding_kind_to_string): Delete decl. + (binding_key::make): Drop enum binding_kind. + (binding_key::dump_to_pp): Make pure virtual. + (binding_key::get_kind): Delete. + (binding_key::mark_deleted): Delete. + (binding_key::mark_empty): Delete. + (binding_key::is_deleted): Delete. + (binding_key::is_empty): Delete. + (binding_key::binding_key): Delete. + (binding_key::impl_hash): Delete. + (binding_key::impl_eq): Delete. + (binding_key::m_kind): Delete. + (bit_range::get_last_bit_offset): New. + (bit_range::contains_p): New. + (byte_range::contains_p): New. + (byte_range::operator==): New. + (byte_range::get_start_byte_offset): New. + (byte_range::get_next_byte_offset): New. + (byte_range::get_last_byte_offset): New. + (byte_range::as_bit_range): New. + (byte_range::cmp): New. + (concrete_binding::concrete_binding): Drop enum binding_kind. + (concrete_binding::hash): Likewise. + (concrete_binding::operator==): Likewise. + (concrete_binding::mark_deleted): New. + (concrete_binding::mark_empty): New. + (concrete_binding::is_deleted): New. + (concrete_binding::is_empty): New. + (default_hash_traits::empty_zero_p): Make false. + (symbolic_binding::symbolic_binding): Drop enum binding_kind. + (symbolic_binding::hash): Likewise. + (symbolic_binding::operator==): Likewise. + (symbolic_binding::mark_deleted): New. + (symbolic_binding::mark_empty): New. + (symbolic_binding::is_deleted): New. + (symbolic_binding::is_empty): New. + (binding_map::remove_overlapping_bindings): New decl. + (binding_map::get_overlapping_bindings): New decl. + (binding_cluster::validate): New decl. + (binding_cluster::bind): Drop enum binding_kind. + (binding_cluster::fill_region): New decl. + (binding_cluster::get_binding): Drop enum binding_kind. + (binding_cluster::get_binding_recursive): Likewise. + (binding_cluster::get_overlapping_bindings): Delete. + (store::validate): New decl. + (store::set_value): Drop enum binding_kind. + (store::fill_region): New decl. + (store_manager::get_concrete_binding): Drop enum binding_kind. + (store_manager::get_symbolic_binding): Likewise. + * svalue.cc (svalue::cmp_ptr): Handle SK_REPEATED and + SK_BITS_WITHIN. + (svalue::extract_bit_range): New. + (svalue::maybe_fold_bits_within): New. + (constant_svalue::maybe_fold_bits_within): New. + (unknown_svalue::maybe_fold_bits_within): New. + (unaryop_svalue::maybe_fold_bits_within): New. + (repeated_svalue::repeated_svalue): New. + (repeated_svalue::dump_to_pp): New. + (repeated_svalue::accept): New. + (repeated_svalue::all_zeroes_p): New. + (repeated_svalue::maybe_fold_bits_within): New. + (bits_within_svalue::bits_within_svalue): New. + (bits_within_svalue::dump_to_pp): New. + (bits_within_svalue::maybe_fold_bits_within): New. + (bits_within_svalue::accept): New. + (bits_within_svalue::implicitly_live_p): New. + (compound_svalue::maybe_fold_bits_within): New. + * svalue.h (enum svalue_kind): Add SK_REPEATED and SK_BITS_WITHIN. + (svalue::dyn_cast_repeated_svalue): New. + (svalue::dyn_cast_bits_within_svalue): New. + (svalue::extract_bit_range): New decl. + (svalue::maybe_fold_bits_within): New vfunc decl. + (region_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (region_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make false. + (constant_svalue::maybe_fold_bits_within): New. + (unknown_svalue::maybe_fold_bits_within): New. + (poisoned_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (poisoned_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make + false. + (setjmp_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (setjmp_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make + false. + (unaryop_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (unaryop_svalue::key_t::is_empty): Likewise. + (unaryop_svalue::maybe_fold_bits_within): New. + (default_hash_traits::empty_zero_p): Make + false. + (binop_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (binop_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make + false. + (sub_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (sub_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make + false. + (class repeated_svalue): New. + (is_a_helper ::test): New. + (struct default_hash_traits): New. + (class bits_within_svalue): New. + (is_a_helper ::test): New. + (struct default_hash_traits): New. + (widening_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (widening_svalue::key_t::is_empty): Likewise. + (default_hash_traits::empty_zero_p): Make + false. + (compound_svalue::key_t::mark_empty): Use 2 rather than NULL_TREE. + (compound_svalue::key_t::is_empty): Likewise. + (compound_svalue::maybe_fold_bits_within): New. + (default_hash_traits::empty_zero_p): Make + false. + 2021-06-28 David Malcolm * analyzer.h (byte_offset_t): New typedef. -- cgit v1.1 From 48e8a7a677b8356df946cd12fbb215538828e747 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 7 Jul 2021 19:29:30 -0400 Subject: analyzer: remove add_any_constraints_from_ssa_def_stmt I'm working on reimplementing -Wanalyzer-use-of-uninitialized-value, but I ran into issues with region_model::add_any_constraints_from_ssa_def_stmt. This function is from the initial commit of the analyzer and walks the SSA names finding conditions that were missed due to the GCC 10 era region_model not retaining useful information on how values were created; as of GCC 11 the symbolic values contain this information, and so the conditions can be reconstructed from them instead. region_model::add_any_constraints_from_ssa_def_stmt is a liability when tracking uninitialized values as it requires looking up SSA values when those values may have been purged, thus greatly complicating detection of uses of uninitialized values. It's simplest to eliminate it and reimplement the condition-finding via the makeup of the svalues, which this patch does. Doing so requires supporting add_condition on svalues rather than just on trees, which requires some changes to ana::state_machine and its subclasses. gcc/analyzer/ChangeLog: * diagnostic-manager.cc (null_assignment_sm_context::get_state): New overload. (null_assignment_sm_context::set_next_state): New overload. (null_assignment_sm_context::get_diagnostic_tree): New. * engine.cc (impl_sm_context::get_state): New overload. (impl_sm_context::set_next_state): New overload. (impl_sm_context::get_diagnostic_tree): New overload. (impl_region_model_context::on_condition): Convert params from tree to const svalue *. * exploded-graph.h (impl_region_model_context::on_condition): Likewise. * region-model.cc (region_model::on_call_pre): Move handling of internal calls to before checking for get_fndecl_for_call. (region_model::add_constraints_from_binop): New. (region_model::add_constraint): Split out into a new overload working on const svalue * rather than tree. Call add_constraints_from_binop. Drop call to add_any_constraints_from_ssa_def_stmt. (region_model::add_any_constraints_from_ssa_def_stmt): Delete. (region_model::add_any_constraints_from_gassign): Delete. (region_model::add_any_constraints_from_gcall): Delete. * region-model.h (region_model::add_any_constraints_from_ssa_def_stmt): Delete. (region_model::add_any_constraints_from_gassign): Delete. (region_model::add_any_constraints_from_gcall): Delete. (region_model::add_constraint): Add overload decl. (region_model::add_constraints_from_binop): New decl. (region_model_context::on_condition): Convert params from tree to const svalue *. (noop_region_model_context::on_condition): Likewise. * sm-file.cc (fileptr_state_machine::condition): Likewise. * sm-malloc.cc (malloc_state_machine::on_condition): Likewise. * sm-pattern-test.cc: Include tristate.h, selftest.h, analyzer/call-string.h, analyzer/program-point.h, analyzer/store.h, and analyzer/region-model.h. (pattern_test_state_machine::on_condition): Convert params from tree to const svalue *. * sm-sensitive.cc (sensitive_state_machine::on_condition): Delete. * sm-signal.cc (signal_state_machine::on_condition): Delete. * sm-taint.cc (taint_state_machine::on_condition): Convert params from tree to const svalue *. * sm.cc: Include tristate.h, selftest.h, analyzer/call-string.h, analyzer/program-point.h, analyzer/store.h, and analyzer/region-model.h. (any_pointer_p): Add overload taking const svalue *sval. * sm.h (any_pointer_p): Add overload taking const svalue *sval. (state_machine::on_condition): Convert params from tree to const svalue *. Provide no-op default implementation. (sm_context::get_state): Add overload taking const svalue *sval. (sm_context::set_next_state): Likewise. (sm_context::on_transition): Likewise. (sm_context::get_diagnostic_tree): Likewise. * svalue.cc (svalue::all_zeroes_p): New. (constant_svalue::all_zeroes_p): New. (repeated_svalue::all_zeroes_p): Convert to vfunc. * svalue.h (svalue::all_zeroes_p): New decl. (constant_svalue::all_zeroes_p): New decl. (repeated_svalue::all_zeroes_p): Convert decl to vfunc. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/pattern-test-2.c: Update expected results. * gcc.dg/plugin/analyzer_gil_plugin.c (gil_state_machine::on_condition): Remove. Signed-off-by: David Malcolm --- gcc/analyzer/diagnostic-manager.cc | 35 +++++ gcc/analyzer/engine.cc | 54 ++++++- gcc/analyzer/exploded-graph.h | 4 +- gcc/analyzer/region-model.cc | 304 +++++++++++++++++++------------------ gcc/analyzer/region-model.h | 29 ++-- gcc/analyzer/sm-file.cc | 15 +- gcc/analyzer/sm-malloc.cc | 10 +- gcc/analyzer/sm-pattern-test.cc | 24 ++- gcc/analyzer/sm-sensitive.cc | 18 --- gcc/analyzer/sm-signal.cc | 21 --- gcc/analyzer/sm-taint.cc | 8 +- gcc/analyzer/sm.cc | 14 ++ gcc/analyzer/sm.h | 34 ++++- gcc/analyzer/svalue.cc | 24 ++- gcc/analyzer/svalue.h | 6 +- 15 files changed, 359 insertions(+), 241 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 7eb4ed8..b7d263b 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1377,6 +1377,14 @@ struct null_assignment_sm_context : public sm_context return current; } + state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED, + const svalue *sval) FINAL OVERRIDE + { + const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx]; + state_machine::state_t current = old_smap->get_state (sval, m_ext_state); + return current; + } + void set_next_state (const gimple *stmt, tree var, state_machine::state_t to, @@ -1401,6 +1409,28 @@ struct null_assignment_sm_context : public sm_context *m_new_state)); } + void set_next_state (const gimple *stmt, + const svalue *sval, + state_machine::state_t to, + tree origin ATTRIBUTE_UNUSED) FINAL OVERRIDE + { + state_machine::state_t from = get_state (stmt, sval); + if (from != m_sm.get_start_state ()) + return; + + const supernode *supernode = m_point->get_supernode (); + int stack_depth = m_point->get_stack_depth (); + + m_emission_path->add_event (new state_change_event (supernode, + m_stmt, + stack_depth, + m_sm, + sval, + from, to, + NULL, + *m_new_state)); + } + void warn (const supernode *, const gimple *, tree, pending_diagnostic *d) FINAL OVERRIDE { @@ -1412,6 +1442,11 @@ struct null_assignment_sm_context : public sm_context return expr; } + tree get_diagnostic_tree (const svalue *sval) FINAL OVERRIDE + { + return m_new_state->m_region_model->get_representative_tree (sval); + } + state_machine::state_t get_global_state () const FINAL OVERRIDE { return 0; diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 4456d9b..01b83a4 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -245,6 +245,16 @@ public: = m_old_smap->get_state (var_old_sval, m_eg.get_ext_state ()); return current; } + state_machine::state_t get_state (const gimple *stmt ATTRIBUTE_UNUSED, + const svalue *sval) + { + logger * const logger = get_logger (); + LOG_FUNC (logger); + state_machine::state_t current + = m_old_smap->get_state (sval, m_eg.get_ext_state ()); + return current; + } + void set_next_state (const gimple *stmt, tree var, @@ -280,6 +290,41 @@ public: to, origin_new_sval, m_eg.get_ext_state ()); } + void set_next_state (const gimple *stmt, + const svalue *sval, + state_machine::state_t to, + tree origin) + { + 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); + + impl_region_model_context new_ctxt (m_eg, m_enode_for_diag, + m_old_state, m_new_state, + NULL, + stmt); + const svalue *origin_new_sval + = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt); + + state_machine::state_t current + = m_old_smap->get_state (sval, m_eg.get_ext_state ()); + if (logger) + { + logger->start_log_line (); + logger->log_partial ("%s: state transition of ", + m_sm.get_name ()); + sval->dump_to_pp (logger->get_printer (), true); + logger->log_partial (": %s -> %s", + current->get_name (), + to->get_name ()); + logger->end_log_line (); + } + m_new_smap->set_state (m_new_state->m_region_model, sval, + to, origin_new_sval, m_eg.get_ext_state ()); + } + void warn (const supernode *snode, const gimple *stmt, tree var, pending_diagnostic *d) FINAL OVERRIDE { @@ -323,6 +368,11 @@ public: return expr; } + tree get_diagnostic_tree (const svalue *sval) FINAL OVERRIDE + { + return m_new_state->m_region_model->get_representative_tree (sval); + } + state_machine::state_t get_global_state () const FINAL OVERRIDE { return m_old_state->m_checker_states[m_sm_idx]->get_global_state (); @@ -654,7 +704,9 @@ impl_region_model_context::on_state_leak (const state_machine &sm, state transitions. */ void -impl_region_model_context::on_condition (tree lhs, enum tree_code op, tree rhs) +impl_region_model_context::on_condition (const svalue *lhs, + enum tree_code op, + const svalue *rhs) { int sm_idx; sm_state_map *smap; diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index eb1baef..2d25e5e 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -59,7 +59,9 @@ class impl_region_model_context : public region_model_context const svalue *sval, state_machine::state_t state); - void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE; + void on_condition (const svalue *lhs, + enum tree_code op, + const svalue *rhs) FINAL OVERRIDE; void on_unknown_change (const svalue *sval, bool is_mutable) FINAL OVERRIDE; diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 4fb6bc9..acbbd11 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -881,12 +881,23 @@ bool region_model::on_call_pre (const gcall *call, region_model_context *ctxt, bool *out_terminate_path) { + call_details cd (call, this, ctxt); + bool unknown_side_effects = false; - if (tree callee_fndecl = get_fndecl_for_call (call, ctxt)) + if (gimple_call_internal_p (call)) { - call_details cd (call, this, ctxt); + switch (gimple_call_internal_fn (call)) + { + default: + break; + case IFN_BUILTIN_EXPECT: + return impl_call_builtin_expect (cd); + } + } + if (tree callee_fndecl = get_fndecl_for_call (call, ctxt)) + { /* The various impl_call_* member functions are implemented in region-model-impl-calls.cc. Having them split out into separate functions makes it easier @@ -958,16 +969,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, on the return value. */ break; } - else if (gimple_call_internal_p (call)) - switch (gimple_call_internal_fn (call)) - { - default: - if (!DECL_PURE_P (callee_fndecl)) - unknown_side_effects = true; - break; - case IFN_BUILTIN_EXPECT: - return impl_call_builtin_expect (cd); - } else if (is_named_call_p (callee_fndecl, "malloc", call, 1)) return impl_call_malloc (cd); else if (is_named_call_p (callee_fndecl, "calloc", call, 2)) @@ -2251,6 +2252,123 @@ region_model::compare_initial_and_pointer (const initial_svalue *init, return tristate::TS_UNKNOWN; } +/* Handle various constraints of the form: + LHS: ((bool)INNER_LHS INNER_OP INNER_RHS)) + OP : == or != + RHS: zero + and (with a cast): + LHS: CAST([long]int, ((bool)INNER_LHS INNER_OP INNER_RHS)) + OP : == or != + RHS: zero + by adding constraints for INNER_LHS INNEROP INNER_RHS. + + Return true if this function can fully handle the constraint; if + so, add the implied constraint(s) and write true to *OUT if they + are consistent with existing constraints, or write false to *OUT + if they contradicts existing constraints. + + Return false for cases that this function doeesn't know how to handle. + + For example, if we're checking a stored conditional, we'll have + something like: + LHS: CAST(long int, (&HEAP_ALLOCATED_REGION(8)!=(int *)0B)) + OP : NE_EXPR + RHS: zero + which this function can turn into an add_constraint of: + (&HEAP_ALLOCATED_REGION(8) != (int *)0B) + + Similarly, optimized && and || conditionals lead to e.g. + if (p && q) + becoming gimple like this: + _1 = p_6 == 0B; + _2 = q_8 == 0B + _3 = _1 | _2 + On the "_3 is false" branch we can have constraints of the form: + ((&HEAP_ALLOCATED_REGION(8)!=(int *)0B) + | (&HEAP_ALLOCATED_REGION(10)!=(int *)0B)) + == 0 + which implies that both _1 and _2 are false, + which this function can turn into a pair of add_constraints of + (&HEAP_ALLOCATED_REGION(8)!=(int *)0B) + and: + (&HEAP_ALLOCATED_REGION(10)!=(int *)0B). */ + +bool +region_model::add_constraints_from_binop (const svalue *outer_lhs, + enum tree_code outer_op, + const svalue *outer_rhs, + bool *out, + region_model_context *ctxt) +{ + while (const svalue *cast = outer_lhs->maybe_undo_cast ()) + outer_lhs = cast; + const binop_svalue *binop_sval = outer_lhs->dyn_cast_binop_svalue (); + if (!binop_sval) + return false; + if (!outer_rhs->all_zeroes_p ()) + return false; + + const svalue *inner_lhs = binop_sval->get_arg0 (); + enum tree_code inner_op = binop_sval->get_op (); + const svalue *inner_rhs = binop_sval->get_arg1 (); + + if (outer_op != NE_EXPR && outer_op != EQ_EXPR) + return false; + + /* We have either + - "OUTER_LHS != false" (i.e. OUTER is true), or + - "OUTER_LHS == false" (i.e. OUTER is false). */ + bool is_true = outer_op == NE_EXPR; + + switch (inner_op) + { + default: + return false; + + case EQ_EXPR: + case NE_EXPR: + { + /* ...and "(inner_lhs OP inner_rhs) == 0" + then (inner_lhs OP inner_rhs) must have the same + logical value as LHS. */ + if (!is_true) + inner_op = invert_tree_comparison (inner_op, false /* honor_nans */); + *out = add_constraint (inner_lhs, inner_op, inner_rhs, ctxt); + return true; + } + break; + + case BIT_AND_EXPR: + if (is_true) + { + /* ...and "(inner_lhs & inner_rhs) != 0" + then both inner_lhs and inner_rhs must be true. */ + const svalue *false_sval + = m_mgr->get_or_create_constant_svalue (boolean_false_node); + bool sat1 = add_constraint (inner_lhs, NE_EXPR, false_sval, ctxt); + bool sat2 = add_constraint (inner_rhs, NE_EXPR, false_sval, ctxt); + *out = sat1 && sat2; + return true; + } + return false; + + case BIT_IOR_EXPR: + if (!is_true) + { + /* ...and "(inner_lhs | inner_rhs) == 0" + i.e. "(inner_lhs | inner_rhs)" is false + then both inner_lhs and inner_rhs must be false. */ + const svalue *false_sval + = m_mgr->get_or_create_constant_svalue (boolean_false_node); + bool sat1 = add_constraint (inner_lhs, EQ_EXPR, false_sval, ctxt); + bool sat2 = add_constraint (inner_rhs, EQ_EXPR, false_sval, ctxt); + *out = sat1 && sat2; + return true; + } + return false; + } +} + /* Attempt to add the constraint "LHS OP RHS" to this region_model. If it is consistent with existing constraints, add it, and return true. Return false if it contradicts existing constraints. @@ -2268,7 +2386,21 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, const svalue *lhs_sval = get_rvalue (lhs, ctxt); const svalue *rhs_sval = get_rvalue (rhs, ctxt); - tristate t_cond = eval_condition (lhs_sval, op, rhs_sval); + return add_constraint (lhs_sval, op, rhs_sval, ctxt); +} + +/* Attempt to add the constraint "LHS OP RHS" to this region_model. + If it is consistent with existing constraints, add it, and return true. + Return false if it contradicts existing constraints. + Use CTXT for reporting any diagnostics associated with the accesses. */ + +bool +region_model::add_constraint (const svalue *lhs, + enum tree_code op, + const svalue *rhs, + region_model_context *ctxt) +{ + tristate t_cond = eval_condition (lhs, op, rhs); /* If we already have the condition, do nothing. */ if (t_cond.is_true ()) @@ -2279,10 +2411,12 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, if (t_cond.is_false ()) return false; - /* Store the constraint. */ - m_constraints->add_constraint (lhs_sval, op, rhs_sval); + bool out; + if (add_constraints_from_binop (lhs, op, rhs, &out, ctxt)) + return out; - add_any_constraints_from_ssa_def_stmt (lhs, op, rhs, ctxt); + /* Store the constraint. */ + m_constraints->add_constraint (lhs, op, rhs); /* Notify the context, if any. This exists so that the state machines in a program_state can be notified about the condition, and so can @@ -2293,9 +2427,10 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, /* If we have ®ION == NULL, then drop dynamic extents for REGION (for the case where REGION is heap-allocated and thus could be NULL). */ - if (op == EQ_EXPR && zerop (rhs)) - if (const region_svalue *region_sval = lhs_sval->dyn_cast_region_svalue ()) - unset_dynamic_extents (region_sval->get_pointee ()); + if (tree rhs_cst = rhs->maybe_get_constant ()) + if (op == EQ_EXPR && zerop (rhs_cst)) + if (const region_svalue *region_sval = lhs->dyn_cast_region_svalue ()) + unset_dynamic_extents (region_sval->get_pointee ()); return true; } @@ -2314,137 +2449,6 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, return sat; } -/* Subroutine of region_model::add_constraint for handling optimized - && and || conditionals. - - If we have an SSA_NAME for a boolean compared against 0, - look at anything implied by the def stmt and call add_constraint - for it (which could recurse). - - For example, if we have - _1 = p_6 == 0B; - _2 = p_8 == 0B - _3 = _1 | _2 - and add the constraint - (_3 == 0), - then the def stmt for _3 implies that _1 and _2 are both false, - and hence we can add the constraints: - p_6 != 0B - p_8 != 0B. */ - -void -region_model::add_any_constraints_from_ssa_def_stmt (tree lhs, - enum tree_code op, - tree rhs, - region_model_context *ctxt) -{ - if (TREE_CODE (lhs) != SSA_NAME) - return; - - if (!zerop (rhs)) - return; - - if (op != NE_EXPR && op != EQ_EXPR) - return; - - gimple *def_stmt = SSA_NAME_DEF_STMT (lhs); - if (const gassign *assign = dyn_cast (def_stmt)) - add_any_constraints_from_gassign (op, rhs, assign, ctxt); - else if (gcall *call = dyn_cast (def_stmt)) - add_any_constraints_from_gcall (op, rhs, call, ctxt); -} - -/* Add any constraints for an SSA_NAME defined by ASSIGN - where the result OP RHS. */ - -void -region_model::add_any_constraints_from_gassign (enum tree_code op, - tree rhs, - const gassign *assign, - region_model_context *ctxt) -{ - /* We have either - - "LHS != false" (i.e. LHS is true), or - - "LHS == false" (i.e. LHS is false). */ - bool is_true = op == NE_EXPR; - - enum tree_code rhs_code = gimple_assign_rhs_code (assign); - - switch (rhs_code) - { - default: - break; - - case NOP_EXPR: - case VIEW_CONVERT_EXPR: - { - add_constraint (gimple_assign_rhs1 (assign), op, rhs, ctxt); - } - break; - - case BIT_AND_EXPR: - { - if (is_true) - { - /* ...and "LHS == (rhs1 & rhs2) i.e. "(rhs1 & rhs2)" is true - then both rhs1 and rhs2 must be true. */ - tree rhs1 = gimple_assign_rhs1 (assign); - tree rhs2 = gimple_assign_rhs2 (assign); - add_constraint (rhs1, NE_EXPR, boolean_false_node, ctxt); - add_constraint (rhs2, NE_EXPR, boolean_false_node, ctxt); - } - } - break; - - case BIT_IOR_EXPR: - { - if (!is_true) - { - /* ...and "LHS == (rhs1 | rhs2) - i.e. "(rhs1 | rhs2)" is false - then both rhs1 and rhs2 must be false. */ - tree rhs1 = gimple_assign_rhs1 (assign); - tree rhs2 = gimple_assign_rhs2 (assign); - add_constraint (rhs1, EQ_EXPR, boolean_false_node, ctxt); - add_constraint (rhs2, EQ_EXPR, boolean_false_node, ctxt); - } - } - break; - - case EQ_EXPR: - case NE_EXPR: - { - /* ...and "LHS == (rhs1 OP rhs2)" - then rhs1 OP rhs2 must have the same logical value as LHS. */ - tree rhs1 = gimple_assign_rhs1 (assign); - tree rhs2 = gimple_assign_rhs2 (assign); - if (!is_true) - rhs_code - = invert_tree_comparison (rhs_code, false /* honor_nans */); - add_constraint (rhs1, rhs_code, rhs2, ctxt); - } - break; - } -} - -/* Add any constraints for an SSA_NAME defined by CALL - where the result OP RHS. */ - -void -region_model::add_any_constraints_from_gcall (enum tree_code op, - tree rhs, - const gcall *call, - region_model_context *ctxt) -{ - if (gimple_call_builtin_p (call, BUILT_IN_EXPECT) - || gimple_call_builtin_p (call, BUILT_IN_EXPECT_WITH_PROBABILITY) - || gimple_call_internal_p (call, IFN_BUILTIN_EXPECT)) - { - /* __builtin_expect's return value is its initial argument. */ - add_constraint (gimple_call_arg (call, 0), op, rhs, ctxt); - } -} - /* Determine what is known about the condition "LHS OP RHS" within this model. Use CTXT for reporting any diagnostics associated with the accesses. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index b42852b..cf5232d 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -691,18 +691,15 @@ class region_model get_representative_path_var_1 (const region *reg, svalue_set *visited) const; - void add_any_constraints_from_ssa_def_stmt (tree lhs, - enum tree_code op, - tree rhs, - region_model_context *ctxt); - void add_any_constraints_from_gassign (enum tree_code op, - tree rhs, - const gassign *assign, - region_model_context *ctxt); - void add_any_constraints_from_gcall (enum tree_code op, - tree rhs, - const gcall *call, - region_model_context *ctxt); + bool add_constraint (const svalue *lhs, + enum tree_code op, + const svalue *rhs, + region_model_context *ctxt); + bool add_constraints_from_binop (const svalue *outer_lhs, + enum tree_code outer_op, + const svalue *outer_rhs, + bool *out, + region_model_context *ctxt); void update_for_call_superedge (const call_superedge &call_edge, region_model_context *ctxt); @@ -781,7 +778,9 @@ class region_model_context and use them to trigger sm-state transitions (e.g. transitions due to ptrs becoming known to be NULL or non-NULL, rather than just "unchecked") */ - virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0; + virtual void on_condition (const svalue *lhs, + enum tree_code op, + const svalue *rhs) = 0; /* Hooks for clients to be notified when an unknown change happens to SVAL (in response to a call to an unknown function). */ @@ -812,9 +811,9 @@ public: void on_liveness_change (const svalue_set &, const region_model *) OVERRIDE {} logger *get_logger () OVERRIDE { return NULL; } - void on_condition (tree lhs ATTRIBUTE_UNUSED, + void on_condition (const svalue *lhs ATTRIBUTE_UNUSED, enum tree_code op ATTRIBUTE_UNUSED, - tree rhs ATTRIBUTE_UNUSED) OVERRIDE + const svalue *rhs ATTRIBUTE_UNUSED) OVERRIDE { } void on_unknown_change (const svalue *sval ATTRIBUTE_UNUSED, diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index 3a5f95d..b40a9a1 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -77,9 +77,9 @@ public: void on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const FINAL OVERRIDE; + const svalue *rhs) const FINAL OVERRIDE; bool can_purge_p (state_t s) const FINAL OVERRIDE; pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE; @@ -381,19 +381,18 @@ void fileptr_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const + const svalue *rhs) const { - if (!zerop (rhs)) + if (!rhs->all_zeroes_p ()) return; // TODO: has to be a FILE *, specifically - if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE) + if (!any_pointer_p (lhs)) return; - // TODO: has to be a FILE *, specifically - if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE) + if (!any_pointer_p (rhs)) return; if (op == NE_EXPR) diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index a1582ca..40e64b3 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -375,9 +375,9 @@ public: void on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const FINAL OVERRIDE; + const svalue *rhs) const FINAL OVERRIDE; bool can_purge_p (state_t s) const FINAL OVERRIDE; pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE; @@ -1863,11 +1863,11 @@ void malloc_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node ATTRIBUTE_UNUSED, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const + const svalue *rhs) const { - if (!zerop (rhs)) + if (!rhs->all_zeroes_p ()) return; if (!any_pointer_p (lhs)) diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc index 43b8475..4e28549 100644 --- a/gcc/analyzer/sm-pattern-test.cc +++ b/gcc/analyzer/sm-pattern-test.cc @@ -37,6 +37,12 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/analyzer-logging.h" #include "analyzer/sm.h" #include "analyzer/pending-diagnostic.h" +#include "tristate.h" +#include "selftest.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" #if ENABLE_ANALYZER @@ -61,9 +67,9 @@ public: void on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const FINAL OVERRIDE; + const svalue *rhs) const FINAL OVERRIDE; bool can_purge_p (state_t s) const FINAL OVERRIDE; }; @@ -118,18 +124,22 @@ void pattern_test_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const + const svalue *rhs) const { if (stmt == NULL) return; - if (!CONSTANT_CLASS_P (rhs)) + tree rhs_cst = rhs->maybe_get_constant (); + if (!rhs_cst) return; - pending_diagnostic *diag = new pattern_match (lhs, op, rhs); - sm_ctxt->warn (node, stmt, lhs, diag); + if (tree lhs_expr = sm_ctxt->get_diagnostic_tree (lhs)) + { + pending_diagnostic *diag = new pattern_match (lhs_expr, op, rhs_cst); + sm_ctxt->warn (node, stmt, lhs_expr, diag); + } } bool diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc index 9703f7e..4add55e 100644 --- a/gcc/analyzer/sm-sensitive.cc +++ b/gcc/analyzer/sm-sensitive.cc @@ -58,13 +58,6 @@ public: const supernode *node, const gimple *stmt) const FINAL OVERRIDE; - void on_condition (sm_context *sm_ctxt, - const supernode *node, - const gimple *stmt, - tree lhs, - enum tree_code op, - tree rhs) const FINAL OVERRIDE; - bool can_purge_p (state_t s) const FINAL OVERRIDE; /* State for "sensitive" data, such as a password. */ @@ -222,17 +215,6 @@ sensitive_state_machine::on_stmt (sm_context *sm_ctxt, return false; } -void -sensitive_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED, - const supernode *node ATTRIBUTE_UNUSED, - const gimple *stmt ATTRIBUTE_UNUSED, - tree lhs ATTRIBUTE_UNUSED, - enum tree_code op ATTRIBUTE_UNUSED, - tree rhs ATTRIBUTE_UNUSED) const -{ - /* Empty. */ -} - bool sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const { diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc index 42be809..fcbf322 100644 --- a/gcc/analyzer/sm-signal.cc +++ b/gcc/analyzer/sm-signal.cc @@ -81,13 +81,6 @@ public: const supernode *node, const gimple *stmt) const FINAL OVERRIDE; - void on_condition (sm_context *sm_ctxt, - const supernode *node, - const gimple *stmt, - tree lhs, - enum tree_code op, - tree rhs) const FINAL OVERRIDE; - bool can_purge_p (state_t s) const FINAL OVERRIDE; /* These states are "global", rather than per-expression. */ @@ -363,20 +356,6 @@ signal_state_machine::on_stmt (sm_context *sm_ctxt, return false; } -/* Implementation of state_machine::on_condition vfunc for - signal_state_machine. */ - -void -signal_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED, - const supernode *node ATTRIBUTE_UNUSED, - const gimple *stmt ATTRIBUTE_UNUSED, - tree lhs ATTRIBUTE_UNUSED, - enum tree_code op ATTRIBUTE_UNUSED, - tree rhs ATTRIBUTE_UNUSED) const -{ - // Empty -} - bool signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const { diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc index e2460f9..721d3ea 100644 --- a/gcc/analyzer/sm-taint.cc +++ b/gcc/analyzer/sm-taint.cc @@ -61,9 +61,9 @@ public: void on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs) const FINAL OVERRIDE; + const svalue *rhs) const FINAL OVERRIDE; bool can_purge_p (state_t s) const FINAL OVERRIDE; @@ -281,9 +281,9 @@ void taint_state_machine::on_condition (sm_context *sm_ctxt, const supernode *node, const gimple *stmt, - tree lhs, + const svalue *lhs, enum tree_code op, - tree rhs ATTRIBUTE_UNUSED) const + const svalue *rhs ATTRIBUTE_UNUSED) const { if (stmt == NULL) return; diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc index 2d227dd..db07bf3 100644 --- a/gcc/analyzer/sm.cc +++ b/gcc/analyzer/sm.cc @@ -35,6 +35,11 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/analyzer.h" #include "analyzer/analyzer-logging.h" #include "analyzer/sm.h" +#include "tristate.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/svalue.h" #if ENABLE_ANALYZER @@ -48,6 +53,15 @@ any_pointer_p (tree var) return POINTER_TYPE_P (TREE_TYPE (var)); } +/* Return true if SVAL has pointer or reference type. */ + +bool +any_pointer_p (const svalue *sval) +{ + if (!sval->get_type ()) + return false; + return POINTER_TYPE_P (sval->get_type ()); +} /* class state_machine::state. */ diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index 8d4d030..6bb036e 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -29,7 +29,8 @@ class state_machine; class sm_context; class pending_diagnostic; -extern bool any_pointer_p (tree var); +extern bool any_pointer_p (tree expr); +extern bool any_pointer_p (const svalue *sval); /* An abstract base class for a state machine describing an API. Manages a set of state objects, and has various virtual functions @@ -89,10 +90,14 @@ public: { } - virtual void on_condition (sm_context *sm_ctxt, - const supernode *node, - const gimple *stmt, - tree lhs, enum tree_code op, tree rhs) const = 0; + virtual void on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED, + const supernode *node ATTRIBUTE_UNUSED, + const gimple *stmt ATTRIBUTE_UNUSED, + const svalue *lhs ATTRIBUTE_UNUSED, + enum tree_code op ATTRIBUTE_UNUSED, + const svalue *rhs ATTRIBUTE_UNUSED) const + { + } /* Return true if it safe to discard the given state (to help when simplifying state objects). @@ -182,6 +187,8 @@ public: /* Get the old state of VAR at STMT. */ virtual state_machine::state_t get_state (const gimple *stmt, tree var) = 0; + virtual state_machine::state_t get_state (const gimple *stmt, + const svalue *) = 0; /* Set the next state of VAR to be TO, recording the "origin" of the state as ORIGIN. Use STMT for location information. */ @@ -189,6 +196,10 @@ public: tree var, state_machine::state_t to, tree origin = NULL_TREE) = 0; + virtual void set_next_state (const gimple *stmt, + const svalue *var, + state_machine::state_t to, + tree origin = NULL_TREE) = 0; /* Called by state_machine in response to pattern matches: if VAR is in state FROM, transition it to state TO, potentially @@ -206,6 +217,18 @@ public: set_next_state (stmt, var, to, origin); } + void on_transition (const supernode *node ATTRIBUTE_UNUSED, + const gimple *stmt, + const svalue *var, + state_machine::state_t from, + state_machine::state_t to, + tree origin = NULL_TREE) + { + state_machine::state_t current = get_state (stmt, var); + if (current == from) + set_next_state (stmt, var, to, origin); + } + /* Called by state_machine in response to pattern matches: issue a diagnostic D using NODE and STMT for location information. */ virtual void warn (const supernode *node, const gimple *stmt, @@ -220,6 +243,7 @@ public: { return expr; } + virtual tree get_diagnostic_tree (const svalue *) = 0; virtual state_machine::state_t get_global_state () const = 0; virtual void set_global_state (state_machine::state_t) = 0; diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 6c8afef..70c23f0 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -557,6 +557,15 @@ svalue::maybe_fold_bits_within (tree, return NULL; } +/* Base implementation of svalue::all_zeroes_p. + Return true if this value is known to be all zeroes. */ + +bool +svalue::all_zeroes_p () const +{ + return false; +} + /* class region_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for region_svalue. */ @@ -742,6 +751,14 @@ constant_svalue::maybe_fold_bits_within (tree type, return NULL; } +/* Implementation of svalue::all_zeroes_p for constant_svalue. */ + +bool +constant_svalue::all_zeroes_p () const +{ + return zerop (m_cst_expr); +} + /* class unknown_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for unknown_svalue. */ @@ -1154,15 +1171,12 @@ repeated_svalue::accept (visitor *v) const m_inner_svalue->accept (v); } -/* Return true if this value is known to be all zeroes. */ +/* Implementation of svalue::all_zeroes_p for repeated_svalue. */ bool repeated_svalue::all_zeroes_p () const { - if (tree cst = m_inner_svalue->maybe_get_constant ()) - if (zerop (cst)) - return true; - return false; + return m_inner_svalue->all_zeroes_p (); } /* Implementation of svalue::maybe_fold_bits_within vfunc diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 3965a5f..5552fcf 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -157,6 +157,8 @@ public: const bit_range &subrange, region_model_manager *mgr) const; + virtual bool all_zeroes_p () const; + protected: svalue (complexity c, tree type) : m_complexity (c), m_type (type) @@ -277,6 +279,8 @@ public: const bit_range &subrange, region_model_manager *mgr) const FINAL OVERRIDE; + bool all_zeroes_p () const FINAL OVERRIDE; + private: tree m_cst_expr; }; @@ -858,7 +862,7 @@ public: const svalue *get_outer_size () const { return m_outer_size; } const svalue *get_inner_svalue () const { return m_inner_svalue; } - bool all_zeroes_p () const; + bool all_zeroes_p () const FINAL OVERRIDE; const svalue * maybe_fold_bits_within (tree type, -- cgit v1.1 From c24a97078221fad98d1f48ed9bd1af2094e1a01d Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 8 Jul 2021 00:16:27 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index bb9f4d3..f0b2d96 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,64 @@ +2021-07-07 David Malcolm + + * diagnostic-manager.cc (null_assignment_sm_context::get_state): + New overload. + (null_assignment_sm_context::set_next_state): New overload. + (null_assignment_sm_context::get_diagnostic_tree): New. + * engine.cc (impl_sm_context::get_state): New overload. + (impl_sm_context::set_next_state): New overload. + (impl_sm_context::get_diagnostic_tree): New overload. + (impl_region_model_context::on_condition): Convert params from + tree to const svalue *. + * exploded-graph.h (impl_region_model_context::on_condition): + Likewise. + * region-model.cc (region_model::on_call_pre): Move handling of + internal calls to before checking for get_fndecl_for_call. + (region_model::add_constraints_from_binop): New. + (region_model::add_constraint): Split out into a new overload + working on const svalue * rather than tree. Call + add_constraints_from_binop. Drop call to + add_any_constraints_from_ssa_def_stmt. + (region_model::add_any_constraints_from_ssa_def_stmt): Delete. + (region_model::add_any_constraints_from_gassign): Delete. + (region_model::add_any_constraints_from_gcall): Delete. + * region-model.h + (region_model::add_any_constraints_from_ssa_def_stmt): Delete. + (region_model::add_any_constraints_from_gassign): Delete. + (region_model::add_any_constraints_from_gcall): Delete. + (region_model::add_constraint): Add overload decl. + (region_model::add_constraints_from_binop): New decl. + (region_model_context::on_condition): Convert params from tree to + const svalue *. + (noop_region_model_context::on_condition): Likewise. + * sm-file.cc (fileptr_state_machine::condition): Likewise. + * sm-malloc.cc (malloc_state_machine::on_condition): Likewise. + * sm-pattern-test.cc: Include tristate.h, selftest.h, + analyzer/call-string.h, analyzer/program-point.h, + analyzer/store.h, and analyzer/region-model.h. + (pattern_test_state_machine::on_condition): Convert params from tree to + const svalue *. + * sm-sensitive.cc (sensitive_state_machine::on_condition): Delete. + * sm-signal.cc (signal_state_machine::on_condition): Delete. + * sm-taint.cc (taint_state_machine::on_condition): Convert params + from tree to const svalue *. + * sm.cc: Include tristate.h, selftest.h, analyzer/call-string.h, + analyzer/program-point.h, analyzer/store.h, and + analyzer/region-model.h. + (any_pointer_p): Add overload taking const svalue *sval. + * sm.h (any_pointer_p): Add overload taking const svalue *sval. + (state_machine::on_condition): Convert params from tree to + const svalue *. Provide no-op default implementation. + (sm_context::get_state): Add overload taking const svalue *sval. + (sm_context::set_next_state): Likewise. + (sm_context::on_transition): Likewise. + (sm_context::get_diagnostic_tree): Likewise. + * svalue.cc (svalue::all_zeroes_p): New. + (constant_svalue::all_zeroes_p): New. + (repeated_svalue::all_zeroes_p): Convert to vfunc. + * svalue.h (svalue::all_zeroes_p): New decl. + (constant_svalue::all_zeroes_p): New decl. + (repeated_svalue::all_zeroes_p): Convert decl to vfunc. + 2021-06-30 David Malcolm PR analyzer/95006 -- cgit v1.1 From a9241df96e1950c630550ada9371c0b4a03496cf Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 15 Jul 2021 15:01:57 -0400 Subject: analyzer: handle self-referential phis gcc/analyzer/ChangeLog: * state-purge.cc (self_referential_phi_p): New. (state_purge_per_ssa_name::process_point): Don't purge an SSA name at its def-stmt if the def-stmt is self-referential. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/phi-1.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/state-purge.cc | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index 70a09ed..e82ea87 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -288,6 +288,20 @@ state_purge_per_ssa_name::add_to_worklist (const function_point &point, } } +/* Does this phi depend on itself? + e.g. in: + added_2 = PHI + the middle defn (from edge 3) requires added_2 itself. */ + +static bool +self_referential_phi_p (const gphi *phi) +{ + for (unsigned i = 0; i < gimple_phi_num_args (phi); i++) + if (gimple_phi_arg_def (phi, i) == gimple_phi_result (phi)) + return true; + return false; +} + /* Process POINT, popped from WORKLIST. Iterate over predecessors of POINT, adding to WORKLIST. */ @@ -326,11 +340,28 @@ state_purge_per_ssa_name::process_point (const function_point &point, !gsi_end_p (gpi); gsi_next (&gpi)) { gphi *phi = gpi.phi (); + /* Are we at the def-stmt for m_name? */ if (phi == def_stmt) { - if (logger) - logger->log ("def stmt within phis; terminating"); - return; + /* Does this phi depend on itself? + e.g. in: + added_2 = PHI + the middle defn (from edge 3) requires added_2 itself + so we can't purge it here. */ + if (self_referential_phi_p (phi)) + { + if (logger) + logger->log ("self-referential def stmt within phis;" + " continuing"); + } + else + { + /* Otherwise, we can stop here, so that m_name + can be purged. */ + if (logger) + logger->log ("def stmt within phis; terminating"); + return; + } } } -- cgit v1.1 From e9711fe482b4abef0e7572809d3593631991276e Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 15 Jul 2021 15:02:42 -0400 Subject: analyzer: use DECL_DEBUG_EXPR on SSA names for artificial vars gcc/analyzer/ChangeLog: * analyzer.cc (fixup_tree_for_diagnostic_1): Use DECL_DEBUG_EXPR if it's available. * engine.cc (readability): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.cc | 9 +++++++-- gcc/analyzer/engine.cc | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index 12c03f6..a8ee1a1 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -165,8 +165,13 @@ fixup_tree_for_diagnostic_1 (tree expr, hash_set *visited) && TREE_CODE (expr) == SSA_NAME && (SSA_NAME_VAR (expr) == NULL_TREE || DECL_ARTIFICIAL (SSA_NAME_VAR (expr)))) - if (tree expr2 = maybe_reconstruct_from_def_stmt (expr, visited)) - return expr2; + { + if (tree var = SSA_NAME_VAR (expr)) + if (VAR_P (var) && DECL_HAS_DEBUG_EXPR_P (var)) + return DECL_DEBUG_EXPR (var); + if (tree expr2 = maybe_reconstruct_from_def_stmt (expr, visited)) + return expr2; + } return expr; } diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 01b83a4..8f3e7f7 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -527,9 +527,22 @@ readability (const_tree expr) case SSA_NAME: { if (tree var = SSA_NAME_VAR (expr)) - /* Slightly favor the underlying var over the SSA name to - avoid having them compare equal. */ - return readability (var) - 1; + { + if (DECL_ARTIFICIAL (var)) + { + /* If we have an SSA name for an artificial var, + only use it if it has a debug expr associated with + it that fixup_tree_for_diagnostic can use. */ + if (VAR_P (var) && DECL_HAS_DEBUG_EXPR_P (var)) + return readability (DECL_DEBUG_EXPR (var)) - 1; + } + else + { + /* Slightly favor the underlying var over the SSA name to + avoid having them compare equal. */ + return readability (var) - 1; + } + } /* Avoid printing '' for SSA names for temporaries. */ return -1; } -- cgit v1.1 From 98cd4d123aa14598b1f0d54c22663c8200a96d9c Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 15 Jul 2021 15:04:07 -0400 Subject: analyzer: add -fdump-analyzer-exploded-paths gcc/analyzer/ChangeLog: * analyzer.opt (fdump-analyzer-exploded-paths): New. * diagnostic-manager.cc (diagnostic_manager::emit_saved_diagnostic): Implement it. * engine.cc (exploded_path::dump_to_pp): Add ext_state param and use it to dump states if non-NULL. (exploded_path::dump): Likewise. (exploded_path::dump_to_file): New. * exploded-graph.h (exploded_path::dump_to_pp): Add ext_state param. (exploded_path::dump): Likewise. (exploded_path::dump): Likewise. (exploded_path::dump_to_file): New. gcc/ChangeLog: * doc/invoke.texi (-fdump-analyzer-exploded-paths): New. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.opt | 4 ++++ gcc/analyzer/diagnostic-manager.cc | 11 +++++++++++ gcc/analyzer/engine.cc | 34 ++++++++++++++++++++++++++++------ gcc/analyzer/exploded-graph.h | 9 ++++++--- 4 files changed, 49 insertions(+), 9 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index dd34495..7b77ae8 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -210,6 +210,10 @@ fdump-analyzer-exploded-nodes-3 Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_3) Dump a textual representation of the exploded graph to SRCFILE.eg-ID.txt. +fdump-analyzer-exploded-paths +Common RejectNegative Var(flag_dump_analyzer_exploded_paths) +Dump a textual representation of each diagnostic's exploded path to SRCFILE.IDX.KIND.epath.txt. + fdump-analyzer-feasibility Common RejectNegative Var(flag_dump_analyzer_feasibility) Dump various analyzer internals to SRCFILE.*.fg.dot and SRCFILE.*.tg.dot. diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index b7d263b..d005fac 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1164,6 +1164,17 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, inform_n (loc, num_dupes, "%i duplicate", "%i duplicates", num_dupes); + if (flag_dump_analyzer_exploded_paths) + { + auto_timevar tv (TV_ANALYZER_DUMP); + pretty_printer pp; + pp_printf (&pp, "%s.%i.%s.epath.txt", + dump_base_name, sd.get_index (), sd.m_d->get_kind ()); + char *filename = xstrdup (pp_formatted_text (&pp)); + epath->dump_to_file (filename, eg.get_ext_state ()); + inform (loc, "exploded path written to %qs", filename); + free (filename); + } } delete pp; } diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 8f3e7f7..dc07a79 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -3630,10 +3630,12 @@ exploded_path::feasible_p (logger *logger, feasibility_problem **out, return true; } -/* Dump this path in multiline form to PP. */ +/* Dump this path in multiline form to PP. + If EXT_STATE is non-NULL, then show the nodes. */ void -exploded_path::dump_to_pp (pretty_printer *pp) const +exploded_path::dump_to_pp (pretty_printer *pp, + const extrinsic_state *ext_state) const { for (unsigned i = 0; i < m_edges.length (); i++) { @@ -3643,28 +3645,48 @@ exploded_path::dump_to_pp (pretty_printer *pp) const eedge->m_src->m_index, eedge->m_dest->m_index); pp_newline (pp); + + if (ext_state) + eedge->m_dest->dump_to_pp (pp, *ext_state); } } /* Dump this path in multiline form to FP. */ void -exploded_path::dump (FILE *fp) const +exploded_path::dump (FILE *fp, const extrinsic_state *ext_state) const { pretty_printer pp; pp_format_decoder (&pp) = default_tree_printer; pp_show_color (&pp) = pp_show_color (global_dc->printer); pp.buffer->stream = fp; - dump_to_pp (&pp); + dump_to_pp (&pp, ext_state); pp_flush (&pp); } /* Dump this path in multiline form to stderr. */ DEBUG_FUNCTION void -exploded_path::dump () const +exploded_path::dump (const extrinsic_state *ext_state) const { - dump (stderr); + dump (stderr, ext_state); +} + +/* Dump this path verbosely to FILENAME. */ + +void +exploded_path::dump_to_file (const char *filename, + const extrinsic_state &ext_state) const +{ + FILE *fp = fopen (filename, "w"); + if (!fp) + return; + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp.buffer->stream = fp; + dump_to_pp (&pp, &ext_state); + pp_flush (&pp); + fclose (fp); } /* class feasibility_problem. */ diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 2d25e5e..1d8b73d 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -895,9 +895,12 @@ public: exploded_node *get_final_enode () const; - void dump_to_pp (pretty_printer *pp) const; - void dump (FILE *fp) const; - void dump () const; + void dump_to_pp (pretty_printer *pp, + const extrinsic_state *ext_state) const; + void dump (FILE *fp, const extrinsic_state *ext_state) const; + void dump (const extrinsic_state *ext_state = NULL) const; + void dump_to_file (const char *filename, + const extrinsic_state &ext_state) const; bool feasible_p (logger *logger, feasibility_problem **out, engine *eng, const exploded_graph *eg) const; -- cgit v1.1 From 33255ad3ac14e3953750fe0f2d82b901c2852ff6 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 15 Jul 2021 15:07:07 -0400 Subject: 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 --- 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 + 23 files changed, 880 insertions(+), 233 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index a8ee1a1..ddace9a 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 02830e4..d42bee7 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 7b77ae8..6ddb6e3 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 51cf522..5b5a9de 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 3173610..2bb3215 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 d005fac..631fef6 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 fc8ac26..ad2eb4d 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 dc07a79..7662a7f 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 1d8b73d..8f48d8a 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 571fc1b..48e2b3e 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 6d60c04..23cfcb0 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 466d397..4be6550 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 55acb90..7a52a64 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 e165cda..1f65307 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 acbbd11..3fe2cce 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 cf5232d..71f6b3e 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 4633717..6cccb0f 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 353d5c4..a17e73c 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 40e64b3..9707a68 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 a65c741..0042a20 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 2ac2923..bc58694 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 70c23f0..22da769 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 5552fcf..54b97f8 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: -- cgit v1.1 From c031ea2782a1873eee5ba82fb114cd87ff831412 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 15 Jul 2021 19:33:07 -0400 Subject: analyzer: fix const-correctness of various is_a_helper gcc/analyzer/ChangeLog: * svalue.h (is_a_helper ::test): Make param and template param const. (is_a_helper ::test): Likewise. (is_a_helper ::test): Likewise. (is_a_helper ::test): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/svalue.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 54b97f8..20d7cf8 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -1063,7 +1063,7 @@ public: template <> template <> inline bool -is_a_helper ::test (svalue *sval) +is_a_helper ::test (const svalue *sval) { return sval->get_kind () == SK_PLACEHOLDER; } @@ -1165,7 +1165,7 @@ public: template <> template <> inline bool -is_a_helper ::test (svalue *sval) +is_a_helper ::test (const svalue *sval) { return sval->get_kind () == SK_WIDENING; } @@ -1266,7 +1266,7 @@ public: template <> template <> inline bool -is_a_helper ::test (svalue *sval) +is_a_helper ::test (const svalue *sval) { return sval->get_kind () == SK_COMPOUND; } @@ -1366,7 +1366,7 @@ public: template <> template <> inline bool -is_a_helper ::test (svalue *sval) +is_a_helper ::test (const svalue *sval) { return sval->get_kind () == SK_CONJURED; } -- cgit v1.1 From d97d71a1989e9ee8e1b8563b351c42b7732da108 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Fri, 16 Jul 2021 00:16:25 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index f0b2d96..e6bd95c 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,151 @@ +2021-07-15 David Malcolm + + * svalue.h (is_a_helper ::test): Make + param and template param const. + (is_a_helper ::test): Likewise. + (is_a_helper ::test): Likewise. + (is_a_helper ::test): Likewise. + +2021-07-15 David Malcolm + + 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. + +2021-07-15 David Malcolm + + * analyzer.opt (fdump-analyzer-exploded-paths): New. + * diagnostic-manager.cc + (diagnostic_manager::emit_saved_diagnostic): Implement it. + * engine.cc (exploded_path::dump_to_pp): Add ext_state param and + use it to dump states if non-NULL. + (exploded_path::dump): Likewise. + (exploded_path::dump_to_file): New. + * exploded-graph.h (exploded_path::dump_to_pp): Add ext_state + param. + (exploded_path::dump): Likewise. + (exploded_path::dump): Likewise. + (exploded_path::dump_to_file): New. + +2021-07-15 David Malcolm + + * analyzer.cc (fixup_tree_for_diagnostic_1): Use DECL_DEBUG_EXPR + if it's available. + * engine.cc (readability): Likewise. + +2021-07-15 David Malcolm + + * state-purge.cc (self_referential_phi_p): New. + (state_purge_per_ssa_name::process_point): Don't purge an SSA name + at its def-stmt if the def-stmt is self-referential. + 2021-07-07 David Malcolm * diagnostic-manager.cc (null_assignment_sm_context::get_state): -- cgit v1.1 From 5932dd35eaa816e8d9b6406c6c433395ff5b6162 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 16 Jul 2021 15:45:33 -0400 Subject: analyzer: add svalue::maybe_get_region gcc/analyzer/ChangeLog: * program-state.cc (program_state::detect_leaks): Simplify using svalue::maybe_get_region. * region-model-impl-calls.cc (region_model::impl_call_fgets): Likewise. (region_model::impl_call_fread): Likewise. (region_model::impl_call_free): Likewise. (region_model::impl_call_operator_delete): Likewise. * region-model.cc (selftest::test_stack_frames): Likewise. (selftest::test_state_merging): Likewise. * svalue.cc (svalue::maybe_get_region): New. * svalue.h (svalue::maybe_get_region): New decl. Signed-off-by: David Malcolm --- gcc/analyzer/program-state.cc | 9 +++------ gcc/analyzer/region-model-impl-calls.cc | 16 ++++------------ gcc/analyzer/region-model.cc | 5 ++--- gcc/analyzer/svalue.cc | 12 ++++++++++++ gcc/analyzer/svalue.h | 1 + 5 files changed, 22 insertions(+), 21 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 23cfcb0..cc53aef 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1285,12 +1285,9 @@ program_state::detect_leaks (const program_state &src_state, /* Purge dead heap-allocated regions from dynamic extents. */ for (const svalue *sval : dead_svals) - if (const region_svalue *region_sval = sval->dyn_cast_region_svalue ()) - { - const region *reg = region_sval->get_pointee (); - if (reg->get_kind () == RK_HEAP_ALLOCATED) - dest_state.m_region_model->unset_dynamic_extents (reg); - } + if (const region *reg = sval->maybe_get_region ()) + if (reg->get_kind () == RK_HEAP_ALLOCATED) + dest_state.m_region_model->unset_dynamic_extents (reg); } #if CHECKING_P diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 4be6550..efb0fc8 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -325,10 +325,8 @@ 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 ()) + if (const region *reg = ptr_sval->maybe_get_region ()) { - 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 ()); @@ -342,10 +340,8 @@ 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 ()) + if (const region *reg = ptr_sval->maybe_get_region ()) { - 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 ()); @@ -372,12 +368,10 @@ void region_model::impl_call_free (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 ()) + if (const region *freed_reg = ptr_sval->maybe_get_region ()) { /* If the ptr points to an underlying heap region, delete it, poisoning pointers. */ - const region *freed_reg = ptr_to_region_sval->get_pointee (); unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); m_dynamic_extents.remove (freed_reg); } @@ -472,12 +466,10 @@ bool region_model::impl_call_operator_delete (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 ()) + if (const region *freed_reg = ptr_sval->maybe_get_region ()) { /* If the ptr points to an underlying heap region, delete it, poisoning pointers. */ - const region *freed_reg = ptr_to_region_sval->get_pointee (); unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); } return false; diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 3fe2cce..190c852 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -4541,7 +4541,7 @@ test_stack_frames () renumbering. */ const svalue *new_q_sval = model.get_rvalue (q, &ctxt); ASSERT_EQ (new_q_sval->get_kind (), SK_REGION); - ASSERT_EQ (new_q_sval->dyn_cast_region_svalue ()->get_pointee (), + ASSERT_EQ (new_q_sval->maybe_get_region (), model.get_lvalue (p, &ctxt)); /* Verify that top of stack has been updated. */ @@ -5070,8 +5070,7 @@ test_state_merging () model0.set_value (q_in_first_frame, sval_ptr, NULL); /* Verify that it's pointing at the newer frame. */ - const region *reg_pointee - = sval_ptr->dyn_cast_region_svalue ()->get_pointee (); + const region *reg_pointee = sval_ptr->maybe_get_region (); ASSERT_EQ (reg_pointee->get_parent_region (), reg_2nd_frame); model0.canonicalize (); diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 22da769..fa9a862 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -111,6 +111,18 @@ svalue::maybe_get_constant () const return NULL_TREE; } +/* If this svalue is a region_svalue, return the region it points to. + Otherwise return NULL. */ + +const region * +svalue::maybe_get_region () const +{ + if (const region_svalue *region_sval = dyn_cast_region_svalue ()) + return region_sval->get_pointee (); + else + return NULL; +} + /* If this svalue is a cast (i.e a unaryop NOP_EXPR or VIEW_CONVERT_EXPR), return the underlying svalue. Otherwise return NULL. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 20d7cf8..1519889 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -126,6 +126,7 @@ public: dyn_cast_conjured_svalue () const { return NULL; } tree maybe_get_constant () const; + const region *maybe_get_region () const; const svalue *maybe_undo_cast () const; const svalue *unwrap_any_unmergeable () const; -- cgit v1.1 From 9ea10c480565fa42b1804fb436f7e26ca77b71a3 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 16 Jul 2021 15:47:06 -0400 Subject: analyzer: add __analyzer_dump_state gcc/analyzer/ChangeLog: * engine.cc (exploded_node::on_stmt_pre): Handle __analyzer_dump_state. * program-state.cc (extrinsic_state::get_sm_idx_by_name): New. (program_state::impl_call_analyzer_dump_state): New. * program-state.h (extrinsic_state::get_sm_idx_by_name): New decl. (program_state::impl_call_analyzer_dump_state): New decl. * region-model-impl-calls.cc (call_details::get_arg_string_literal): New. * region-model.h (call_details::get_arg_string_literal): New decl. gcc/ChangeLog: * doc/analyzer.texi: Add __analyzer_dump_state. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/analyzer-decls.h (__analyzer_dump_state): New. * gcc.dg/analyzer/dump-state.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/engine.cc | 3 ++ gcc/analyzer/program-state.cc | 49 +++++++++++++++++++++++++++++++++ gcc/analyzer/program-state.h | 6 ++++ gcc/analyzer/region-model-impl-calls.cc | 18 ++++++++++++ gcc/analyzer/region-model.h | 1 + 5 files changed, 77 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 7662a7f..f9fc581 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1270,6 +1270,9 @@ exploded_node::on_stmt_pre (exploded_graph &eg, state->dump (eg.get_ext_state (), true); return; } + else if (is_special_named_call_p (call, "__analyzer_dump_state", 2)) + state->impl_call_analyzer_dump_state (call, eg.get_ext_state (), + ctxt); else if (is_setjmp_call_p (call)) { state->m_region_model->on_setjmp (call, this, ctxt); diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index cc53aef..3081217 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -131,6 +131,27 @@ extrinsic_state::get_model_manager () const return NULL; /* for selftests. */ } +/* Try to find a state machine named NAME. + If found, return true and write its index to *OUT. + Otherwise return false. */ + +bool +extrinsic_state::get_sm_idx_by_name (const char *name, unsigned *out) const +{ + unsigned i; + state_machine *sm; + FOR_EACH_VEC_ELT (m_checkers, i, sm) + if (0 == strcmp (name, sm->get_name ())) + { + /* Found NAME. */ + *out = i; + return true; + } + + /* NAME not found. */ + return false; +} + /* struct sm_state_map::entry_t. */ int @@ -1290,6 +1311,34 @@ program_state::detect_leaks (const program_state &src_state, dest_state.m_region_model->unset_dynamic_extents (reg); } +/* Handle calls to "__analyzer_dump_state". */ + +void +program_state::impl_call_analyzer_dump_state (const gcall *call, + const extrinsic_state &ext_state, + region_model_context *ctxt) +{ + call_details cd (call, m_region_model, ctxt); + const char *sm_name = cd.get_arg_string_literal (0); + if (!sm_name) + { + error_at (call->location, "cannot determine state machine"); + return; + } + unsigned sm_idx; + if (!ext_state.get_sm_idx_by_name (sm_name, &sm_idx)) + { + error_at (call->location, "unrecognized state machine %qs", sm_name); + return; + } + const sm_state_map *smap = m_checker_states[sm_idx]; + + const svalue *sval = cd.get_arg_svalue (1); + + state_machine::state_t state = smap->get_state (sval, ext_state); + warning_at (call->location, 0, "state: %qs", state->get_name ()); +} + #if CHECKING_P namespace selftest { diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index f16fe6b..8dee930 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -58,6 +58,8 @@ public: engine *get_engine () const { return m_engine; } region_model_manager *get_model_manager () const; + bool get_sm_idx_by_name (const char *name, unsigned *out) const; + private: /* The state machines. */ auto_delete_vec &m_checkers; @@ -256,6 +258,10 @@ public: const extrinsic_state &ext_state, region_model_context *ctxt); + void impl_call_analyzer_dump_state (const gcall *call, + const extrinsic_state &ext_state, + region_model_context *ctxt); + /* TODO: lose the pointer here (const-correctness issues?). */ region_model *m_region_model; auto_delete_vec m_checker_states; diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index efb0fc8..545634b 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -140,6 +140,24 @@ call_details::get_arg_svalue (unsigned idx) const return m_model->get_rvalue (arg, m_ctxt); } +/* Attempt to get the string literal for argument IDX, or return NULL + otherwise. + For use when implementing "__analyzer_*" functions that take + string literals. */ + +const char * +call_details::get_arg_string_literal (unsigned idx) const +{ + const svalue *str_arg = get_arg_svalue (idx); + if (const region *pointee = str_arg->maybe_get_region ()) + if (const string_region *string_reg = pointee->dyn_cast_string_region ()) + { + tree string_cst = string_reg->get_string_cst (); + return TREE_STRING_POINTER (string_cst); + } + return NULL; +} + /* Dump a multiline representation of this call to PP. */ void diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 71f6b3e..f07a287 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -468,6 +468,7 @@ public: tree get_arg_tree (unsigned idx) const; tree get_arg_type (unsigned idx) const; const svalue *get_arg_svalue (unsigned idx) const; + const char *get_arg_string_literal (unsigned idx) const; void dump_to_pp (pretty_printer *pp, bool simple) const; void dump (bool simple) const; -- cgit v1.1 From 9faf8348621ae6ab583af593d67ac424300a2bad Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 16 Jul 2021 15:49:17 -0400 Subject: analyzer: add region_model::check_region_access I've been experimenting with various new diagnostics that require a common place for the analyzer to check the validity of reads or writes to memory (e.g. buffer overflow). As preliminary work, this patch adds new region_model::check_region_for_{read|write} functions which are called anywhere that the analyzer "sees" memory being read from or written to (via region_model::get_store_value and region_model::set_value). This takes over the hardcoded calls to check_for_writable_region (allowing for other kinds of checks on writes); checking reads is currently a no-op. gcc/analyzer/ChangeLog: * analyzer.h (enum access_direction): New. * engine.cc (exploded_node::on_longjmp): Update for new param of get_store_value. * program-state.cc (program_state::prune_for_point): Likewise. * region-model-impl-calls.cc (region_model::impl_call_memcpy): Replace call to check_for_writable_region with call to check_region_for_write. (region_model::impl_call_memset): Likewise. (region_model::impl_call_strcpy): Likewise. * region-model-reachability.cc (reachable_regions::add): Update for new param of get_store_value. * region-model.cc (region_model::get_rvalue_1): Likewise, also for get_rvalue_for_bits. (region_model::get_store_value): Add ctxt param and use it to call check_region_for_read. (region_model::get_rvalue_for_bits): Add ctxt param and use it to call get_store_value. (region_model::check_region_access): New. (region_model::check_region_for_write): New. (region_model::check_region_for_read): New. (region_model::set_value): Update comment. Replace call to check_for_writable_region with call to check_region_for_write. * region-model.h (region_model::get_rvalue_for_bits): Add ctxt param. (region_model::get_store_value): Add ctxt param. (region_model::check_region_access): New decl. (region_model::check_region_for_write): New decl. (region_model::check_region_for_read): New decl. * region.cc (region_model::copy_region): Update call to get_store_value. * svalue.cc (initial_svalue::implicitly_live_p): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.h | 8 ++++ gcc/analyzer/engine.cc | 3 +- gcc/analyzer/program-state.cc | 2 +- gcc/analyzer/region-model-impl-calls.cc | 6 +-- gcc/analyzer/region-model-reachability.cc | 2 +- gcc/analyzer/region-model.cc | 70 ++++++++++++++++++++++++++----- gcc/analyzer/region-model.h | 13 +++++- gcc/analyzer/region.cc | 2 +- gcc/analyzer/svalue.cc | 2 +- 9 files changed, 88 insertions(+), 20 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index d42bee7..90143d9 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -208,6 +208,14 @@ public: virtual logger *get_logger () const = 0; }; +/* An enum for describing the direction of an access to memory. */ + +enum access_direction +{ + DIR_READ, + DIR_WRITE +}; + } // namespace ana extern bool is_special_named_call_p (const gcall *call, const char *funcname, diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index f9fc581..ee625fb 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1468,7 +1468,8 @@ exploded_node::on_longjmp (exploded_graph &eg, const region *buf = new_region_model->deref_rvalue (buf_ptr_sval, buf_ptr, ctxt); - const svalue *buf_content_sval = new_region_model->get_store_value (buf); + const svalue *buf_content_sval + = new_region_model->get_store_value (buf, ctxt); const setjmp_svalue *setjmp_sval = buf_content_sval->dyn_cast_setjmp_svalue (); if (!setjmp_sval) diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 3081217..ccfe7b0 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1082,7 +1082,7 @@ program_state::prune_for_point (exploded_graph &eg, temporaries keep the value reachable until the frame is popped. */ const svalue *sval - = new_state.m_region_model->get_store_value (reg); + = new_state.m_region_model->get_store_value (reg, NULL); if (!new_state.can_purge_p (eg.get_ext_state (), sval) && SSA_NAME_VAR (ssa_name)) { diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 545634b..eff8caa 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -431,7 +431,7 @@ region_model::impl_call_memcpy (const call_details &cd) return; } - check_for_writable_region (dest_reg, cd.get_ctxt ()); + check_region_for_write (dest_reg, cd.get_ctxt ()); /* Otherwise, mark region's contents as unknown. */ mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); @@ -455,7 +455,7 @@ region_model::impl_call_memset (const call_details &cd) const region *sized_dest_reg = m_mgr->get_sized_region (dest_reg, NULL_TREE, num_bytes_sval); - check_for_writable_region (sized_dest_reg, cd.get_ctxt ()); + check_region_for_write (sized_dest_reg, cd.get_ctxt ()); fill_region (sized_dest_reg, fill_value_u8); return true; } @@ -515,7 +515,7 @@ region_model::impl_call_strcpy (const call_details &cd) cd.maybe_set_lhs (dest_sval); - check_for_writable_region (dest_reg, cd.get_ctxt ()); + check_region_for_write (dest_reg, cd.get_ctxt ()); /* For now, just mark region's contents as unknown. */ mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc index 1f65307..b5ae787 100644 --- a/gcc/analyzer/region-model-reachability.cc +++ b/gcc/analyzer/region-model-reachability.cc @@ -154,7 +154,7 @@ reachable_regions::add (const region *reg, bool is_mutable) if (binding_cluster *bind_cluster = m_store->get_cluster (base_reg)) bind_cluster->for_each_value (handle_sval_cb, this); else - handle_sval (m_model->get_store_value (reg)); + handle_sval (m_model->get_store_value (reg, NULL)); } void diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 190c852..4fab1ef 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1743,7 +1743,7 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) const gcc_assert (TREE_CODE (first_bit_offset) == INTEGER_CST); bit_range bits (TREE_INT_CST_LOW (first_bit_offset), TREE_INT_CST_LOW (num_bits)); - return get_rvalue_for_bits (TREE_TYPE (expr), reg, bits); + return get_rvalue_for_bits (TREE_TYPE (expr), reg, bits, ctxt); } case SSA_NAME: @@ -1753,7 +1753,7 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) const case ARRAY_REF: { const region *reg = get_lvalue (pv, ctxt); - return get_store_value (reg); + return get_store_value (reg, ctxt); } case REALPART_EXPR: @@ -1808,7 +1808,7 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) const case MEM_REF: { const region *ref_reg = get_lvalue (pv, ctxt); - return get_store_value (ref_reg); + return get_store_value (ref_reg, ctxt); } } } @@ -1913,11 +1913,15 @@ region_model::get_initial_value_for_global (const region *reg) const } /* Get a value for REG, looking it up in the store, or otherwise falling - back to "initial" or "unknown" values. */ + back to "initial" or "unknown" values. + Use CTXT to report any warnings associated with reading from REG. */ const svalue * -region_model::get_store_value (const region *reg) const +region_model::get_store_value (const region *reg, + region_model_context *ctxt) const { + check_region_for_read (reg, ctxt); + /* Special-case: handle var_decls in the constant pool. */ if (const decl_region *decl_reg = reg->dyn_cast_decl_region ()) if (const svalue *sval = decl_reg->maybe_get_constant_value (m_mgr)) @@ -2077,14 +2081,16 @@ region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, /* Attempt to get BITS within any value of REG, as TYPE. In particular, extract values from compound_svalues for the case where there's a concrete binding at BITS. - Return an unknown svalue if we can't handle the given case. */ + Return an unknown svalue if we can't handle the given case. + Use CTXT to report any warnings associated with reading from REG. */ const svalue * region_model::get_rvalue_for_bits (tree type, const region *reg, - const bit_range &bits) const + const bit_range &bits, + region_model_context *ctxt) const { - const svalue *sval = get_store_value (reg); + const svalue *sval = get_store_value (reg, ctxt); return m_mgr->get_or_create_bits_within (type, bits, sval); } @@ -2240,8 +2246,52 @@ region_model::get_capacity (const region *reg) const return m_mgr->get_or_create_unknown_svalue (sizetype); } +/* If CTXT is non-NULL, use it to warn about any problems accessing REG, + using DIR to determine if this access is a read or write. */ + +void +region_model::check_region_access (const region *reg, + enum access_direction dir, + region_model_context *ctxt) const +{ + /* Fail gracefully if CTXT is NULL. */ + if (!ctxt) + return; + + switch (dir) + { + default: + gcc_unreachable (); + case DIR_READ: + /* Currently a no-op. */ + break; + case DIR_WRITE: + check_for_writable_region (reg, ctxt); + break; + } +} + +/* If CTXT is non-NULL, use it to warn about any problems writing to REG. */ + +void +region_model::check_region_for_write (const region *dest_reg, + region_model_context *ctxt) const +{ + check_region_access (dest_reg, DIR_WRITE, ctxt); +} + +/* If CTXT is non-NULL, use it to warn about any problems reading from REG. */ + +void +region_model::check_region_for_read (const region *src_reg, + region_model_context *ctxt) const +{ + check_region_access (src_reg, DIR_READ, ctxt); +} + /* Set the value of the region given by LHS_REG to the value given - by RHS_SVAL. */ + by RHS_SVAL. + Use CTXT to report any warnings associated with writing to LHS_REG. */ void region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, @@ -2250,7 +2300,7 @@ region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, gcc_assert (lhs_reg); gcc_assert (rhs_sval); - check_for_writable_region (lhs_reg, ctxt); + check_region_for_write (lhs_reg, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, ctxt ? ctxt->get_uncertainty () : NULL); diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index f07a287..734ec60 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -609,7 +609,8 @@ class region_model const svalue *get_rvalue_for_bits (tree type, const region *reg, - const bit_range &bits) const; + const bit_range &bits, + region_model_context *ctxt) const; void set_value (const region *lhs_reg, const svalue *rhs_sval, region_model_context *ctxt); @@ -687,7 +688,8 @@ class region_model static void append_ssa_names_cb (const region *base_reg, struct append_ssa_names_cb_data *data); - const svalue *get_store_value (const region *reg) const; + const svalue *get_store_value (const region *reg, + region_model_context *ctxt) const; bool region_exists_p (const region *reg) const; @@ -748,6 +750,13 @@ class region_model void check_for_writable_region (const region* dest_reg, region_model_context *ctxt) const; + void check_region_access (const region *reg, + enum access_direction dir, + region_model_context *ctxt) const; + void check_region_for_write (const region *dest_reg, + region_model_context *ctxt) const; + void check_region_for_read (const region *src_reg, + region_model_context *ctxt) const; /* Storing this here to avoid passing it around everywhere. */ region_model_manager *const m_mgr; diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 6cccb0f..fa187fd 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -573,7 +573,7 @@ region_model::copy_region (const region *dst_reg, const region *src_reg, if (dst_reg == src_reg) return; - const svalue *sval = get_store_value (src_reg); + const svalue *sval = get_store_value (src_reg, ctxt); set_value (dst_reg, sval, ctxt); } diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index fa9a862..323df80 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -936,7 +936,7 @@ initial_svalue::implicitly_live_p (const svalue_set *, a popped stack frame. */ if (model->region_exists_p (m_reg)) { - const svalue *reg_sval = model->get_store_value (m_reg); + const svalue *reg_sval = model->get_store_value (m_reg, NULL); if (reg_sval == this) return true; } -- cgit v1.1 From 87277b6a04486b606761b86dbcfbc9a4b6871f4c Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sat, 17 Jul 2021 00:16:31 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index e6bd95c..7b63636 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,62 @@ +2021-07-16 David Malcolm + + * analyzer.h (enum access_direction): New. + * engine.cc (exploded_node::on_longjmp): Update for new param of + get_store_value. + * program-state.cc (program_state::prune_for_point): Likewise. + * region-model-impl-calls.cc (region_model::impl_call_memcpy): + Replace call to check_for_writable_region with call to + check_region_for_write. + (region_model::impl_call_memset): Likewise. + (region_model::impl_call_strcpy): Likewise. + * region-model-reachability.cc (reachable_regions::add): Update + for new param of get_store_value. + * region-model.cc (region_model::get_rvalue_1): Likewise, also for + get_rvalue_for_bits. + (region_model::get_store_value): Add ctxt param and use it to call + check_region_for_read. + (region_model::get_rvalue_for_bits): Add ctxt param and use it to + call get_store_value. + (region_model::check_region_access): New. + (region_model::check_region_for_write): New. + (region_model::check_region_for_read): New. + (region_model::set_value): Update comment. Replace call to + check_for_writable_region with call to check_region_for_write. + * region-model.h (region_model::get_rvalue_for_bits): Add ctxt + param. + (region_model::get_store_value): Add ctxt param. + (region_model::check_region_access): New decl. + (region_model::check_region_for_write): New decl. + (region_model::check_region_for_read): New decl. + * region.cc (region_model::copy_region): Update call to + get_store_value. + * svalue.cc (initial_svalue::implicitly_live_p): Likewise. + +2021-07-16 David Malcolm + + * engine.cc (exploded_node::on_stmt_pre): Handle + __analyzer_dump_state. + * program-state.cc (extrinsic_state::get_sm_idx_by_name): New. + (program_state::impl_call_analyzer_dump_state): New. + * program-state.h (extrinsic_state::get_sm_idx_by_name): New decl. + (program_state::impl_call_analyzer_dump_state): New decl. + * region-model-impl-calls.cc + (call_details::get_arg_string_literal): New. + * region-model.h (call_details::get_arg_string_literal): New decl. + +2021-07-16 David Malcolm + + * program-state.cc (program_state::detect_leaks): Simplify using + svalue::maybe_get_region. + * region-model-impl-calls.cc (region_model::impl_call_fgets): Likewise. + (region_model::impl_call_fread): Likewise. + (region_model::impl_call_free): Likewise. + (region_model::impl_call_operator_delete): Likewise. + * region-model.cc (selftest::test_stack_frames): Likewise. + (selftest::test_state_merging): Likewise. + * svalue.cc (svalue::maybe_get_region): New. + * svalue.h (svalue::maybe_get_region): New decl. + 2021-07-15 David Malcolm * svalue.h (is_a_helper ::test): Make -- cgit v1.1 From a113b14398f2a4ad2742e6e9c87e25cac60f263e Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 19 Jul 2021 15:44:02 -0400 Subject: analyzer: add svalue::can_have_associated_state_p [PR101503] PR analyzer/101503 reports an assertion failure due to an unexpected "UNKNOWN" value (due to using --param analyzer-max-svalue-depth=0). This patch fixes this by rejecting attempts to purge state involving unknown/poisoned svalues (in region_model::purge_state_involving), as these svalues should not have state associated with them - they are singletons w.r.t each type. To be more systematic about this, the patch also introduces a new svalue::can_have_associated_state_p which returns false for unknown/poisoned svalues, so that we can reject adding constraints or sm-state on them, or building various kinds of svalue in terms of them (e.g. unary ops, binary ops, etc). gcc/analyzer/ChangeLog: PR analyzer/101503 * constraint-manager.cc (constraint_manager::add_constraint): Use can_have_associated_state_p rather than testing for unknown. (constraint_manager::get_or_add_equiv_class): Likewise. * program-state.cc (sm_state_map::set_state): Likewise. (sm_state_map::impl_set_state): Add assertion. * region-model-manager.cc (region_model_manager::maybe_fold_unaryop): Handle poisoned values. (region_model_manager::maybe_fold_binop): Move handling of unknown values... (region_model_manager::get_or_create_binop): ...to here, and generalize to use can_have_associated_state_p. (region_model_manager::maybe_fold_sub_svalue): Use can_have_associated_state_p rather than testing for unknown. (region_model_manager::maybe_fold_repeated_svalue): Use unknown when the size or repeated value is "unknown"/"poisoned". * region-model.cc (region_model::purge_state_involving): Reject attempts to purge unknown/poisoned svalues, as these svalues should not have state associated with them. * svalue.cc (sub_svalue::sub_svalue): Assert that we're building on top of an svalue with can_have_associated_state_p. (repeated_svalue::repeated_svalue): Likewise. (bits_within_svalue::bits_within_svalue): Likewise. * svalue.h (svalue::can_have_associated_state_p): New. (unknown_svalue::can_have_associated_state_p): New. (poisoned_svalue::can_have_associated_state_p): New. (unaryop_svalue::unaryop_svalue): Assert that we're building on top of an svalue with can_have_associated_state_p. (binop_svalue::binop_svalue): Likewise. (widening_svalue::widening_svalue): Likewise. gcc/testsuite/ChangeLog: PR analyzer/101503 * gcc.dg/analyzer/pr101503.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/constraint-manager.cc | 8 ++++---- gcc/analyzer/program-state.cc | 6 ++++-- gcc/analyzer/region-model-manager.cc | 28 ++++++++++++++++++++-------- gcc/analyzer/region-model.cc | 2 ++ gcc/analyzer/svalue.cc | 4 ++++ gcc/analyzer/svalue.h | 16 ++++++++++++++++ 6 files changed, 50 insertions(+), 14 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index 5b5a9de..f59929a 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -833,9 +833,9 @@ constraint_manager::add_constraint (const svalue *lhs, lhs = lhs->unwrap_any_unmergeable (); rhs = rhs->unwrap_any_unmergeable (); - /* Nothing can be known about unknown values. */ - if (lhs->get_kind () == SK_UNKNOWN - || rhs->get_kind () == SK_UNKNOWN) + /* Nothing can be known about unknown/poisoned values. */ + if (!lhs->can_have_associated_state_p () + || !rhs->can_have_associated_state_p ()) /* Not a contradiction. */ return true; @@ -1175,7 +1175,7 @@ constraint_manager::get_or_add_equiv_class (const svalue *sval) { equiv_class_id result (-1); - gcc_assert (sval->get_kind () != SK_UNKNOWN); + gcc_assert (sval->can_have_associated_state_p ()); /* Convert all NULL pointers to (void *) to avoid state explosions involving all of the various (foo *)NULL vs (bar *)NULL. */ diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index ccfe7b0..5bb8676 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -453,8 +453,8 @@ sm_state_map::set_state (region_model *model, if (model == NULL) return; - /* Reject attempts to set state on UNKNOWN. */ - if (sval->get_kind () == SK_UNKNOWN) + /* Reject attempts to set state on UNKNOWN/POISONED. */ + if (!sval->can_have_associated_state_p ()) return; equiv_class &ec = model->get_constraints ()->get_equiv_class (sval); @@ -492,6 +492,8 @@ sm_state_map::impl_set_state (const svalue *sval, if (get_state (sval, ext_state) == state) return false; + gcc_assert (sval->can_have_associated_state_p ()); + /* Special-case state 0 as the default value. */ if (state == 0) { diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 7a52a64..fccb93e 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -340,6 +340,13 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op, /* Ops on "unknown" are also unknown. */ if (arg->get_kind () == SK_UNKNOWN) return get_or_create_unknown_svalue (type); + /* Likewise for "poisoned". */ + else if (const poisoned_svalue *poisoned_sval + = arg->dyn_cast_poisoned_svalue ()) + return get_or_create_poisoned_svalue (poisoned_sval->get_poison_kind (), + type); + + gcc_assert (arg->can_have_associated_state_p ()); switch (op) { @@ -615,12 +622,6 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, get_or_create_binop (size_type_node, op, binop->get_arg1 (), arg1)); - /* Ops on "unknown" are also unknown (unless we can use one of the - identities above). */ - if (arg0->get_kind () == SK_UNKNOWN - || arg1->get_kind () == SK_UNKNOWN) - return get_or_create_unknown_svalue (type); - /* etc. */ return NULL; @@ -641,6 +642,12 @@ region_model_manager::get_or_create_binop (tree type, enum tree_code op, if (const svalue *folded = maybe_fold_binop (type, op, arg0, arg1)) return folded; + /* Ops on "unknown"/"poisoned" are unknown (unless we were able to fold + it via an identity in maybe_fold_binop). */ + if (!arg0->can_have_associated_state_p () + || !arg1->can_have_associated_state_p ()) + return get_or_create_unknown_svalue (type); + binop_svalue::key_t key (type, op, arg0, arg1); if (binop_svalue **slot = m_binop_values_map.get (key)) return *slot; @@ -658,8 +665,8 @@ region_model_manager::maybe_fold_sub_svalue (tree type, const svalue *parent_svalue, const region *subregion) { - /* Subvalues of "unknown" are unknown. */ - if (parent_svalue->get_kind () == SK_UNKNOWN) + /* Subvalues of "unknown"/"poisoned" are unknown. */ + if (!parent_svalue->can_have_associated_state_p ()) return get_or_create_unknown_svalue (type); /* If we have a subregion of a zero-fill, it's zero. */ @@ -755,6 +762,11 @@ region_model_manager::maybe_fold_repeated_svalue (tree type, const svalue *outer_size, const svalue *inner_svalue) { + /* Repeated "unknown"/"poisoned" is unknown. */ + if (!outer_size->can_have_associated_state_p () + || !inner_svalue->can_have_associated_state_p ()) + return get_or_create_unknown_svalue (type); + /* If INNER_SVALUE is the same size as OUTER_SIZE, turn into simply a cast. */ if (tree cst_outer_num_bytes = outer_size->maybe_get_constant ()) diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 4fab1ef..6d02c60 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1304,6 +1304,8 @@ void region_model::purge_state_involving (const svalue *sval, region_model_context *ctxt) { + if (!sval->can_have_associated_state_p ()) + return; m_store.purge_state_involving (sval, m_mgr); m_constraints->purge_state_involving (sval); m_dynamic_extents.purge_state_involving (sval); diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 323df80..094c725 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -1109,6 +1109,7 @@ sub_svalue::sub_svalue (tree type, const svalue *parent_svalue, type), m_parent_svalue (parent_svalue), m_subregion (subregion) { + gcc_assert (parent_svalue->can_have_associated_state_p ()); } /* Implementation of svalue::dump_to_pp vfunc for sub_svalue. */ @@ -1165,6 +1166,8 @@ repeated_svalue::repeated_svalue (tree type, m_outer_size (outer_size), m_inner_svalue (inner_svalue) { + gcc_assert (outer_size->can_have_associated_state_p ()); + gcc_assert (inner_svalue->can_have_associated_state_p ()); } /* Implementation of svalue::dump_to_pp vfunc for repeated_svalue. */ @@ -1290,6 +1293,7 @@ bits_within_svalue::bits_within_svalue (tree type, m_bits (bits), m_inner_svalue (inner_svalue) { + gcc_assert (inner_svalue->can_have_associated_state_p ()); } /* Implementation of svalue::dump_to_pp vfunc for bits_within_svalue. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 1519889..debe439 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -160,6 +160,11 @@ public: virtual bool all_zeroes_p () const; + /* Can this svalue be involved in constraints and sm-state? + Most can, but UNKNOWN and POISONED svalues are singletons + per-type and thus it's meaningless for them to "have state". */ + virtual bool can_have_associated_state_p () const { return true; } + protected: svalue (complexity c, tree type) : m_complexity (c), m_type (type) @@ -319,6 +324,9 @@ public: maybe_fold_bits_within (tree type, const bit_range &subrange, region_model_manager *mgr) const FINAL OVERRIDE; + + /* Unknown values are singletons per-type, so can't have state. */ + bool can_have_associated_state_p () const FINAL OVERRIDE { return false; } }; /* An enum describing a particular kind of "poisoned" value. */ @@ -389,6 +397,9 @@ public: enum poison_kind get_poison_kind () const { return m_kind; } + /* Poisoned svalues are singletons per-type, so can't have state. */ + bool can_have_associated_state_p () const FINAL OVERRIDE { return false; } + private: enum poison_kind m_kind; }; @@ -602,6 +613,7 @@ public: unaryop_svalue (tree type, enum tree_code op, const svalue *arg) : svalue (complexity (arg), type), m_op (op), m_arg (arg) { + gcc_assert (arg->can_have_associated_state_p ()); } enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_UNARYOP; } @@ -694,6 +706,8 @@ public: type), m_op (op), m_arg0 (arg0), m_arg1 (arg1) { + gcc_assert (arg0->can_have_associated_state_p ()); + gcc_assert (arg1->can_have_associated_state_p ()); } enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_BINOP; } @@ -1135,6 +1149,8 @@ public: m_point (point.get_function_point ()), m_base_sval (base_sval), m_iter_sval (iter_sval) { + gcc_assert (base_sval->can_have_associated_state_p ()); + gcc_assert (iter_sval->can_have_associated_state_p ()); } enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_WIDENING; } -- cgit v1.1 From 21ea2f9320d31d3d925031a8ba189d9b19e52bc1 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 20 Jul 2021 00:16:38 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 7b63636..f32fe08 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,37 @@ +2021-07-19 David Malcolm + + PR analyzer/101503 + * constraint-manager.cc (constraint_manager::add_constraint): Use + can_have_associated_state_p rather than testing for unknown. + (constraint_manager::get_or_add_equiv_class): Likewise. + * program-state.cc (sm_state_map::set_state): Likewise. + (sm_state_map::impl_set_state): Add assertion. + * region-model-manager.cc + (region_model_manager::maybe_fold_unaryop): Handle poisoned + values. + (region_model_manager::maybe_fold_binop): Move handling of unknown + values... + (region_model_manager::get_or_create_binop): ...to here, and + generalize to use can_have_associated_state_p. + (region_model_manager::maybe_fold_sub_svalue): Use + can_have_associated_state_p rather than testing for unknown. + (region_model_manager::maybe_fold_repeated_svalue): Use unknown + when the size or repeated value is "unknown"/"poisoned". + * region-model.cc (region_model::purge_state_involving): Reject + attempts to purge unknown/poisoned svalues, as these svalues + should not have state associated with them. + * svalue.cc (sub_svalue::sub_svalue): Assert that we're building + on top of an svalue with can_have_associated_state_p. + (repeated_svalue::repeated_svalue): Likewise. + (bits_within_svalue::bits_within_svalue): Likewise. + * svalue.h (svalue::can_have_associated_state_p): New. + (unknown_svalue::can_have_associated_state_p): New. + (poisoned_svalue::can_have_associated_state_p): New. + (unaryop_svalue::unaryop_svalue): Assert that we're building on + top of an svalue with can_have_associated_state_p. + (binop_svalue::binop_svalue): Likewise. + (widening_svalue::widening_svalue): Likewise. + 2021-07-16 David Malcolm * analyzer.h (enum access_direction): New. -- cgit v1.1 From dcdf6bb24e5f113f2bb9298588105a071bddf50f Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 17:19:31 -0400 Subject: analyzer: tweak dumping of min_expr/max_expr gcc/analyzer/ChangeLog: * svalue.cc (infix_p): New. (binop_svalue::dump_to_pp): Use it to print MIN_EXPR and MAX_EXPR in prefix form, rather than infix. Signed-off-by: David Malcolm --- gcc/analyzer/svalue.cc | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 094c725..a1e6f50 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -1053,6 +1053,21 @@ unaryop_svalue::maybe_fold_bits_within (tree type, /* class binop_svalue : public svalue. */ +/* Return whether OP be printed as an infix operator. */ + +static bool +infix_p (enum tree_code op) +{ + switch (op) + { + default: + return true; + case MAX_EXPR: + case MIN_EXPR: + return false; + } +} + /* Implementation of svalue::dump_to_pp vfunc for binop_svalue. */ void @@ -1060,11 +1075,25 @@ binop_svalue::dump_to_pp (pretty_printer *pp, bool simple) const { if (simple) { - pp_character (pp, '('); - m_arg0->dump_to_pp (pp, simple); - pp_string (pp, op_symbol_code (m_op)); - m_arg1->dump_to_pp (pp, simple); - pp_character (pp, ')'); + if (infix_p (m_op)) + { + /* Print "(A OP B)". */ + pp_character (pp, '('); + m_arg0->dump_to_pp (pp, simple); + pp_string (pp, op_symbol_code (m_op)); + m_arg1->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } + else + { + /* Print "OP(A, B)". */ + pp_string (pp, op_symbol_code (m_op)); + pp_character (pp, '('); + m_arg0->dump_to_pp (pp, simple); + pp_string (pp, ", "); + m_arg1->dump_to_pp (pp, simple); + pp_character (pp, ')'); + } } else { -- cgit v1.1 From 81703584769707c34533e78c7a2bc229b0e14b2d Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 17:21:22 -0400 Subject: analyzer: show BB index in BEFORE_SUPERNODE's in-edge This is useful for debugging how the analyzer handles phi nodes. gcc/analyzer/ChangeLog: * program-point.cc (function_point::print): Show src BB index at BEFORE_SUPERNODE. Signed-off-by: David Malcolm --- gcc/analyzer/program-point.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index d8cfc61..d73b621 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -119,8 +119,15 @@ function_point::print (pretty_printer *pp, const format &f) const case PK_BEFORE_SUPERNODE: { if (m_from_edge) - pp_printf (pp, "before SN: %i (from SN: %i)", - m_supernode->m_index, m_from_edge->m_src->m_index); + { + if (basic_block bb = m_from_edge->m_src->m_bb) + pp_printf (pp, "before SN: %i (from SN: %i (bb: %i))", + m_supernode->m_index, m_from_edge->m_src->m_index, + bb->index); + else + pp_printf (pp, "before SN: %i (from SN: %i)", + m_supernode->m_index, m_from_edge->m_src->m_index); + } else pp_printf (pp, "before SN: %i (NULL from-edge)", m_supernode->m_index); -- cgit v1.1 From 6bbad96cd44774bc199b256dbf4260b25b87c7db Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 17:22:45 -0400 Subject: analyzer: fixes to -fdump-analyzer-state-purge for phi nodes gcc/analyzer/ChangeLog: * state-purge.cc (state_purge_annotator::add_node_annotations): Rather than erroneously always using the NULL in-edge, determine each relevant in-edge, and print the appropriate data for each in-edge. Use print_needed to print the data as comma-separated lists of SSA names. (print_vec_of_names): Add "within_table" param and use it. (state_purge_annotator::add_stmt_annotations): Factor out collation and printing code into... (state_purge_annotator::print_needed): ...this new function. * state-purge.h (state_purge_annotator::print_needed): New decl. Signed-off-by: David Malcolm --- gcc/analyzer/state-purge.cc | 66 ++++++++++++++++++++++++++------------------- gcc/analyzer/state-purge.h | 4 +++ 2 files changed, 43 insertions(+), 27 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index e82ea87..3c3b775 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -477,23 +477,20 @@ state_purge_annotator::add_node_annotations (graphviz_out *gv, "lightblue"); pp_write_text_to_stream (pp); - // FIXME: passing in a NULL in-edge means we get no hits - function_point before_supernode - (function_point::before_supernode (&n, NULL)); - - for (state_purge_map::iterator iter = m_map->begin (); - iter != m_map->end (); - ++iter) + /* Different in-edges mean different names need purging. + Determine which points to dump. */ + auto_vec points; + if (n.entry_p ()) + points.safe_push (function_point::before_supernode (&n, NULL)); + else + for (auto inedge : n.m_preds) + points.safe_push (function_point::before_supernode (&n, inedge)); + + for (auto & point : points) { - tree name = (*iter).first; - state_purge_per_ssa_name *per_name_data = (*iter).second; - if (per_name_data->get_function () == n.m_fun) - { - if (per_name_data->needed_at_point_p (before_supernode)) - pp_printf (pp, "%qE needed here", name); - else - pp_printf (pp, "%qE not needed here", name); - } + point.print (pp, format (true)); + pp_newline (pp); + print_needed (gv, point, false); pp_newline (pp); } @@ -502,19 +499,20 @@ state_purge_annotator::add_node_annotations (graphviz_out *gv, return false; } -/* Print V to GV as a comma-separated list in braces within a , - titling it with TITLE. +/* Print V to GV as a comma-separated list in braces, titling it with TITLE. + If WITHIN_TABLE is true, print it within a - Subroutine of state_purge_annotator::add_stmt_annotations. */ + Subroutine of state_purge_annotator::print_needed. */ static void print_vec_of_names (graphviz_out *gv, const char *title, - const auto_vec &v) + const auto_vec &v, bool within_table) { pretty_printer *pp = gv->get_pp (); tree name; unsigned i; - gv->begin_trtd (); + if (within_table) + gv->begin_trtd (); pp_printf (pp, "%s: {", title); FOR_EACH_VEC_ELT (v, i, name) { @@ -523,8 +521,11 @@ print_vec_of_names (graphviz_out *gv, const char *title, pp_printf (pp, "%qE", name); } pp_printf (pp, "}"); - pp_write_text_as_html_like_dot_to_stream (pp); - gv->end_tdtr (); + if (within_table) + { + pp_write_text_as_html_like_dot_to_stream (pp); + gv->end_tdtr (); + } pp_newline (pp); } @@ -556,6 +557,17 @@ state_purge_annotator::add_stmt_annotations (graphviz_out *gv, function_point before_stmt (function_point::before_stmt (supernode, stmt_idx)); + print_needed (gv, before_stmt, true); +} + +/* Get the ssa names needed and not-needed at POINT, and print them to GV. + If WITHIN_TABLE is true, print them within elements. */ + +void +state_purge_annotator::print_needed (graphviz_out *gv, + const function_point &point, + bool within_table) const +{ auto_vec needed; auto_vec not_needed; for (state_purge_map::iterator iter = m_map->begin (); @@ -564,17 +576,17 @@ state_purge_annotator::add_stmt_annotations (graphviz_out *gv, { tree name = (*iter).first; state_purge_per_ssa_name *per_name_data = (*iter).second; - if (per_name_data->get_function () == supernode->m_fun) + if (per_name_data->get_function () == point.get_function ()) { - if (per_name_data->needed_at_point_p (before_stmt)) + if (per_name_data->needed_at_point_p (point)) needed.safe_push (name); else not_needed.safe_push (name); } } - print_vec_of_names (gv, "needed here", needed); - print_vec_of_names (gv, "not needed here", not_needed); + print_vec_of_names (gv, "needed here", needed, within_table); + print_vec_of_names (gv, "not needed here", not_needed, within_table); } #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/state-purge.h b/gcc/analyzer/state-purge.h index 879013d..409490e 100644 --- a/gcc/analyzer/state-purge.h +++ b/gcc/analyzer/state-purge.h @@ -159,6 +159,10 @@ public: const FINAL OVERRIDE; private: + void print_needed (graphviz_out *gv, + const function_point &point, + bool within_table) const; + const state_purge_map *m_map; }; -- cgit v1.1 From e0a7a6752dad7848eb4b29b826a551c0992256ec Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 17:24:08 -0400 Subject: analyzer: fix issues with phi handling The analyzer's state purging code was overzealously purging state for ssa names that might be used within phi nodes, leading to false positives from -Wanalyzer-use-of-uninitialized-value. This patch updates phi handling in the analyzer to fix these issues. gcc/analyzer/ChangeLog: * region-model.cc (region_model::handle_phi): Add "old_state" param and use it. (region_model::update_for_phis): Update so that all of the phi stmts are effectively handled simultaneously, rather than in order. * region-model.h (region_model::handle_phi): Add "old_state" param. * state-purge.cc (self_referential_phi_p): Replace with... (name_used_by_phis_p): ...this new function. (state_purge_per_ssa_name::process_point): Update to use the above, so that all phi stmts at a basic block are effectively considered simultaneously, and only consider the phi arguments for the pertinent in-edge. * supergraph.cc (cfg_superedge::get_phi_arg_idx): New. (cfg_superedge::get_phi_arg): Use the above. * supergraph.h (cfg_superedge::get_phi_arg_idx): New decl. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/explode-2.c: Remove xfail. * gcc.dg/analyzer/explode-2a.c: Remove expected leak warning on while stmt. * gcc.dg/analyzer/phi-2.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 18 +++++++++++++----- gcc/analyzer/region-model.h | 1 + gcc/analyzer/state-purge.cc | 42 ++++++++++++++++++++++++------------------ gcc/analyzer/supergraph.cc | 11 ++++++++++- gcc/analyzer/supergraph.h | 1 + 5 files changed, 49 insertions(+), 24 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 6d02c60..c029759 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1553,11 +1553,14 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call, /* Update this region_model for a phi stmt of the form LHS = PHI <...RHS...>. - where RHS is for the appropriate edge. */ + where RHS is for the appropriate edge. + Get state from OLD_STATE so that all of the phi stmts for a basic block + are effectively handled simultaneously. */ void region_model::handle_phi (const gphi *phi, tree lhs, tree rhs, + const region_model &old_state, region_model_context *ctxt) { /* For now, don't bother tracking the .MEM SSA names. */ @@ -1566,9 +1569,10 @@ region_model::handle_phi (const gphi *phi, if (VAR_DECL_IS_VIRTUAL_OPERAND (var)) return; - const svalue *rhs_sval = get_rvalue (rhs, ctxt); + const svalue *src_sval = old_state.get_rvalue (rhs, ctxt); + const region *dst_reg = old_state.get_lvalue (lhs, ctxt); - set_value (get_lvalue (lhs, ctxt), rhs_sval, ctxt); + set_value (dst_reg, src_sval, ctxt); if (ctxt) ctxt->on_phi (phi, rhs); @@ -3036,6 +3040,10 @@ region_model::update_for_phis (const supernode *snode, { gcc_assert (last_cfg_superedge); + /* Copy this state and pass it to handle_phi so that all of the phi stmts + are effectively handled simultaneously. */ + const region_model old_state (*this); + for (gphi_iterator gpi = const_cast(snode)->start_phis (); !gsi_end_p (gpi); gsi_next (&gpi)) { @@ -3044,8 +3052,8 @@ region_model::update_for_phis (const supernode *snode, tree src = last_cfg_superedge->get_phi_arg (phi); tree lhs = gimple_phi_result (phi); - /* Update next_state based on phi. */ - handle_phi (phi, lhs, src, ctxt); + /* Update next_state based on phi and old_state. */ + handle_phi (phi, lhs, src, old_state, ctxt); } } diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 734ec60..cc39929 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -582,6 +582,7 @@ class region_model region_model_context *ctxt); void handle_phi (const gphi *phi, tree lhs, tree rhs, + const region_model &old_state, region_model_context *ctxt); bool maybe_update_for_edge (const superedge &edge, diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index 3c3b775..bfa48a9 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -288,17 +288,23 @@ state_purge_per_ssa_name::add_to_worklist (const function_point &point, } } -/* Does this phi depend on itself? - e.g. in: - added_2 = PHI - the middle defn (from edge 3) requires added_2 itself. */ +/* Return true iff NAME is used by any of the phi nodes in SNODE + when processing the in-edge with PHI_ARG_IDX. */ static bool -self_referential_phi_p (const gphi *phi) +name_used_by_phis_p (tree name, const supernode *snode, + size_t phi_arg_idx) { - for (unsigned i = 0; i < gimple_phi_num_args (phi); i++) - if (gimple_phi_arg_def (phi, i) == gimple_phi_result (phi)) - return true; + gcc_assert (TREE_CODE (name) == SSA_NAME); + + for (gphi_iterator gpi + = const_cast (snode)->start_phis (); + !gsi_end_p (gpi); gsi_next (&gpi)) + { + gphi *phi = gpi.phi (); + if (gimple_phi_arg_def (phi, phi_arg_idx) == name) + return true; + } return false; } @@ -339,27 +345,27 @@ state_purge_per_ssa_name::process_point (const function_point &point, = const_cast (snode)->start_phis (); !gsi_end_p (gpi); gsi_next (&gpi)) { + gcc_assert (point.get_from_edge ()); + const cfg_superedge *cfg_sedge + = point.get_from_edge ()->dyn_cast_cfg_superedge (); + gcc_assert (cfg_sedge); + gphi *phi = gpi.phi (); /* Are we at the def-stmt for m_name? */ if (phi == def_stmt) { - /* Does this phi depend on itself? - e.g. in: - added_2 = PHI - the middle defn (from edge 3) requires added_2 itself - so we can't purge it here. */ - if (self_referential_phi_p (phi)) + if (name_used_by_phis_p (m_name, snode, + cfg_sedge->get_phi_arg_idx ())) { if (logger) - logger->log ("self-referential def stmt within phis;" + logger->log ("name in def stmt used within phis;" " continuing"); } else { - /* Otherwise, we can stop here, so that m_name - can be purged. */ if (logger) - logger->log ("def stmt within phis; terminating"); + logger->log ("name in def stmt not used within phis;" + " terminating"); return; } } diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 8611d0f..1eb2543 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -1032,12 +1032,21 @@ cfg_superedge::dump_label_to_pp (pretty_printer *pp, /* Otherwise, no label. */ } +/* Get the index number for this edge for use in phi stmts + in its destination. */ + +size_t +cfg_superedge::get_phi_arg_idx () const +{ + return m_cfg_edge->dest_idx; +} + /* Get the phi argument for PHI for this CFG edge. */ tree cfg_superedge::get_phi_arg (const gphi *phi) const { - size_t index = m_cfg_edge->dest_idx; + size_t index = get_phi_arg_idx (); return gimple_phi_arg_def (phi, index); } diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index f4090fd..877958f 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -514,6 +514,7 @@ class cfg_superedge : public superedge int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; } int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; } + size_t get_phi_arg_idx () const; tree get_phi_arg (const gphi *phi) const; private: -- cgit v1.1 From 87bd75cd49aac68e90bd9b6b5e14582d6e0ccafa Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 19:16:08 -0400 Subject: analyzer: fix ICE in binding_cluster::purge_state_involving [PR101522] gcc/analyzer/ChangeLog: PR analyzer/101522 * store.cc (binding_cluster::purge_state_involving): Don't change m_map whilst iterating through it. gcc/testsuite/ChangeLog: PR analyzer/101522 * g++.dg/analyzer/pr101522.C: New test. Signed-off-by: David Malcolm --- gcc/analyzer/store.cc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index 0042a20..8ee414d 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -1323,6 +1323,7 @@ binding_cluster::purge_state_involving (const svalue *sval, region_model_manager *sval_mgr) { auto_vec to_remove; + auto_vec > to_make_unknown; for (auto iter : m_map) { const binding_key *iter_key = iter.first; @@ -1335,17 +1336,20 @@ binding_cluster::purge_state_involving (const svalue *sval, } 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); - } + to_make_unknown.safe_push (std::make_pair(iter_key, + iter_sval->get_type ())); } for (auto iter : to_remove) { m_map.remove (iter); m_touched = true; } + for (auto iter : to_make_unknown) + { + const svalue *new_sval + = sval_mgr->get_or_create_unknown_svalue (iter.second); + m_map.put (iter.first, new_sval); + } } /* Get any SVAL bound to REG within this cluster via kind KIND, -- cgit v1.1 From 893b12cc12877aca1c9df6272123b26eddf12722 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 21 Jul 2021 19:19:31 -0400 Subject: analyzer: bulletproof -Wanalyzer-file-leak [PR101547] gcc/analyzer/ChangeLog: PR analyzer/101547 * sm-file.cc (file_leak::emit): Handle m_arg being NULL. (file_leak::describe_final_event): Handle ev.m_expr being NULL. gcc/testsuite/ChangeLog: PR analyzer/101547 * gcc.dg/analyzer/pr101547.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/sm-file.cc | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index b40a9a1..6a17019 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -193,9 +193,13 @@ public: /* CWE-775: "Missing Release of File Descriptor or Handle after Effective Lifetime". */ m.add_cwe (775); - return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak, - "leak of FILE %qE", - m_arg); + if (m_arg) + return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak, + "leak of FILE %qE", + m_arg); + else + return warning_meta (rich_loc, m, OPT_Wanalyzer_file_leak, + "leak of FILE"); } label_text describe_state_change (const evdesc::state_change &change) @@ -212,10 +216,21 @@ public: label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE { if (m_fopen_event.known_p ()) - return ev.formatted_print ("%qE leaks here; was opened at %@", - ev.m_expr, &m_fopen_event); + { + if (ev.m_expr) + return ev.formatted_print ("%qE leaks here; was opened at %@", + ev.m_expr, &m_fopen_event); + else + return ev.formatted_print ("leaks here; was opened at %@", + &m_fopen_event); + } else - return ev.formatted_print ("%qE leaks here", ev.m_expr); + { + if (ev.m_expr) + return ev.formatted_print ("%qE leaks here", ev.m_expr); + else + return ev.formatted_print ("leaks here"); + } } private: -- cgit v1.1 From 419c6c68e60adc8801b44dab72ebcd680cfe1d97 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 22 Jul 2021 00:16:46 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index f32fe08..272bf15 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,58 @@ +2021-07-21 David Malcolm + + PR analyzer/101547 + * sm-file.cc (file_leak::emit): Handle m_arg being NULL. + (file_leak::describe_final_event): Handle ev.m_expr being NULL. + +2021-07-21 David Malcolm + + PR analyzer/101522 + * store.cc (binding_cluster::purge_state_involving): Don't change + m_map whilst iterating through it. + +2021-07-21 David Malcolm + + * region-model.cc (region_model::handle_phi): Add "old_state" + param and use it. + (region_model::update_for_phis): Update so that all of the phi + stmts are effectively handled simultaneously, rather than in + order. + * region-model.h (region_model::handle_phi): Add "old_state" + param. + * state-purge.cc (self_referential_phi_p): Replace with... + (name_used_by_phis_p): ...this new function. + (state_purge_per_ssa_name::process_point): Update to use the + above, so that all phi stmts at a basic block are effectively + considered simultaneously, and only consider the phi arguments for + the pertinent in-edge. + * supergraph.cc (cfg_superedge::get_phi_arg_idx): New. + (cfg_superedge::get_phi_arg): Use the above. + * supergraph.h (cfg_superedge::get_phi_arg_idx): New decl. + +2021-07-21 David Malcolm + + * state-purge.cc (state_purge_annotator::add_node_annotations): + Rather than erroneously always using the NULL in-edge, determine + each relevant in-edge, and print the appropriate data for each + in-edge. Use print_needed to print the data as comma-separated + lists of SSA names. + (print_vec_of_names): Add "within_table" param and use it. + (state_purge_annotator::add_stmt_annotations): Factor out + collation and printing code into... + (state_purge_annotator::print_needed): ...this new function. + * state-purge.h (state_purge_annotator::print_needed): New decl. + +2021-07-21 David Malcolm + + * program-point.cc (function_point::print): Show src BB index at + BEFORE_SUPERNODE. + +2021-07-21 David Malcolm + + * svalue.cc (infix_p): New. + (binop_svalue::dump_to_pp): Use it to print MIN_EXPR and MAX_EXPR + in prefix form, rather than infix. + 2021-07-19 David Malcolm PR analyzer/101503 -- cgit v1.1 From 60933a148ab33c82915b40690b3ced6abc32a1bf Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 22 Jul 2021 22:36:05 -0400 Subject: analyzer: fix feasibility false +ve with overly complex svalues gcc/analyzer/ChangeLog: * diagnostic-manager.cc (class auto_disable_complexity_checks): New. (epath_finder::explore_feasible_paths): Use it to disable complexity checks whilst processing the worklist. * region-model-manager.cc (region_model_manager::region_model_manager): Initialize m_check_complexity. (region_model_manager::reject_if_too_complex): Bail if m_check_complexity is false. * region-model.h (region_model_manager::enable_complexity_check): New. (region_model_manager::disable_complexity_check): New. (region_model_manager::m_check_complexity): New. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/feasibility-3.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/diagnostic-manager.cc | 47 ++++++++++++++++++++++++++++++------ gcc/analyzer/region-model-manager.cc | 4 +++ gcc/analyzer/region-model.h | 5 ++++ 3 files changed, 49 insertions(+), 7 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 631fef6..ef3df32 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -292,6 +292,34 @@ private: const shortest_paths &m_sep; }; +/* When we're building the exploded graph we want to simplify + overly-complicated symbolic values down to "UNKNOWN" to try to avoid + state explosions and unbounded chains of exploration. + + However, when we're building the feasibility graph for a diagnostic + (actually a tree), we don't want UNKNOWN values, as conditions on them + are also unknown: we don't want to have a contradiction such as a path + where (VAL != 0) and then (VAL == 0) along the same path. + + Hence this is an RAII class for temporarily disabling complexity-checking + in the region_model_manager, for use within + epath_finder::explore_feasible_paths. */ + +class auto_disable_complexity_checks +{ +public: + auto_disable_complexity_checks (region_model_manager *mgr) : m_mgr (mgr) + { + m_mgr->disable_complexity_check (); + } + ~auto_disable_complexity_checks () + { + m_mgr->enable_complexity_check (); + } +private: + region_model_manager *m_mgr; +}; + /* Attempt to find the shortest feasible path from the origin to TARGET_ENODE by iteratively building a feasible_graph, in which every path to a feasible_node is feasible by construction. @@ -344,6 +372,8 @@ epath_finder::explore_feasible_paths (const exploded_node *target_enode, logger *logger = get_logger (); LOG_SCOPE (logger); + region_model_manager *mgr = m_eg.get_engine ()->get_model_manager (); + /* Determine the shortest path to TARGET_ENODE from each node in the exploded graph. */ shortest_paths sep @@ -363,8 +393,7 @@ epath_finder::explore_feasible_paths (const exploded_node *target_enode, /* Populate the worklist with the origin node. */ { - feasibility_state init_state (m_eg.get_engine ()->get_model_manager (), - m_eg.get_supergraph ()); + feasibility_state init_state (mgr, m_eg.get_supergraph ()); feasible_node *origin = fg.add_node (m_eg.get_origin (), init_state, 0); worklist.add_node (origin); } @@ -376,11 +405,15 @@ epath_finder::explore_feasible_paths (const exploded_node *target_enode, /* Set this if we find a feasible path to TARGET_ENODE. */ exploded_path *best_path = NULL; - while (process_worklist_item (&worklist, tg, &fg, target_enode, diag_idx, - &best_path)) - { - /* Empty; the work is done within process_worklist_item. */ - } + { + auto_disable_complexity_checks sentinel (mgr); + + while (process_worklist_item (&worklist, tg, &fg, target_enode, diag_idx, + &best_path)) + { + /* Empty; the work is done within process_worklist_item. */ + } + } if (logger) { diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index fccb93e..14c57d8 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -71,6 +71,7 @@ region_model_manager::region_model_manager () m_stack_region (alloc_region_id (), &m_root_region), m_heap_region (alloc_region_id (), &m_root_region), m_unknown_NULL (NULL), + m_check_complexity (true), m_max_complexity (0, 0), m_code_region (alloc_region_id (), &m_root_region), m_fndecls_map (), m_labels_map (), @@ -160,6 +161,9 @@ region_model_manager::too_complex_p (const complexity &c) const bool region_model_manager::reject_if_too_complex (svalue *sval) { + if (!m_check_complexity) + return false; + const complexity &c = sval->get_complexity (); if (!too_complex_p (c)) { diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index cc39929..1c7a386 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -323,6 +323,9 @@ public: void log_stats (logger *logger, bool show_objs) const; + void enable_complexity_check (void) { m_check_complexity = true; } + void disable_complexity_check (void) { m_check_complexity = false; } + private: bool too_complex_p (const complexity &c) const; bool reject_if_too_complex (svalue *sval); @@ -407,6 +410,8 @@ private: conjured_svalue *> conjured_values_map_t; conjured_values_map_t m_conjured_values_map; + bool m_check_complexity; + /* Maximum complexity of svalues that weren't rejected. */ complexity m_max_complexity; -- cgit v1.1 From ead235f60139edc6eb408d8d083cbb15e417b447 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sat, 24 Jul 2021 00:16:44 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 272bf15..fd799e3 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,19 @@ +2021-07-23 David Malcolm + + * diagnostic-manager.cc + (class auto_disable_complexity_checks): New. + (epath_finder::explore_feasible_paths): Use it to disable + complexity checks whilst processing the worklist. + * region-model-manager.cc + (region_model_manager::region_model_manager): Initialize + m_check_complexity. + (region_model_manager::reject_if_too_complex): Bail if + m_check_complexity is false. + * region-model.h + (region_model_manager::enable_complexity_check): New. + (region_model_manager::disable_complexity_check): New. + (region_model_manager::m_check_complexity): New. + 2021-07-21 David Malcolm PR analyzer/101547 -- cgit v1.1 From 3a1d168e9e0e3e38adedf5df393e9f8c075fc755 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 26 Jul 2021 15:25:00 -0400 Subject: analyzer: fix uninit false +ve when returning structs This patch fixes some false positives from -Wanalyzer-use-of-uninitialized-value when returning structs from functions (seen on the Linux kernel). gcc/analyzer/ChangeLog: * region-model.cc (region_model::on_call_pre): Always set conjured LHS, not just for SSA names. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/sock-1.c: New test. * gcc.dg/analyzer/sock-2.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index c029759..9d84b8c 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1066,14 +1066,11 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, 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); - } + 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)) -- cgit v1.1 From 1a7febe9432f5302620aebc9cb5760c6c1d31d4c Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 27 Jul 2021 00:16:27 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index fd799e3..3234732 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,8 @@ +2021-07-26 David Malcolm + + * region-model.cc (region_model::on_call_pre): Always set conjured + LHS, not just for SSA names. + 2021-07-23 David Malcolm * diagnostic-manager.cc -- cgit v1.1 From 84606efb0c6b1c1598d5ec6b05544e71596663b5 Mon Sep 17 00:00:00 2001 From: Siddhesh Poyarekar Date: Wed, 28 Jul 2021 10:33:46 +0530 Subject: analyzer: Recognize __builtin_free as a matching deallocator Recognize __builtin_free as being equivalent to free when passed into __attribute__((malloc ())), similar to how it is treated when it is encountered as a call. This fixes spurious warnings in glibc where xmalloc family of allocators as well as reallocarray, memalign, etc. are declared to have __builtin_free as the free function. gcc/analyzer/ChangeLog: * sm-malloc.cc (malloc_state_machine::get_or_create_deallocator): Recognize __builtin_free. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/attr-malloc-1.c (compatible_alloc, compatible_alloc2): New extern allocator declarations. (test_9, test_10): New tests. --- gcc/analyzer/sm-malloc.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 9707a68..1d69d57 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1511,7 +1511,8 @@ malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl) /* Reuse "free". */ deallocator *d; if (is_named_call_p (deallocator_fndecl, "free") - || is_std_named_call_p (deallocator_fndecl, "free")) + || is_std_named_call_p (deallocator_fndecl, "free") + || is_named_call_p (deallocator_fndecl, "__builtin_free")) d = &m_free.m_deallocator; else { -- cgit v1.1 From 31534ac26e0ec1deeb648b2548dbbe17574ea78c Mon Sep 17 00:00:00 2001 From: Siddhesh Poyarekar Date: Wed, 28 Jul 2021 15:43:47 +0530 Subject: analyzer: Handle strdup builtins Consolidate allocator builtin handling and add support for __builtin_strdup and __builtin_strndup. gcc/analyzer/ChangeLog: * analyzer.cc (is_named_call_p, is_std_named_call_p): Make first argument a const_tree. * analyzer.h (is_named_call_p, -s_std_named_call_p): Likewise. * sm-malloc.cc (known_allocator_p): New function. (malloc_state_machine::on_stmt): Use it. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/strdup-1.c (test_4, test_5, test_6): New tests. --- gcc/analyzer/analyzer.cc | 8 ++++---- gcc/analyzer/analyzer.h | 8 ++++---- gcc/analyzer/sm-malloc.cc | 41 +++++++++++++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 16 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index ddace9a..b845b86 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -240,7 +240,7 @@ is_special_named_call_p (const gcall *call, const char *funcname, Compare with special_function_p in calls.c. */ bool -is_named_call_p (tree fndecl, const char *funcname) +is_named_call_p (const_tree fndecl, const char *funcname) { gcc_assert (fndecl); gcc_assert (funcname); @@ -292,7 +292,7 @@ is_std_function_p (const_tree fndecl) /* Like is_named_call_p, but look for std::FUNCNAME. */ bool -is_std_named_call_p (tree fndecl, const char *funcname) +is_std_named_call_p (const_tree fndecl, const char *funcname) { gcc_assert (fndecl); gcc_assert (funcname); @@ -314,7 +314,7 @@ is_std_named_call_p (tree fndecl, const char *funcname) arguments? */ bool -is_named_call_p (tree fndecl, const char *funcname, +is_named_call_p (const_tree fndecl, const char *funcname, const gcall *call, unsigned int num_args) { gcc_assert (fndecl); @@ -332,7 +332,7 @@ is_named_call_p (tree fndecl, const char *funcname, /* Like is_named_call_p, but check for std::FUNCNAME. */ bool -is_std_named_call_p (tree fndecl, const char *funcname, +is_std_named_call_p (const_tree fndecl, const char *funcname, const gcall *call, unsigned int num_args) { gcc_assert (fndecl); diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 90143d9..8de5d60 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -220,11 +220,11 @@ enum access_direction extern bool is_special_named_call_p (const gcall *call, const char *funcname, unsigned int num_args); -extern bool is_named_call_p (tree fndecl, const char *funcname); -extern bool is_named_call_p (tree fndecl, const char *funcname, +extern bool is_named_call_p (const_tree fndecl, const char *funcname); +extern bool is_named_call_p (const_tree fndecl, const char *funcname, const gcall *call, unsigned int num_args); -extern bool is_std_named_call_p (tree fndecl, const char *funcname); -extern bool is_std_named_call_p (tree fndecl, const char *funcname, +extern bool is_std_named_call_p (const_tree fndecl, const char *funcname); +extern bool is_std_named_call_p (const_tree fndecl, const char *funcname, const gcall *call, unsigned int num_args); extern bool is_setjmp_call_p (const gcall *call); extern bool is_longjmp_call_p (const gcall *call); diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 1d69d57..4f07d1f 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1526,6 +1526,38 @@ malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl) return d; } +/* Try to identify the function declaration either by name or as a known malloc + builtin. */ + +static bool +known_allocator_p (const_tree fndecl, const gcall *call) +{ + /* Either it is a function we know by name and number of arguments... */ + if (is_named_call_p (fndecl, "malloc", call, 1) + || is_named_call_p (fndecl, "calloc", call, 2) + || is_std_named_call_p (fndecl, "malloc", call, 1) + || is_std_named_call_p (fndecl, "calloc", call, 2) + || is_named_call_p (fndecl, "strdup", call, 1) + || is_named_call_p (fndecl, "strndup", call, 2)) + return true; + + /* ... or it is a builtin allocator that allocates objects freed with + __builtin_free. */ + if (fndecl_built_in_p (fndecl)) + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_MALLOC: + case BUILT_IN_CALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + return true; + default: + break; + } + + return false; +} + /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */ bool @@ -1536,14 +1568,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt, if (const gcall *call = dyn_cast (stmt)) if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) { - if (is_named_call_p (callee_fndecl, "malloc", call, 1) - || is_named_call_p (callee_fndecl, "calloc", call, 2) - || is_std_named_call_p (callee_fndecl, "malloc", call, 1) - || is_std_named_call_p (callee_fndecl, "calloc", call, 2) - || is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1) - || is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2) - || is_named_call_p (callee_fndecl, "strdup", call, 1) - || is_named_call_p (callee_fndecl, "strndup", call, 2)) + if (known_allocator_p (callee_fndecl, call)) { on_allocator_call (sm_ctxt, call, &m_free); return true; -- cgit v1.1 From b5081130166a4f2e363f116e0e6b43d83422c947 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 28 Jul 2021 14:45:59 -0400 Subject: analyzer: remove redundant return value from various impl_call_* gcc/analyzer/ChangeLog: * region-model-impl-calls.cc (region_model::impl_call_alloca): Drop redundant return value. (region_model::impl_call_builtin_expect): Likewise. (region_model::impl_call_calloc): Likewise. (region_model::impl_call_malloc): Likewise. (region_model::impl_call_memset): Likewise. (region_model::impl_call_operator_new): Likewise. (region_model::impl_call_operator_delete): Likewise. (region_model::impl_call_strlen): Likewise. * region-model.cc (region_model::on_call_pre): Fix return value of known functions that don't have unknown side-effects. * region-model.h (region_model::impl_call_alloca): Drop redundant return value. (region_model::impl_call_builtin_expect): Likewise. (region_model::impl_call_calloc): Likewise. (region_model::impl_call_malloc): Likewise. (region_model::impl_call_memset): Likewise. (region_model::impl_call_strlen): Likewise. (region_model::impl_call_operator_new): Likewise. (region_model::impl_call_operator_delete): Likewise. Signed-off-by: David Malcolm --- gcc/analyzer/region-model-impl-calls.cc | 31 ++++++++------------- gcc/analyzer/region-model.cc | 49 +++++++++++++++++++++++---------- gcc/analyzer/region-model.h | 16 +++++------ 3 files changed, 53 insertions(+), 43 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index eff8caa..e5a6cb2 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -207,7 +207,7 @@ call_details::get_or_create_conjured_svalue (const region *reg) const /* Handle the on_call_pre part of "alloca". */ -bool +void region_model::impl_call_alloca (const call_details &cd) { const svalue *size_sval = cd.get_arg_svalue (0); @@ -215,7 +215,6 @@ region_model::impl_call_alloca (const call_details &cd) const svalue *ptr_sval = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); cd.maybe_set_lhs (ptr_sval); - return true; } /* Handle a call to "__analyzer_describe". @@ -274,18 +273,17 @@ region_model::impl_call_analyzer_eval (const gcall *call, /* Handle the on_call_pre part of "__builtin_expect" etc. */ -bool +void region_model::impl_call_builtin_expect (const call_details &cd) { /* __builtin_expect's return value is its initial argument. */ const svalue *sval = cd.get_arg_svalue (0); cd.maybe_set_lhs (sval); - return false; } /* Handle the on_call_pre part of "calloc". */ -bool +void region_model::impl_call_calloc (const call_details &cd) { const svalue *nmemb_sval = cd.get_arg_svalue (0); @@ -302,7 +300,6 @@ region_model::impl_call_calloc (const call_details &cd) = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); cd.maybe_set_lhs (ptr_sval); } - return true; } /* Handle the on_call_pre part of "error" and "error_at_line" from @@ -397,7 +394,7 @@ region_model::impl_call_free (const call_details &cd) /* Handle the on_call_pre part of "malloc". */ -bool +void region_model::impl_call_malloc (const call_details &cd) { const svalue *size_sval = cd.get_arg_svalue (0); @@ -408,7 +405,6 @@ region_model::impl_call_malloc (const call_details &cd) = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); cd.maybe_set_lhs (ptr_sval); } - return true; } /* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy". */ @@ -439,7 +435,7 @@ region_model::impl_call_memcpy (const call_details &cd) /* Handle the on_call_pre part of "memset" and "__builtin_memset". */ -bool +void region_model::impl_call_memset (const call_details &cd) { const svalue *dest_sval = cd.get_arg_svalue (0); @@ -457,12 +453,11 @@ region_model::impl_call_memset (const call_details &cd) num_bytes_sval); check_region_for_write (sized_dest_reg, cd.get_ctxt ()); fill_region (sized_dest_reg, fill_value_u8); - return true; } /* Handle the on_call_pre part of "operator new". */ -bool +void region_model::impl_call_operator_new (const call_details &cd) { const svalue *size_sval = cd.get_arg_svalue (0); @@ -473,14 +468,13 @@ region_model::impl_call_operator_new (const call_details &cd) = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); cd.maybe_set_lhs (ptr_sval); } - return false; } /* Handle the on_call_pre part of "operator delete", which comes in both sized and unsized variants (2 arguments and 1 argument respectively). */ -bool +void region_model::impl_call_operator_delete (const call_details &cd) { const svalue *ptr_sval = cd.get_arg_svalue (0); @@ -490,7 +484,6 @@ region_model::impl_call_operator_delete (const call_details &cd) poisoning pointers. */ unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); } - return false; } /* Handle the on_call_pre part of "realloc". */ @@ -521,10 +514,9 @@ region_model::impl_call_strcpy (const call_details &cd) mark_region_as_unknown (dest_reg, cd.get_uncertainty ()); } -/* Handle the on_call_pre part of "strlen". - Return true if the LHS is updated. */ +/* Handle the on_call_pre part of "strlen". */ -bool +void region_model::impl_call_strlen (const call_details &cd) { region_model_context *ctxt = cd.get_ctxt (); @@ -543,11 +535,10 @@ region_model::impl_call_strlen (const call_details &cd) const svalue *result_sval = m_mgr->get_or_create_constant_svalue (t_cst); cd.maybe_set_lhs (result_sval); - return true; + return; } } - /* Otherwise an unknown value. */ - return true; + /* Otherwise a conjured value. */ } /* Handle calls to functions referenced by diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 9d84b8c..92fa917 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1080,7 +1080,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, default: break; case IFN_BUILTIN_EXPECT: - return impl_call_builtin_expect (cd); + impl_call_builtin_expect (cd); + return false; } } @@ -1101,17 +1102,21 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, break; case BUILT_IN_ALLOCA: case BUILT_IN_ALLOCA_WITH_ALIGN: - return impl_call_alloca (cd); + impl_call_alloca (cd); + return false; case BUILT_IN_CALLOC: - return impl_call_calloc (cd); + impl_call_calloc (cd); + return false; case BUILT_IN_EXPECT: case BUILT_IN_EXPECT_WITH_PROBABILITY: - return impl_call_builtin_expect (cd); + impl_call_builtin_expect (cd); + return false; case BUILT_IN_FREE: /* Handle in "on_call_post". */ break; case BUILT_IN_MALLOC: - return impl_call_malloc (cd); + impl_call_malloc (cd); + return false; case BUILT_IN_MEMCPY: case BUILT_IN_MEMCPY_CHK: impl_call_memcpy (cd); @@ -1129,9 +1134,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, impl_call_strcpy (cd); return false; case BUILT_IN_STRLEN: - if (impl_call_strlen (cd)) - return false; - break; + impl_call_strlen (cd); + return false; /* Stdio builtins. */ case BUILT_IN_FPRINTF: @@ -1158,11 +1162,20 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, break; } else if (is_named_call_p (callee_fndecl, "malloc", call, 1)) - return impl_call_malloc (cd); + { + impl_call_malloc (cd); + return false; + } else if (is_named_call_p (callee_fndecl, "calloc", call, 2)) - return impl_call_calloc (cd); + { + impl_call_calloc (cd); + return false; + } else if (is_named_call_p (callee_fndecl, "alloca", call, 1)) - return impl_call_alloca (cd); + { + impl_call_alloca (cd); + return false; + } else if (is_named_call_p (callee_fndecl, "realloc", call, 2)) { impl_call_realloc (cd); @@ -1207,13 +1220,19 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, else if (is_named_call_p (callee_fndecl, "strlen", call, 1) && POINTER_TYPE_P (cd.get_arg_type (0))) { - if (impl_call_strlen (cd)) - return false; + impl_call_strlen (cd); + return false; } else if (is_named_call_p (callee_fndecl, "operator new", call, 1)) - return impl_call_operator_new (cd); + { + impl_call_operator_new (cd); + return false; + } else if (is_named_call_p (callee_fndecl, "operator new []", call, 1)) - return impl_call_operator_new (cd); + { + impl_call_operator_new (cd); + return false; + } else if (is_named_call_p (callee_fndecl, "operator delete", call, 1) || is_named_call_p (callee_fndecl, "operator delete", call, 2) || is_named_call_p (callee_fndecl, "operator delete []", call, 1)) diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 1c7a386..d07ce9c 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -546,28 +546,28 @@ class region_model 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_alloca (const call_details &cd); void impl_call_analyzer_describe (const gcall *call, region_model_context *ctxt); void impl_call_analyzer_dump_capacity (const gcall *call, region_model_context *ctxt); void impl_call_analyzer_eval (const gcall *call, region_model_context *ctxt); - bool impl_call_builtin_expect (const call_details &cd); - bool impl_call_calloc (const call_details &cd); + void impl_call_builtin_expect (const call_details &cd); + void 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_malloc (const call_details &cd); void impl_call_memcpy (const call_details &cd); - bool impl_call_memset (const call_details &cd); + void impl_call_memset (const call_details &cd); void impl_call_realloc (const call_details &cd); void impl_call_strcpy (const call_details &cd); - bool impl_call_strlen (const call_details &cd); - bool impl_call_operator_new (const call_details &cd); - bool impl_call_operator_delete (const call_details &cd); + void impl_call_strlen (const call_details &cd); + void impl_call_operator_new (const call_details &cd); + void impl_call_operator_delete (const call_details &cd); void impl_deallocation_call (const call_details &cd); void handle_unrecognized_call (const gcall *call, -- cgit v1.1 From 37eb3ef48c9840475646528751b5f8ffb7eb34ce Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 28 Jul 2021 14:47:54 -0400 Subject: analyzer: play better with -fsanitize=bounds gcc/analyzer/ChangeLog: * region-model.cc (region_model::on_call_pre): Treat IFN_UBSAN_BOUNDS, BUILT_IN_STACK_SAVE, and BUILT_IN_STACK_RESTORE as no-ops, rather than handling them as unknown functions. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/torture/ubsan-1.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 92fa917..1bc411b 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1082,6 +1082,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, case IFN_BUILTIN_EXPECT: impl_call_builtin_expect (cd); return false; + case IFN_UBSAN_BOUNDS: + return false; } } @@ -1137,6 +1139,10 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, impl_call_strlen (cd); return false; + case BUILT_IN_STACK_SAVE: + case BUILT_IN_STACK_RESTORE: + return false; + /* Stdio builtins. */ case BUILT_IN_FPRINTF: case BUILT_IN_FPRINTF_UNLOCKED: -- cgit v1.1 From 3916902930769d5172c0feaa5f535ca7b2bafdf7 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 29 Jul 2021 00:16:43 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 3234732..bc0f4bd 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,46 @@ +2021-07-28 David Malcolm + + * region-model.cc (region_model::on_call_pre): Treat + IFN_UBSAN_BOUNDS, BUILT_IN_STACK_SAVE, and BUILT_IN_STACK_RESTORE + as no-ops, rather than handling them as unknown functions. + +2021-07-28 David Malcolm + + * region-model-impl-calls.cc (region_model::impl_call_alloca): + Drop redundant return value. + (region_model::impl_call_builtin_expect): Likewise. + (region_model::impl_call_calloc): Likewise. + (region_model::impl_call_malloc): Likewise. + (region_model::impl_call_memset): Likewise. + (region_model::impl_call_operator_new): Likewise. + (region_model::impl_call_operator_delete): Likewise. + (region_model::impl_call_strlen): Likewise. + * region-model.cc (region_model::on_call_pre): Fix return value of + known functions that don't have unknown side-effects. + * region-model.h (region_model::impl_call_alloca): Drop redundant + return value. + (region_model::impl_call_builtin_expect): Likewise. + (region_model::impl_call_calloc): Likewise. + (region_model::impl_call_malloc): Likewise. + (region_model::impl_call_memset): Likewise. + (region_model::impl_call_strlen): Likewise. + (region_model::impl_call_operator_new): Likewise. + (region_model::impl_call_operator_delete): Likewise. + +2021-07-28 Siddhesh Poyarekar + + * analyzer.cc (is_named_call_p, is_std_named_call_p): Make + first argument a const_tree. + * analyzer.h (is_named_call_p, -s_std_named_call_p): Likewise. + * sm-malloc.cc (known_allocator_p): New function. + (malloc_state_machine::on_stmt): Use it. + +2021-07-28 Siddhesh Poyarekar + + * sm-malloc.cc + (malloc_state_machine::get_or_create_deallocator): Recognize + __builtin_free. + 2021-07-26 David Malcolm * region-model.cc (region_model::on_call_pre): Always set conjured -- cgit v1.1 From e8de5bad250909c9599da49d07f09fb9a8cabd0f Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Sun, 25 Jul 2021 14:47:53 +0530 Subject: analyzer: : Refactor callstring to work with pairs of supernodes. 2021-07-25 Ankur Saini gcc/analyzer/ChangeLog: * call-string.cc (call_string::element_t::operator==): New operator. (call_String::element_t::operator!=): New operator. (call_string::element_t::get_caller_function): New function. (call_string::element_t::get_callee_function): New function. (call_string::call_string): Refactor to Initialise m_elements. (call_string::operator=): Refactor to work with m_elements. (call_string::operator==): Likewise. (call_string::to_json): Likewise. (call_string::hash): Refactor to hash e.m_caller. (call_string::push_call): Refactor to work with m_elements. (call_string::push_call): New overload to push call via supernodes. (call_string::pop): Refactor to work with m_elements. (call_string::calc_recursion_depth): Likewise. (call_string::cmp): Likewise. (call_string::validate): Likewise. (call_string::operator[]): Likewise. * call-string.h (class supernode): New forward decl. (struct call_string::element_t): New struct. (call_string::call_string): Refactor to initialise m_elements. (call_string::bool empty_p): Refactor to work with m_elements. (call_string::get_callee_node): New decl. (call_string::get_caller_node): New decl. (m_elements): Replaces m_return_edges. * program-point.cc (program_point::get_function_at_depth): Refactor to work with new call-string format. (program_point::validate): Likewise. (program_point::on_edge): Likewise. --- gcc/analyzer/call-string.cc | 143 +++++++++++++++++++++++++++++++----------- gcc/analyzer/call-string.h | 52 ++++++++++++--- gcc/analyzer/program-point.cc | 10 +-- 3 files changed, 154 insertions(+), 51 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc index 9f4f77a..1e652a0 100644 --- a/gcc/analyzer/call-string.cc +++ b/gcc/analyzer/call-string.cc @@ -45,13 +45,42 @@ along with GCC; see the file COPYING3. If not see /* class call_string. */ +/* struct call_string::element_t. */ + +/* call_string::element_t's equality operator. */ + +bool +call_string::element_t::operator== (const call_string::element_t &other) const +{ + return (m_caller == other.m_caller && m_callee == other.m_callee); +} + +/* call_string::element_t's inequality operator. */ +bool +call_string::element_t::operator!= (const call_string::element_t &other) const +{ + return !(*this == other); +} + +function * +call_string::element_t::get_caller_function () const +{ + return m_caller->get_function (); +} + +function * +call_string::element_t::get_callee_function () const +{ + return m_callee->get_function (); +} + /* call_string's copy ctor. */ call_string::call_string (const call_string &other) -: m_return_edges (other.m_return_edges.length ()) +: m_elements (other.m_elements.length ()) { - for (const return_superedge *e : other.m_return_edges) - m_return_edges.quick_push (e); + for (const call_string::element_t &e : other.m_elements) + m_elements.quick_push (e); } /* call_string's assignment operator. */ @@ -60,12 +89,12 @@ call_string& call_string::operator= (const call_string &other) { // would be much simpler if we could rely on vec<> assignment op - m_return_edges.truncate (0); - m_return_edges.reserve (other.m_return_edges.length (), true); - const return_superedge *e; + m_elements.truncate (0); + m_elements.reserve (other.m_elements.length (), true); + call_string::element_t *e; int i; - FOR_EACH_VEC_ELT (other.m_return_edges, i, e) - m_return_edges.quick_push (e); + FOR_EACH_VEC_ELT (other.m_elements, i, e) + m_elements.quick_push (*e); return *this; } @@ -74,12 +103,12 @@ call_string::operator= (const call_string &other) bool call_string::operator== (const call_string &other) const { - if (m_return_edges.length () != other.m_return_edges.length ()) + if (m_elements.length () != other.m_elements.length ()) return false; - const return_superedge *e; + call_string::element_t *e; int i; - FOR_EACH_VEC_ELT (m_return_edges, i, e) - if (e != other.m_return_edges[i]) + FOR_EACH_VEC_ELT (m_elements, i, e) + if (*e != other.m_elements[i]) return false; return true; } @@ -91,15 +120,15 @@ call_string::print (pretty_printer *pp) const { pp_string (pp, "["); - const return_superedge *e; + call_string::element_t *e; int i; - FOR_EACH_VEC_ELT (m_return_edges, i, e) + FOR_EACH_VEC_ELT (m_elements, i, e) { if (i > 0) pp_string (pp, ", "); pp_printf (pp, "(SN: %i -> SN: %i in %s)", - e->m_src->m_index, e->m_dest->m_index, - function_name (e->m_dest->m_fun)); + e->m_callee->m_index, e->m_caller->m_index, + function_name (e->m_caller->m_fun)); } pp_string (pp, "]"); @@ -109,22 +138,22 @@ call_string::print (pretty_printer *pp) const [{"src_snode_idx" : int, "dst_snode_idx" : int, "funcname" : str}, - ...for each return_superedge in the callstring]. */ + ...for each element in the callstring]. */ json::value * call_string::to_json () const { json::array *arr = new json::array (); - for (const return_superedge *e : m_return_edges) + for (const call_string::element_t &e : m_elements) { json::object *e_obj = new json::object (); e_obj->set ("src_snode_idx", - new json::integer_number (e->m_src->m_index)); + new json::integer_number (e.m_callee->m_index)); e_obj->set ("dst_snode_idx", - new json::integer_number (e->m_dest->m_index)); + new json::integer_number (e.m_caller->m_index)); e_obj->set ("funcname", - new json::string (function_name (e->m_dest->m_fun))); + new json::string (function_name (e.m_caller->m_fun))); arr->append (e_obj); } @@ -137,8 +166,8 @@ hashval_t call_string::hash () const { inchash::hash hstate; - for (const return_superedge *e : m_return_edges) - hstate.add_ptr (e); + for (const call_string::element_t &e : m_elements) + hstate.add_ptr (e.m_caller); return hstate.end (); } @@ -152,22 +181,36 @@ call_string::push_call (const supergraph &sg, gcc_assert (call_sedge); const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg); gcc_assert (return_sedge); - m_return_edges.safe_push (return_sedge); + call_string::element_t e (return_sedge->m_dest, return_sedge->m_src); + m_elements.safe_push (e); +} + +void +call_string::push_call (const supernode *caller, + const supernode *callee) +{ + call_string::element_t e (caller, callee); + m_elements.safe_push (e); +} + +call_string::element_t +call_string::pop () +{ + return m_elements.pop(); } /* Count the number of times the top-most call site appears in the stack. */ - int call_string::calc_recursion_depth () const { - if (m_return_edges.is_empty ()) + if (m_elements.is_empty ()) return 0; - const return_superedge *top_return_sedge - = m_return_edges[m_return_edges.length () - 1]; + const call_string::element_t top_return_sedge + = m_elements[m_elements.length () - 1]; int result = 0; - for (const return_superedge *e : m_return_edges) + for (const call_string::element_t &e : m_elements) if (e == top_return_sedge) ++result; return result; @@ -201,13 +244,15 @@ call_string::cmp (const call_string &a, if (i >= len_b) return -1; - /* Otherwise, compare the edges. */ - const return_superedge *edge_a = a[i]; - const return_superedge *edge_b = b[i]; - int src_cmp = edge_a->m_src->m_index - edge_b->m_src->m_index; + /* Otherwise, compare the node pairs. */ + const call_string::element_t a_node_pair = a[i]; + const call_string::element_t b_node_pair = b[i]; + int src_cmp + = a_node_pair.m_callee->m_index - b_node_pair.m_callee->m_index; if (src_cmp) return src_cmp; - int dest_cmp = edge_a->m_dest->m_index - edge_b->m_dest->m_index; + int dest_cmp + = a_node_pair.m_caller->m_index - b_node_pair.m_caller->m_index; if (dest_cmp) return dest_cmp; i++; @@ -215,6 +260,26 @@ call_string::cmp (const call_string &a, } } +/* Return the pointer to callee of the topmost call in the stack, + or NULL if stack is empty. */ +const supernode * +call_string::get_callee_node () const +{ + if(m_elements.is_empty ()) + return NULL; + return m_elements[m_elements.length () - 1].m_callee; +} + +/* Return the pointer to caller of the topmost call in the stack, + or NULL if stack is empty. */ +const supernode * +call_string::get_caller_node () const +{ + if(m_elements.is_empty ()) + return NULL; + return m_elements[m_elements.length () - 1].m_caller; +} + /* Assert that this object is sane. */ void @@ -226,12 +291,14 @@ call_string::validate () const #endif /* Each entry's "caller" should be the "callee" of the previous entry. */ - const return_superedge *e; + call_string::element_t *e; int i; - FOR_EACH_VEC_ELT (m_return_edges, i, e) + FOR_EACH_VEC_ELT (m_elements, i, e) if (i > 0) - gcc_assert (e->get_caller_function () - == m_return_edges[i - 1]->get_callee_function ()); + { + gcc_assert (e->get_caller_function () == + m_elements[i - 1].get_callee_function ()); + } } #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h index 7721571..a1ac60d 100644 --- a/gcc/analyzer/call-string.h +++ b/gcc/analyzer/call-string.h @@ -24,22 +24,48 @@ along with GCC; see the file COPYING3. If not see namespace ana { class supergraph; +class supernode; class call_superedge; class return_superedge; + /* A string of return_superedge pointers, representing a call stack at a program point. This is used to ensure that we generate interprocedurally valid paths i.e. that we return to the same callsite that called us. - The class actually stores the return edges, rather than the call edges, - since that's what we need to compare against. */ + The class stores returning calls ( which may be represented by a + returning superedge ). We do so because this is what we need to compare + against. */ class call_string { public: - call_string () : m_return_edges () {} + /* A struct representing an element in the call_string. + + Each element represents a path from m_callee to m_caller which represents + returning from function. */ + + struct element_t + { + element_t (const supernode *caller, const supernode *callee) + : m_caller (caller), m_callee (callee) + { + } + + bool operator== (const element_t &other) const; + bool operator!= (const element_t &other) const; + + /* Accessors */ + function *get_caller_function () const; + function *get_callee_function () const; + + const supernode *m_caller; + const supernode *m_callee; + }; + + call_string () : m_elements () {} call_string (const call_string &other); call_string& operator= (const call_string &other); @@ -51,27 +77,35 @@ public: hashval_t hash () const; - bool empty_p () const { return m_return_edges.is_empty (); } + bool empty_p () const { return m_elements.is_empty (); } void push_call (const supergraph &sg, const call_superedge *sedge); - const return_superedge *pop () { return m_return_edges.pop (); } + + void push_call (const supernode *src, + const supernode *dest); + + element_t pop (); int calc_recursion_depth () const; static int cmp (const call_string &a, const call_string &b); - unsigned length () const { return m_return_edges.length (); } - const return_superedge *operator[] (unsigned idx) const + /* Accessors */ + + const supernode *get_callee_node () const; + const supernode *get_caller_node () const; + unsigned length () const { return m_elements.length (); } + element_t operator[] (unsigned idx) const { - return m_return_edges[idx]; + return m_elements[idx]; } void validate () const; private: - auto_vec m_return_edges; + auto_vec m_elements; }; } // namespace ana diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index d73b621..d9f50da 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -350,7 +350,7 @@ program_point::get_function_at_depth (unsigned depth) const if (depth == m_call_string.length ()) return m_function_point.get_function (); else - return m_call_string[depth]->get_caller_function (); + return m_call_string[depth].get_caller_function (); } /* Assert that this object is sane. */ @@ -367,7 +367,7 @@ program_point::validate () const /* The "callee" of the final entry in the callstring should be the function of the m_function_point. */ if (m_call_string.length () > 0) - gcc_assert (m_call_string[m_call_string.length () - 1]->get_callee_function () + gcc_assert (m_call_string[m_call_string.length () - 1].get_callee_function () == get_function ()); } @@ -438,8 +438,10 @@ program_point::on_edge (exploded_graph &eg, logger->log ("rejecting return edge: empty call string"); return false; } - const return_superedge *top_of_stack = m_call_string.pop (); - if (top_of_stack != succ) + const call_string::element_t top_of_stack = m_call_string.pop (); + call_string::element_t current_call_string_element (succ->m_dest, + succ->m_src); + if (top_of_stack != current_call_string_element) { if (logger) logger->log ("rejecting return edge: return to wrong callsite"); -- cgit v1.1 From 4d17ca1bc74109e5cc4ef34890b6293c4bcb1d6a Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 3 Aug 2021 07:49:16 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index bc0f4bd..2da5aae5 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,33 @@ +2021-07-29 Ankur Saini + + * call-string.cc (call_string::element_t::operator==): New operator. + (call_String::element_t::operator!=): New operator. + (call_string::element_t::get_caller_function): New function. + (call_string::element_t::get_callee_function): New function. + (call_string::call_string): Refactor to Initialise m_elements. + (call_string::operator=): Refactor to work with m_elements. + (call_string::operator==): Likewise. + (call_string::to_json): Likewise. + (call_string::hash): Refactor to hash e.m_caller. + (call_string::push_call): Refactor to work with m_elements. + (call_string::push_call): New overload to push call via supernodes. + (call_string::pop): Refactor to work with m_elements. + (call_string::calc_recursion_depth): Likewise. + (call_string::cmp): Likewise. + (call_string::validate): Likewise. + (call_string::operator[]): Likewise. + * call-string.h (class supernode): New forward decl. + (struct call_string::element_t): New struct. + (call_string::call_string): Refactor to initialise m_elements. + (call_string::bool empty_p): Refactor to work with m_elements. + (call_string::get_callee_node): New decl. + (call_string::get_caller_node): New decl. + (m_elements): Replaces m_return_edges. + * program-point.cc (program_point::get_function_at_depth): Refactor to + work with new call-string format. + (program_point::validate): Likewise. + (program_point::on_edge): Likewise. + 2021-07-28 David Malcolm * region-model.cc (region_model::on_call_pre): Treat -- cgit v1.1 From 1a830c0636472e47a7503a5ed879725149e2e728 Mon Sep 17 00:00:00 2001 From: Jakub Jelinek Date: Tue, 3 Aug 2021 12:44:17 +0200 Subject: analyzer: Fix ICE on MD builtin [PR101721] The following testcase ICEs because DECL_FUNCTION_CODE asserts the builtin is BUILT_IN_NORMAL, but it sees a backend (MD) builtin instead. The FE, normal and MD builtin numbers overlap, so one should always check what kind of builtin it is before looking at specific codes. On the other side, region-model.cc has: if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL) && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) which IMO should use DECL_FUNCTION_CODE instead, it checked first it is a normal builtin... 2021-08-03 Jakub Jelinek PR analyzer/101721 * sm-malloc.cc (known_allocator_p): Only check DECL_FUNCTION_CODE on BUILT_IN_NORMAL builtins. * gcc.dg/analyzer/pr101721.c: New test. --- gcc/analyzer/sm-malloc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 4f07d1f..74c6fee 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1543,7 +1543,7 @@ known_allocator_p (const_tree fndecl, const gcall *call) /* ... or it is a builtin allocator that allocates objects freed with __builtin_free. */ - if (fndecl_built_in_p (fndecl)) + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) switch (DECL_FUNCTION_CODE (fndecl)) { case BUILT_IN_MALLOC: -- cgit v1.1 From fa1407c7613214cb4a45734fdb14c4756a83808a Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Wed, 4 Aug 2021 00:16:51 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 2da5aae5..4579796 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,9 @@ +2021-08-03 Jakub Jelinek + + PR analyzer/101721 + * sm-malloc.cc (known_allocator_p): Only check DECL_FUNCTION_CODE on + BUILT_IN_NORMAL builtins. + 2021-07-29 Ankur Saini * call-string.cc (call_string::element_t::operator==): New operator. -- cgit v1.1 From ded2c2c068f6f2825474758cb03a05070a5837e8 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 4 Aug 2021 18:21:21 -0400 Subject: analyzer: initial implementation of asm support [PR101570] gcc/ChangeLog: PR analyzer/101570 * Makefile.in (ANALYZER_OBJS): Add analyzer/region-model-asm.o. gcc/analyzer/ChangeLog: PR analyzer/101570 * analyzer.cc (maybe_reconstruct_from_def_stmt): Add GIMPLE_ASM case. * analyzer.h (class asm_output_svalue): New forward decl. (class reachable_regions): New forward decl. * complexity.cc (complexity::from_vec_svalue): New. * complexity.h (complexity::from_vec_svalue): New decl. * engine.cc (feasibility_state::maybe_update_for_edge): Handle asm stmts by calling on_asm_stmt. * region-model-asm.cc: New file. * region-model-manager.cc (region_model_manager::maybe_fold_asm_output_svalue): New. (region_model_manager::get_or_create_asm_output_svalue): New. (region_model_manager::log_stats): Log m_asm_output_values_map. * region-model.cc (region_model::on_stmt_pre): Handle GIMPLE_ASM. * region-model.h (visitor::visit_asm_output_svalue): New. (region_model_manager::get_or_create_asm_output_svalue): New decl. (region_model_manager::maybe_fold_asm_output_svalue): New decl. (region_model_manager::asm_output_values_map_t): New typedef. (region_model_manager::m_asm_output_values_map): New field. (region_model::on_asm_stmt): New. * store.cc (binding_cluster::on_asm): New. * store.h (binding_cluster::on_asm): New decl. * svalue.cc (svalue::cmp_ptr): Handle SK_ASM_OUTPUT. (asm_output_svalue::dump_to_pp): New. (asm_output_svalue::dump_input): New. (asm_output_svalue::input_idx_to_asm_idx): New. (asm_output_svalue::accept): New. * svalue.h (enum svalue_kind): Add SK_ASM_OUTPUT. (svalue::dyn_cast_asm_output_svalue): New. (class asm_output_svalue): New. (is_a_helper ::test): New. (struct default_hash_traits): New. gcc/testsuite/ChangeLog: PR analyzer/101570 * gcc.dg/analyzer/asm-x86-1.c: New test. * gcc.dg/analyzer/asm-x86-lp64-1.c: New test. * gcc.dg/analyzer/asm-x86-lp64-2.c: New test. * gcc.dg/analyzer/pr101570.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-array_index_mask_nospec.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-cpuid-paravirt-1.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-cpuid-paravirt-2.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-cpuid.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-rdmsr-paravirt.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-rdmsr.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-wfx_get_ps_timeout-full.c: New test. * gcc.dg/analyzer/torture/asm-x86-linux-wfx_get_ps_timeout-reduced.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/analyzer.cc | 1 + gcc/analyzer/analyzer.h | 2 + gcc/analyzer/complexity.cc | 16 ++ gcc/analyzer/complexity.h | 1 + gcc/analyzer/engine.cc | 2 + gcc/analyzer/region-model-asm.cc | 303 +++++++++++++++++++++++++++++++++++ gcc/analyzer/region-model-manager.cc | 48 ++++++ gcc/analyzer/region-model.cc | 5 +- gcc/analyzer/region-model.h | 13 ++ gcc/analyzer/store.cc | 17 ++ gcc/analyzer/store.h | 1 + gcc/analyzer/svalue.cc | 89 ++++++++++ gcc/analyzer/svalue.h | 145 ++++++++++++++++- 13 files changed, 640 insertions(+), 3 deletions(-) create mode 100644 gcc/analyzer/region-model-asm.cc (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index b845b86..5578877 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -131,6 +131,7 @@ maybe_reconstruct_from_def_stmt (tree ssa_name, { default: gcc_unreachable (); + case GIMPLE_ASM: case GIMPLE_NOP: case GIMPLE_PHI: /* Can't handle these. */ diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 8de5d60..896b350 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -53,6 +53,7 @@ class svalue; class widening_svalue; class compound_svalue; class conjured_svalue; + class asm_output_svalue; typedef hash_set svalue_set; class region; class frame_region; @@ -77,6 +78,7 @@ class call_details; struct rejected_constraint; class constraint_manager; class equiv_class; +class reachable_regions; class pending_diagnostic; class state_change_event; diff --git a/gcc/analyzer/complexity.cc b/gcc/analyzer/complexity.cc index ece4272..ae9f982 100644 --- a/gcc/analyzer/complexity.cc +++ b/gcc/analyzer/complexity.cc @@ -90,6 +90,22 @@ complexity::from_pair (const complexity &c1, const complexity &c2) MAX (c1.m_max_depth, c2.m_max_depth) + 1); } +/* Get complexity for a new node that references the svalues in VEC. */ + +complexity +complexity::from_vec_svalue (const vec &vec) +{ + unsigned num_nodes = 0; + unsigned max_depth = 0; + for (auto iter_sval : vec) + { + const complexity &iter_c = iter_sval->get_complexity (); + num_nodes += iter_c.m_num_nodes; + max_depth = MAX (max_depth, iter_c.m_max_depth); + } + return complexity (num_nodes + 1, max_depth + 1); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/complexity.h b/gcc/analyzer/complexity.h index 459987e..85c0372 100644 --- a/gcc/analyzer/complexity.h +++ b/gcc/analyzer/complexity.h @@ -36,6 +36,7 @@ struct complexity complexity (const region *reg); complexity (const svalue *sval); static complexity from_pair (const complexity &c1, const complexity &c); + static complexity from_vec_svalue (const vec &vec); /* The total number of svalues and regions in the tree of this entity, including the entity itself. */ diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index ee625fb..ecd4265 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -3718,6 +3718,8 @@ feasibility_state::maybe_update_for_edge (logger *logger, if (const gassign *assign = dyn_cast (stmt)) m_model.on_assignment (assign, NULL); + else if (const gasm *asm_stmt = dyn_cast (stmt)) + m_model.on_asm_stmt (asm_stmt, NULL); else if (const gcall *call = dyn_cast (stmt)) { bool terminate_path; diff --git a/gcc/analyzer/region-model-asm.cc b/gcc/analyzer/region-model-asm.cc new file mode 100644 index 0000000..3efc3fd --- /dev/null +++ b/gcc/analyzer/region-model-asm.cc @@ -0,0 +1,303 @@ +/* Handling inline asm in the analyzer. + Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "diagnostic-core.h" +#include "pretty-print.h" +#include "tristate.h" +#include "selftest.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "options.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/region-model-reachability.h" +#include "stmt.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* Minimal asm support for the analyzer. + + The objective of this code is to: + - minimize false positives from the analyzer on the Linux kernel + (which makes heavy use of inline asm), whilst + - avoiding having to "teach" the compiler anything about specific strings + in asm statements. + + Specifically, we want to: + + (a) mark asm outputs and certain other regions as having been written to, + to avoid false postives from -Wanalyzer-use-of-uninitialized-value. + + (b) identify some of these stmts as "deterministic" so that we can + write consistent outputs given consistent inputs, so that we can + avoid false positives for paths in which an asm is invoked twice + with the same inputs and is expected to emit the same output. + + This file implements heuristics for achieving the above. */ + +/* Determine if ASM_STMT is deterministic, in the sense of (b) above. + + Consider this x86 function taken from the Linux kernel + (arch/x86/include/asm/barrier.h): + + static inline unsigned long array_index_mask_nospec(unsigned long index, + unsigned long size) + { + unsigned long mask; + + asm volatile ("cmp %1,%2; sbb %0,%0;" + :"=r" (mask) + :"g"(size),"r" (index) + :"cc"); + return mask; + } + + The above is a mitigation for Spectre-variant-1 attacks, for clamping + an array access to within the range of [0, size] if the CPU speculates + past the array bounds. + + However, it is ultimately used to implement wdev_to_wvif: + + static inline struct wfx_vif * + wdev_to_wvif(struct wfx_dev *wdev, int vif_id) + { + vif_id = array_index_nospec(vif_id, ARRAY_SIZE(wdev->vif)); + if (!wdev->vif[vif_id]) { + return NULL; + } + return (struct wfx_vif *)wdev->vif[vif_id]->drv_priv; + } + + which is used by: + + if (wdev_to_wvif(wvif->wdev, 1)) + return wdev_to_wvif(wvif->wdev, 1)->vif; + + The code has been written to assume that wdev_to_wvif is deterministic, + and won't change from returning non-NULL at the "if" clause to + returning NULL at the "->vif" dereference. + + By treating the above specific "asm volatile" as deterministic we avoid + a false positive from -Wanalyzer-null-dereference. */ + +static bool +deterministic_p (const gasm *asm_stmt) +{ + /* Assume something volatile with no inputs is querying + changeable state e.g. rdtsc. */ + if (gimple_asm_ninputs (asm_stmt) == 0 + && gimple_asm_volatile_p (asm_stmt)) + return false; + + /* Otherwise assume it's purely a function of its inputs. */ + return true; +} + +/* Update this model for the asm STMT, using CTXT to report any + diagnostics. + + Compare with cfgexpand.c: expand_asm_stmt. */ + +void +region_model::on_asm_stmt (const gasm *stmt, region_model_context *ctxt) +{ + logger *logger = ctxt ? ctxt->get_logger () : NULL; + LOG_SCOPE (logger); + + const unsigned noutputs = gimple_asm_noutputs (stmt); + const unsigned ninputs = gimple_asm_ninputs (stmt); + + auto_vec output_tvec; + auto_vec input_tvec; + auto_vec constraints; + + /* Copy the gimple vectors into new vectors that we can manipulate. */ + output_tvec.safe_grow (noutputs, true); + input_tvec.safe_grow (ninputs, true); + constraints.safe_grow (noutputs + ninputs, true); + + for (unsigned i = 0; i < noutputs; ++i) + { + tree t = gimple_asm_output_op (stmt, i); + output_tvec[i] = TREE_VALUE (t); + constraints[i] = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); + } + for (unsigned i = 0; i < ninputs; i++) + { + tree t = gimple_asm_input_op (stmt, i); + input_tvec[i] = TREE_VALUE (t); + constraints[i + noutputs] + = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t))); + } + + /* Determine which regions are reachable from the inputs + to this stmt. */ + reachable_regions reachable_regs (this); + + int num_errors = 0; + + auto_vec output_regions (noutputs); + for (unsigned i = 0; i < noutputs; ++i) + { + tree val = output_tvec[i]; + const char *constraint; + bool is_inout; + bool allows_reg; + bool allows_mem; + + const region *dst_reg = get_lvalue (val, ctxt); + output_regions.quick_push (dst_reg); + reachable_regs.add (dst_reg, true); + + /* Try to parse the output constraint. If that fails, there's + no point in going further. */ + constraint = constraints[i]; + if (!parse_output_constraint (&constraint, i, ninputs, noutputs, + &allows_mem, &allows_reg, &is_inout)) + { + if (logger) + logger->log ("error parsing constraint for output %i: %qs", + i, constraint); + num_errors++; + continue; + } + + if (logger) + { + logger->log ("output %i: %qs %qE" + " is_inout: %i allows_reg: %i allows_mem: %i", + i, constraint, val, + (int)is_inout, (int)allows_reg, (int)allows_mem); + logger->start_log_line (); + logger->log_partial (" region: "); + dst_reg->dump_to_pp (logger->get_printer (), true); + logger->end_log_line (); + } + + } + + /* Ideally should combine with inout_svals to determine the + "effective inputs" and use this for the asm_output_svalue. */ + + auto_vec input_svals (ninputs); + for (unsigned i = 0; i < ninputs; i++) + { + tree val = input_tvec[i]; + const char *constraint = constraints[i + noutputs]; + bool allows_reg, allows_mem; + if (! parse_input_constraint (&constraint, i, ninputs, noutputs, 0, + constraints.address (), + &allows_mem, &allows_reg)) + { + if (logger) + logger->log ("error parsing constraint for input %i: %qs", + i, constraint); + num_errors++; + continue; + } + + tree src_expr = input_tvec[i]; + const svalue *src_sval = get_rvalue (src_expr, ctxt); + check_for_poison (src_sval, src_expr, ctxt); + input_svals.quick_push (src_sval); + reachable_regs.handle_sval (src_sval); + + if (logger) + { + logger->log ("input %i: %qs %qE" + " allows_reg: %i allows_mem: %i", + i, constraint, val, + (int)allows_reg, (int)allows_mem); + logger->start_log_line (); + logger->log_partial (" sval: "); + src_sval->dump_to_pp (logger->get_printer (), true); + logger->end_log_line (); + } + } + + if (num_errors > 0) + gcc_unreachable (); + + if (logger) + { + logger->log ("reachability: "); + reachable_regs.dump_to_pp (logger->get_printer ()); + logger->end_log_line (); + } + + /* Given the regions that were reachable from the inputs we + want to clobber them. + This is similar to region_model::handle_unrecognized_call, + but the unknown call policies seems too aggressive (e.g. purging state + from anything that's ever escaped). Instead, clobber any clusters + that were reachable in *this* asm stmt, rather than those that + escaped, and we don't treat the values as having escaped. + We also assume that asm stmts don't affect sm-state. */ + for (auto iter = reachable_regs.begin_mutable_base_regs (); + iter != reachable_regs.end_mutable_base_regs (); ++iter) + { + const region *base_reg = *iter; + if (base_reg->symbolic_for_unknown_ptr_p ()) + continue; + + binding_cluster *cluster = m_store.get_or_create_cluster (base_reg); + cluster->on_asm (stmt, m_mgr->get_store_manager ()); + } + + /* Update the outputs. */ + for (unsigned output_idx = 0; output_idx < noutputs; output_idx++) + { + tree dst_expr = output_tvec[output_idx]; + const region *dst_reg = output_regions[output_idx]; + + const svalue *sval; + if (deterministic_p (stmt) + && input_svals.length () <= asm_output_svalue::MAX_INPUTS) + sval = m_mgr->get_or_create_asm_output_svalue (TREE_TYPE (dst_expr), + stmt, + output_idx, + input_svals); + else + { + sval = m_mgr->get_or_create_conjured_svalue (TREE_TYPE (dst_expr), + stmt, + dst_reg); + purge_state_involving (sval, ctxt); + } + set_value (dst_reg, sval, ctxt); + } +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 14c57d8..9e4644f 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -1087,6 +1087,51 @@ region_model_manager::get_or_create_conjured_svalue (tree type, return conjured_sval; } +/* Subroutine of region_model_manager::get_or_create_asm_output_svalue. + Return a folded svalue, or NULL. */ + +const svalue * +region_model_manager:: +maybe_fold_asm_output_svalue (tree type, + const vec &inputs) +{ + /* Unknown inputs should lead to unknown results. */ + for (const auto &iter : inputs) + if (iter->get_kind () == SK_UNKNOWN) + return get_or_create_unknown_svalue (type); + + return NULL; +} + +/* Return the svalue * of type TYPE for OUTPUT_IDX of the deterministic + asm stmt ASM_STMT, given INPUTS as inputs. */ + +const svalue * +region_model_manager:: +get_or_create_asm_output_svalue (tree type, + const gasm *asm_stmt, + unsigned output_idx, + const vec &inputs) +{ + gcc_assert (inputs.length () <= asm_output_svalue::MAX_INPUTS); + + if (const svalue *folded + = maybe_fold_asm_output_svalue (type, inputs)) + return folded; + + const char *asm_string = gimple_asm_string (asm_stmt); + const unsigned noutputs = gimple_asm_noutputs (asm_stmt); + + asm_output_svalue::key_t key (type, asm_string, output_idx, inputs); + if (asm_output_svalue **slot = m_asm_output_values_map.get (key)) + return *slot; + asm_output_svalue *asm_output_sval + = new asm_output_svalue (type, asm_string, output_idx, noutputs, inputs); + RETURN_UNKNOWN_IF_TOO_COMPLEX (asm_output_sval); + m_asm_output_values_map.put (key, asm_output_sval); + return asm_output_sval; +} + /* Given STRING_CST, a STRING_CST and BYTE_OFFSET_CST a constant, attempt to get the character at that offset, returning either the svalue for the character constant, or NULL if unsuccessful. */ @@ -1505,6 +1550,9 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const log_uniq_map (logger, show_objs, "widening_svalue", m_widening_values_map); log_uniq_map (logger, show_objs, "compound_svalue", m_compound_values_map); log_uniq_map (logger, show_objs, "conjured_svalue", m_conjured_values_map); + log_uniq_map (logger, show_objs, "asm_output_svalue", + m_asm_output_values_map); + logger->log ("max accepted svalue num_nodes: %i", m_max_complexity.m_num_nodes); logger->log ("max accepted svalue max_depth: %i", diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 1bc411b..58da7e3 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -980,7 +980,10 @@ region_model::on_stmt_pre (const gimple *stmt, break; case GIMPLE_ASM: - /* No-op for now. */ + { + const gasm *asm_stmt = as_a (stmt); + on_asm_stmt (asm_stmt, ctxt); + } break; case GIMPLE_CALL: diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index d07ce9c..30f02a0 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -221,6 +221,7 @@ public: virtual void visit_widening_svalue (const widening_svalue *) {} virtual void visit_compound_svalue (const compound_svalue *) {} virtual void visit_conjured_svalue (const conjured_svalue *) {} + virtual void visit_asm_output_svalue (const asm_output_svalue *) {} virtual void visit_region (const region *) {} }; @@ -274,6 +275,11 @@ public: const binding_map &map); const svalue *get_or_create_conjured_svalue (tree type, const gimple *stmt, const region *id_reg); + const svalue * + get_or_create_asm_output_svalue (tree type, + const gasm *asm_stmt, + unsigned output_idx, + const vec &inputs); const svalue *maybe_get_char_from_string_cst (tree string_cst, tree byte_offset_cst); @@ -346,6 +352,8 @@ private: const svalue *maybe_undo_optimize_bit_field_compare (tree type, const compound_svalue *compound_sval, tree cst, const svalue *arg1); + const svalue *maybe_fold_asm_output_svalue (tree type, + const vec &inputs); unsigned m_next_region_id; root_region m_root_region; @@ -410,6 +418,10 @@ private: conjured_svalue *> conjured_values_map_t; conjured_values_map_t m_conjured_values_map; + typedef hash_map asm_output_values_map_t; + asm_output_values_map_t m_asm_output_values_map; + bool m_check_complexity; /* Maximum complexity of svalues that weren't rejected. */ @@ -537,6 +549,7 @@ class region_model void on_assignment (const gassign *stmt, region_model_context *ctxt); const svalue *get_gassign_result (const gassign *assign, region_model_context *ctxt); + void on_asm_stmt (const gasm *asm_stmt, region_model_context *ctxt); bool on_call_pre (const gcall *stmt, region_model_context *ctxt, bool *out_terminate_path); void on_call_post (const gcall *stmt, diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index 8ee414d..eac1295 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -1796,6 +1796,23 @@ binding_cluster::on_unknown_fncall (const gcall *call, } } +/* Mark this cluster as having been clobbered by STMT. */ + +void +binding_cluster::on_asm (const gasm *stmt, + store_manager *mgr) +{ + m_map.empty (); + + /* Bind it to a new "conjured" value using CALL. */ + const svalue *sval + = mgr->get_svalue_manager ()->get_or_create_conjured_svalue + (m_base_region->get_type (), stmt, m_base_region); + bind (mgr, m_base_region, sval); + + m_touched = true; +} + /* Return true if this binding_cluster has no information i.e. if there are no bindings, and it hasn't been marked as having escaped, or touched symbolically. */ diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index bc58694..b75691e 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -603,6 +603,7 @@ public: void mark_as_escaped (); void on_unknown_fncall (const gcall *call, store_manager *mgr); + void on_asm (const gasm *stmt, store_manager *mgr); bool escaped_p () const { return m_escaped; } bool touched_p () const { return m_touched; } diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index a1e6f50..6913161 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -508,6 +508,29 @@ svalue::cmp_ptr (const svalue *sval1, const svalue *sval2) conjured_sval2->get_id_region ()); } break; + case SK_ASM_OUTPUT: + { + const asm_output_svalue *asm_output_sval1 + = (const asm_output_svalue *)sval1; + const asm_output_svalue *asm_output_sval2 + = (const asm_output_svalue *)sval2; + if (int asm_string_cmp = strcmp (asm_output_sval1->get_asm_string (), + asm_output_sval2->get_asm_string ())) + return asm_string_cmp; + if (int output_idx_cmp = ((int)asm_output_sval1->get_output_idx () + - (int)asm_output_sval2->get_output_idx ())) + return output_idx_cmp; + if (int cmp = ((int)asm_output_sval1->get_num_inputs () + - (int)asm_output_sval2->get_num_inputs ())) + return cmp; + for (unsigned i = 0; i < asm_output_sval1->get_num_inputs (); i++) + if (int input_cmp + = svalue::cmp_ptr (asm_output_sval1->get_input (i), + asm_output_sval2->get_input (i))) + return input_cmp; + return 0; + } + break; } } @@ -1794,6 +1817,72 @@ conjured_svalue::accept (visitor *v) const m_id_reg->accept (v); } +/* class asm_output_svalue : public svalue. */ + +/* Implementation of svalue::dump_to_pp vfunc for asm_output_svalue. */ + +void +asm_output_svalue::dump_to_pp (pretty_printer *pp, bool simple) const +{ + if (simple) + { + pp_printf (pp, "ASM_OUTPUT(%qs, %%%i, {", + get_asm_string (), + get_output_idx ()); + for (unsigned i = 0; i < m_num_inputs; i++) + { + if (i > 0) + pp_string (pp, ", "); + dump_input (pp, 0, m_input_arr[i], simple); + } + pp_string (pp, "})"); + } + else + { + pp_printf (pp, "asm_output_svalue (%qs, %%%i, {", + get_asm_string (), + get_output_idx ()); + for (unsigned i = 0; i < m_num_inputs; i++) + { + if (i > 0) + pp_string (pp, ", "); + dump_input (pp, 0, m_input_arr[i], simple); + } + pp_string (pp, "})"); + } +} + +/* Subroutine of asm_output_svalue::dump_to_pp. */ + +void +asm_output_svalue::dump_input (pretty_printer *pp, + unsigned input_idx, + const svalue *sval, + bool simple) const +{ + pp_printf (pp, "%%%i: ", input_idx_to_asm_idx (input_idx)); + sval->dump_to_pp (pp, simple); +} + +/* Convert INPUT_IDX from an index into the array of inputs + into the index of all operands for the asm stmt. */ + +unsigned +asm_output_svalue::input_idx_to_asm_idx (unsigned input_idx) const +{ + return input_idx + m_num_outputs; +} + +/* Implementation of svalue::accept vfunc for asm_output_svalue. */ + +void +asm_output_svalue::accept (visitor *v) const +{ + v->visit_asm_output_svalue (this); + for (unsigned i = 0; i < m_num_inputs; i++) + m_input_arr[i]->accept (v); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index debe439..63f7d15 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -47,7 +47,8 @@ enum svalue_kind SK_PLACEHOLDER, SK_WIDENING, SK_COMPOUND, - SK_CONJURED + SK_CONJURED, + SK_ASM_OUTPUT }; /* svalue and its subclasses. @@ -74,7 +75,9 @@ enum svalue_kind widening_svalue (SK_WIDENING): a merger of two svalues (possibly in an iteration). compound_svalue (SK_COMPOUND): a mapping of bit-ranges to svalues - conjured_svalue (SK_CONJURED): a value arising from a stmt. */ + conjured_svalue (SK_CONJURED): a value arising from a stmt + asm_output_svalue (SK_ASM_OUTPUT): an output from a deterministic + asm stmt. */ /* An abstract base class representing a value held by a region of memory. */ @@ -124,6 +127,8 @@ public: dyn_cast_compound_svalue () const { return NULL; } virtual const conjured_svalue * dyn_cast_conjured_svalue () const { return NULL; } + virtual const asm_output_svalue * + dyn_cast_asm_output_svalue () const { return NULL; } tree maybe_get_constant () const; const region *maybe_get_region () const; @@ -1394,4 +1399,140 @@ template <> struct default_hash_traits static const bool empty_zero_p = true; }; +namespace ana { + +/* An output from a deterministic asm stmt, where we want to identify a + particular unknown value, rather than resorting to the unknown_value + singleton. + + Comparisons of variables that share the same asm_output_svalue are known + to be equal, even if we don't know what the value is. */ + +class asm_output_svalue : public svalue +{ +public: + /* Imposing an upper limit and using a (small) array allows key_t + to avoid memory management. */ + static const unsigned MAX_INPUTS = 2; + + /* A support class for uniquifying instances of asm_output_svalue. */ + struct key_t + { + key_t (tree type, + const char *asm_string, + unsigned output_idx, + const vec &inputs) + : m_type (type), m_asm_string (asm_string), m_output_idx (output_idx), + m_num_inputs (inputs.length ()) + { + gcc_assert (inputs.length () <= MAX_INPUTS); + for (unsigned i = 0; i < m_num_inputs; i++) + m_input_arr[i] = inputs[i]; + } + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_type); + /* We don't bother hashing m_asm_str. */ + hstate.add_int (m_output_idx); + for (unsigned i = 0; i < m_num_inputs; i++) + hstate.add_ptr (m_input_arr[i]); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + if (!(m_type == other.m_type + && 0 == (strcmp (m_asm_string, other.m_asm_string)) + && m_output_idx == other.m_output_idx + && m_num_inputs == other.m_num_inputs)) + return false; + for (unsigned i = 0; i < m_num_inputs; i++) + if (m_input_arr[i] != other.m_input_arr[i]) + return false; + return true; + } + + /* Use m_asm_string to mark empty/deleted, as m_type can be NULL for + legitimate instances. */ + void mark_deleted () { m_asm_string = reinterpret_cast (1); } + void mark_empty () { m_asm_string = NULL; } + bool is_deleted () const + { + return m_asm_string == reinterpret_cast (1); + } + bool is_empty () const { return m_asm_string == NULL; } + + tree m_type; + const char *m_asm_string; + unsigned m_output_idx; + unsigned m_num_inputs; + const svalue *m_input_arr[MAX_INPUTS]; + }; + + asm_output_svalue (tree type, + const char *asm_string, + unsigned output_idx, + unsigned num_outputs, + const vec &inputs) + : svalue (complexity::from_vec_svalue (inputs), type), + m_asm_string (asm_string), + m_output_idx (output_idx), + m_num_outputs (num_outputs), + m_num_inputs (inputs.length ()) + { + gcc_assert (inputs.length () <= MAX_INPUTS); + for (unsigned i = 0; i < m_num_inputs; i++) + m_input_arr[i] = inputs[i]; + } + + enum svalue_kind get_kind () const FINAL OVERRIDE { return SK_ASM_OUTPUT; } + const asm_output_svalue * + dyn_cast_asm_output_svalue () const FINAL OVERRIDE + { + return this; + } + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + void accept (visitor *v) const FINAL OVERRIDE; + + const char *get_asm_string () const { return m_asm_string; } + unsigned get_output_idx () const { return m_output_idx; } + unsigned get_num_inputs () const { return m_num_inputs; } + const svalue *get_input (unsigned idx) const { return m_input_arr[idx]; } + + private: + void dump_input (pretty_printer *pp, + unsigned input_idx, + const svalue *sval, + bool simple) const; + unsigned input_idx_to_asm_idx (unsigned input_idx) const; + + const char *m_asm_string; + unsigned m_output_idx; + + /* We capture this so that we can offset the input indices + to match the %0, %1, %2 in the asm_string when dumping. */ + unsigned m_num_outputs; + + unsigned m_num_inputs; + const svalue *m_input_arr[MAX_INPUTS]; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::test (const svalue *sval) +{ + return sval->get_kind () == SK_ASM_OUTPUT; +} + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = true; +}; #endif /* GCC_ANALYZER_SVALUE_H */ -- cgit v1.1 From 2697f8324fbb09b0d92036ba6a6b8a2b8d256b23 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 5 Aug 2021 00:17:03 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 4579796..43e3c63 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,39 @@ +2021-08-04 David Malcolm + + PR analyzer/101570 + * analyzer.cc (maybe_reconstruct_from_def_stmt): Add GIMPLE_ASM + case. + * analyzer.h (class asm_output_svalue): New forward decl. + (class reachable_regions): New forward decl. + * complexity.cc (complexity::from_vec_svalue): New. + * complexity.h (complexity::from_vec_svalue): New decl. + * engine.cc (feasibility_state::maybe_update_for_edge): Handle + asm stmts by calling on_asm_stmt. + * region-model-asm.cc: New file. + * region-model-manager.cc + (region_model_manager::maybe_fold_asm_output_svalue): New. + (region_model_manager::get_or_create_asm_output_svalue): New. + (region_model_manager::log_stats): Log m_asm_output_values_map. + * region-model.cc (region_model::on_stmt_pre): Handle GIMPLE_ASM. + * region-model.h (visitor::visit_asm_output_svalue): New. + (region_model_manager::get_or_create_asm_output_svalue): New decl. + (region_model_manager::maybe_fold_asm_output_svalue): New decl. + (region_model_manager::asm_output_values_map_t): New typedef. + (region_model_manager::m_asm_output_values_map): New field. + (region_model::on_asm_stmt): New. + * store.cc (binding_cluster::on_asm): New. + * store.h (binding_cluster::on_asm): New decl. + * svalue.cc (svalue::cmp_ptr): Handle SK_ASM_OUTPUT. + (asm_output_svalue::dump_to_pp): New. + (asm_output_svalue::dump_input): New. + (asm_output_svalue::input_idx_to_asm_idx): New. + (asm_output_svalue::accept): New. + * svalue.h (enum svalue_kind): Add SK_ASM_OUTPUT. + (svalue::dyn_cast_asm_output_svalue): New. + (class asm_output_svalue): New. + (is_a_helper ::test): New. + (struct default_hash_traits): New. + 2021-08-03 Jakub Jelinek PR analyzer/101721 -- cgit v1.1 From aef703cf982072427e74034f4c460a11c5e04b8e Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Thu, 29 Jul 2021 15:48:07 +0530 Subject: analyzer: detect and analyze calls via function pointer 2021-07-29 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/100546 * analysis-plan.cc (analysis_plan::use_summary_p): Don't use call summaries if there is no callgraph edge * checker-path.cc (call_event::call_event): Handle calls events that are not represented by a supergraph call edge (return_event::return_event): Likewise. (call_event::get_desc): Work with new call_event structure. (return_event::get_desc): Likeise. * checker-path.h (call_event::m_src_snode): New field. (call_event::m_dest_snode): New field. (return_event::m_src_snode): New field. (return_event::m_dest_snode): New field. * diagnostic-manager.cc (diagnostic_manager::prune_for_sm_diagnostic): Refactor to work with edges without callgraph edge. (diagnostic_manager::prune_for_sm_diagnostic): Likewise. * engine.cc (dynamic_call_info_t::update_model): New function. (dynamic_call_info_t::add_events_to_path): New function. (exploded_graph::create_dynamic_call): New function. (exploded_graph::process_node): Work with dynamically discovered calls. * exploded-graph.h (class dynamic_call_info_t): New class. (exploded_graph::create_dynamic_call): New decl. * program-point.cc (program_point::push_to_call_stack): New function. (program_point::pop_from_call_stack): New function. * program-point.h (program_point::push_to_call_stack): New decl. (program_point::pop_from_call_stack): New decl. * program-state.cc (program_state::push_call): New function. (program_state::returning_call): New function. * program-state.h (program_state::push_call): New decl. (program_state::returning_call): New decl. * region-model.cc (region_model::update_for_gcall) New function. (region_model::update_for_return_gcall): New function. (egion_model::update_for_call_superedge): Get the underlying gcall and update for gcall. (region_model::update_for_return_superedge): Likewise. * region-model.h (region_model::update_for_gcall): New decl. (region_model::update_for_return_gcall): New decl. * state-purge.cc (state_purge_per_ssa_name::process_point): Update to work with calls without underlying cgraph edge. * supergraph.cc (supergraph::supergraph) Split snodes at every callsite. * supergraph.h (supernode::get_returning_call) New accessor. gcc/testsuite/ChangeLog: PR analyzer/100546 * gcc.dg/analyzer/function-ptr-4.c: New test. * gcc.dg/analyzer/pr100546.c: New test. --- gcc/analyzer/analysis-plan.cc | 4 + gcc/analyzer/checker-path.cc | 28 ++++-- gcc/analyzer/checker-path.h | 6 ++ gcc/analyzer/diagnostic-manager.cc | 23 ++--- gcc/analyzer/engine.cc | 183 +++++++++++++++++++++++++++++++++++++ gcc/analyzer/exploded-graph.h | 39 ++++++++ gcc/analyzer/program-point.cc | 18 ++++ gcc/analyzer/program-point.h | 3 +- gcc/analyzer/program-state.cc | 44 +++++++++ gcc/analyzer/program-state.h | 11 +++ gcc/analyzer/region-model.cc | 44 +++++++-- gcc/analyzer/region-model.h | 6 ++ gcc/analyzer/state-purge.cc | 35 ++++--- gcc/analyzer/supergraph.cc | 43 ++++++++- gcc/analyzer/supergraph.h | 7 +- 15 files changed, 440 insertions(+), 54 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc index 7dfc48e..57a6dcb 100644 --- a/gcc/analyzer/analysis-plan.cc +++ b/gcc/analyzer/analysis-plan.cc @@ -109,6 +109,10 @@ analysis_plan::use_summary_p (const cgraph_edge *edge) const if (!flag_analyzer_call_summaries) return false; + /* Don't use call summaries if there is no callgraph edge */ + if (!edge || !edge->callee) + return false; + /* TODO: don't count callsites each time. */ int num_call_sites = 0; const cgraph_node *callee = edge->callee; diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index e10c8e2..e132f00 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -614,7 +614,11 @@ call_event::call_event (const exploded_edge &eedge, location_t loc, tree fndecl, int depth) : superedge_event (EK_CALL_EDGE, eedge, loc, fndecl, depth) { - gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL); + if (eedge.m_sedge) + gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL); + + m_src_snode = eedge.m_src->get_supernode (); + m_dest_snode = eedge.m_dest->get_supernode (); } /* Implementation of diagnostic_event::get_desc vfunc for @@ -638,8 +642,8 @@ call_event::get_desc (bool can_colorize) const label_text custom_desc = m_pending_diagnostic->describe_call_with_state (evdesc::call_with_state (can_colorize, - m_sedge->m_src->m_fun->decl, - m_sedge->m_dest->m_fun->decl, + m_src_snode->m_fun->decl, + m_dest_snode->m_fun->decl, var, m_critical_state)); if (custom_desc.m_buffer) @@ -648,8 +652,8 @@ call_event::get_desc (bool can_colorize) const return make_label_text (can_colorize, "calling %qE from %qE", - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl); + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl); } /* Override of checker_event::is_call_p for calls. */ @@ -668,7 +672,11 @@ return_event::return_event (const exploded_edge &eedge, location_t loc, tree fndecl, int depth) : superedge_event (EK_RETURN_EDGE, eedge, loc, fndecl, depth) { - gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN); + if (eedge.m_sedge) + gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN); + + m_src_snode = eedge.m_src->get_supernode (); + m_dest_snode = eedge.m_dest->get_supernode (); } /* Implementation of diagnostic_event::get_desc vfunc for @@ -694,16 +702,16 @@ return_event::get_desc (bool can_colorize) const label_text custom_desc = m_pending_diagnostic->describe_return_of_state (evdesc::return_of_state (can_colorize, - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl, + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl, m_critical_state)); if (custom_desc.m_buffer) return custom_desc; } return make_label_text (can_colorize, "returning to %qE from %qE", - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl); + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl); } /* Override of checker_event::is_return_p for returns. */ diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 1843c4b..27634c2 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -338,6 +338,9 @@ public: label_text get_desc (bool can_colorize) const FINAL OVERRIDE; bool is_call_p () const FINAL OVERRIDE; + + const supernode *m_src_snode; + const supernode *m_dest_snode; }; /* A concrete event subclass for an interprocedural return. */ @@ -351,6 +354,9 @@ public: label_text get_desc (bool can_colorize) const FINAL OVERRIDE; bool is_return_p () const FINAL OVERRIDE; + + const supernode *m_src_snode; + const supernode *m_dest_snode; }; /* A concrete event subclass for the start of a consolidated run of CFG diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index ef3df32..06e7510 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2093,18 +2093,13 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, case EK_CALL_EDGE: { call_event *event = (call_event *)base_event; - const callgraph_superedge& cg_superedge - = event->get_callgraph_superedge (); const region_model *callee_model = event->m_eedge.m_dest->get_state ().m_region_model; + const region_model *caller_model + = event->m_eedge.m_src->get_state ().m_region_model; tree callee_var = callee_model->get_representative_tree (sval); - /* We could just use caller_model->get_representative_tree (sval); - to get the caller_var, but for now use - map_expr_from_callee_to_caller so as to only record critical - state for parms and the like. */ callsite_expr expr; - tree caller_var - = cg_superedge.map_expr_from_callee_to_caller (callee_var, &expr); + tree caller_var = caller_model->get_representative_tree (sval); if (caller_var) { if (get_logger ()) @@ -2126,15 +2121,11 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, if (sval) { return_event *event = (return_event *)base_event; - const callgraph_superedge& cg_superedge - = event->get_callgraph_superedge (); - const region_model *caller_model - = event->m_eedge.m_dest->get_state ().m_region_model; - tree caller_var = caller_model->get_representative_tree (sval); callsite_expr expr; - tree callee_var - = cg_superedge.map_expr_from_caller_to_callee (caller_var, - &expr); + + const region_model *callee_model + = event->m_eedge.m_src->get_state ().m_region_model; + tree callee_var = callee_model->get_representative_tree (sval); if (callee_var) { if (get_logger ()) diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index ecd4265..461de9c 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1627,6 +1627,50 @@ exploded_node::dump_succs_and_preds (FILE *outf) const } } +/* class dynamic_call_info_t : public exploded_edge::custom_info_t. */ + +/* Implementation of exploded_edge::custom_info_t::update_model vfunc + for dynamic_call_info_t. + + Update state for the dynamically discorverd calls */ + +void +dynamic_call_info_t::update_model (region_model *model, + const exploded_edge &eedge) +{ + const program_state &dest_state = eedge.m_dest->get_state (); + *model = *dest_state.m_region_model; +} + +/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc + for dynamic_call_info_t. */ + +void +dynamic_call_info_t::add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) +{ + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + const int src_stack_depth = src_point.get_stack_depth (); + const exploded_node *dest_node = eedge.m_dest; + const program_point &dest_point = dest_node->get_point (); + const int dest_stack_depth = dest_point.get_stack_depth (); + + if (m_is_returning_call) + emission_path->add_event (new return_event (eedge, (m_dynamic_call + ? m_dynamic_call->location + : UNKNOWN_LOCATION), + dest_point.get_fndecl (), + dest_stack_depth)); + else + emission_path->add_event (new call_event (eedge, (m_dynamic_call + ? m_dynamic_call->location + : UNKNOWN_LOCATION), + src_point.get_fndecl (), + src_stack_depth)); + +} + /* class rewind_info_t : public exploded_edge::custom_info_t. */ /* Implementation of exploded_edge::custom_info_t::update_model vfunc @@ -2980,6 +3024,61 @@ state_change_requires_new_enode_p (const program_state &old_state, return false; } +/* Create enodes and eedges for the function calls that doesn't have an + underlying call superedge. + + Such case occurs when GCC's middle end didn't know which function to + call but the analyzer does (with the help of current state). + + Some example such calls are dynamically dispatched calls to virtual + functions or calls that happen via function pointer. */ + +void +exploded_graph::create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger) +{ + LOG_FUNC (logger); + + const program_point *this_point = &node->get_point (); + function *fun = DECL_STRUCT_FUNCTION (fn_decl); + if (fun) + { + const supergraph &sg = this->get_supergraph (); + supernode * sn_entry = sg.get_node_for_function_entry (fun); + supernode * sn_exit = sg.get_node_for_function_exit (fun); + + program_point new_point + = program_point::before_supernode (sn_entry, + NULL, + this_point->get_call_string ()); + + new_point.push_to_call_stack (sn_exit, + next_point.get_supernode()); + next_state.push_call (*this, node, call, uncertainty); + + if (next_state.m_valid) + { + if (logger) + logger->log ("Discovered call to %s [SN: %i -> SN: %i]", + function_name(fun), + this_point->get_supernode ()->m_index, + sn_entry->m_index); + + exploded_node *enode = get_or_create_node (new_point, + next_state, + node); + if (enode) + add_edge (node,enode, NULL, + new dynamic_call_info_t (call)); + } + } +} + /* The core of exploded_graph::process_worklist (the main analysis loop), handling one node in the worklist. @@ -3174,10 +3273,13 @@ exploded_graph::process_node (exploded_node *node) break; case PK_AFTER_SUPERNODE: { + bool found_a_superedge = false; + bool is_an_exit_block = false; /* If this is an EXIT BB, detect leaks, and potentially create a function summary. */ if (point.get_supernode ()->return_p ()) { + is_an_exit_block = true; node->detect_leaks (*this); if (flag_analyzer_call_summaries && point.get_call_string ().empty_p ()) @@ -3205,6 +3307,7 @@ exploded_graph::process_node (exploded_node *node) superedge *succ; FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ) { + found_a_superedge = true; if (logger) logger->log ("considering SN: %i -> SN: %i", succ->m_src->m_index, succ->m_dest->m_index); @@ -3214,6 +3317,54 @@ exploded_graph::process_node (exploded_node *node) point.get_call_string ()); program_state next_state (state); uncertainty_t uncertainty; + + /* Make use the current state and try to discover and analyse + indirect function calls (a call that doesn't have an underlying + cgraph edge representing call). + + Some examples of such calls are virtual function calls + and calls that happen via a function pointer. */ + if (succ->m_kind == SUPEREDGE_INTRAPROCEDURAL_CALL + && !(succ->get_any_callgraph_edge ())) + { + const gcall *call + = point.get_supernode ()->get_final_call (); + + impl_region_model_context ctxt (*this, + node, + &state, + &next_state, + &uncertainty, + point.get_stmt()); + + region_model *model = state.m_region_model; + + if (tree fn_decl = model->get_fndecl_for_call(call,&ctxt)) + create_dynamic_call (call, + fn_decl, + node, + next_state, + next_point, + &uncertainty, + logger); + else + { + /* An unknown function was called at this point, in such + case, don't terminate the analysis of the current + function. + + The analyzer handles calls to unknown functions while + analysing the stmt itself, so the the function call + must have been handled by the anlyzer till now. */ + exploded_node *next + = get_or_create_node (next_point, + next_state, + node); + if (next) + add_edge (node, next, succ); + } + } + if (!node->on_edge (*this, succ, &next_point, &next_state, &uncertainty)) { @@ -3227,6 +3378,38 @@ exploded_graph::process_node (exploded_node *node) if (next) add_edge (node, next, succ); } + + /* Return from the calls which doesn't have a return superedge. + Such case occurs when GCC's middle end didn't knew which function to + call but analyzer did. */ + if((is_an_exit_block && !found_a_superedge) + && (!point.get_call_string ().empty_p ())) + { + const call_string cs = point.get_call_string (); + program_point next_point + = program_point::before_supernode (cs.get_caller_node (), + NULL, + cs); + program_state next_state (state); + uncertainty_t uncertainty; + + const gcall *call + = next_point.get_supernode ()->get_returning_call (); + + if(call) + next_state.returning_call (*this, node, call, &uncertainty); + + if (next_state.m_valid) + { + next_point.pop_from_call_stack (); + exploded_node *enode = get_or_create_node (next_point, + next_state, + node); + if (enode) + add_edge (node, enode, NULL, + new dynamic_call_info_t (call, true)); + } + } } break; } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 8f48d8a..192a4b3 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -362,6 +362,37 @@ private: DISABLE_COPY_AND_ASSIGN (exploded_edge); }; +/* Extra data for an exploded_edge that represents dynamic call info ( calls + that doesn't have an underlying superedge representing the call ). */ + +class dynamic_call_info_t : public exploded_edge::custom_info_t +{ +public: + dynamic_call_info_t (const gcall *dynamic_call, + const bool is_returning_call = false) + : m_dynamic_call (dynamic_call), + m_is_returning_call (is_returning_call) + {} + + void print (pretty_printer *pp) FINAL OVERRIDE + { + if (m_is_returning_call) + pp_string (pp, "dynamic_return"); + else + pp_string (pp, "dynamic_call"); + } + + void update_model (region_model *model, + const exploded_edge &eedge) FINAL OVERRIDE; + + void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) FINAL OVERRIDE; +private: + const gcall *m_dynamic_call; + const bool m_is_returning_call; +}; + + /* Extra data for an exploded_edge that represents a rewind from a longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */ @@ -785,6 +816,14 @@ public: bool maybe_process_run_of_before_supernode_enodes (exploded_node *node); void process_node (exploded_node *node); + void create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger); + exploded_node *get_or_create_node (const program_point &point, const program_state &state, exploded_node *enode_for_diag); diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index d9f50da..25d56af 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -330,6 +330,24 @@ program_point::to_json () const return point_obj; } +/* Update the callstack to represent a call from caller to callee. + + Generally used to push a custom call to a perticular program point + where we don't have a superedge representing the call. */ +void +program_point::push_to_call_stack (const supernode *caller, + const supernode *callee) +{ + m_call_string.push_call (callee, caller); +} + +/* Pop the topmost call from the current callstack. */ +void +program_point::pop_from_call_stack () +{ + m_call_string.pop (); +} + /* Generate a hash value for this program_point. */ hashval_t diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h index 5f86745..6bae29b 100644 --- a/gcc/analyzer/program-point.h +++ b/gcc/analyzer/program-point.h @@ -293,7 +293,8 @@ public: } bool on_edge (exploded_graph &eg, const superedge *succ); - + void push_to_call_stack (const supernode *caller, const supernode *callee); + void pop_from_call_stack (); void validate () const; /* For before_stmt, go to next stmt. */ diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 5bb8676..ea53c61 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1034,6 +1034,50 @@ program_state::on_edge (exploded_graph &eg, return true; } +/* Update this program_state to reflect a call to function + represented by CALL_STMT. + currently used only when the call doesn't have a superedge representing + the call ( like call via a function pointer ) */ +void +program_state::push_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty) +{ + /* Update state. */ + const program_point &point = enode->get_point (); + const gimple *last_stmt = point.get_supernode ()->get_last_stmt (); + + impl_region_model_context ctxt (eg, enode, + &enode->get_state (), + this, + uncertainty, + last_stmt); + m_region_model->update_for_gcall (call_stmt, &ctxt); +} + +/* Update this program_state to reflect a return from function + call to which is represented by CALL_STMT. + currently used only when the call doesn't have a superedge representing + the return */ +void +program_state::returning_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty) +{ + /* Update state. */ + const program_point &point = enode->get_point (); + const gimple *last_stmt = point.get_supernode ()->get_last_stmt (); + + impl_region_model_context ctxt (eg, enode, + &enode->get_state (), + this, + uncertainty, + last_stmt); + m_region_model->update_for_return_gcall (call_stmt, &ctxt); +} + /* Generate a simpler version of THIS, discarding state that's no longer relevant at POINT. The idea is that we're more likely to be able to consolidate diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 8dee930..eb49006 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -218,6 +218,17 @@ public: void push_frame (const extrinsic_state &ext_state, function *fun); function * get_current_function () const; + void push_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty); + + void returning_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty); + + bool on_edge (exploded_graph &eg, exploded_node *enode, const superedge *succ, diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 58da7e3..2316fbe 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -3172,12 +3172,11 @@ region_model::maybe_update_for_edge (const superedge &edge, caller's frame. */ void -region_model::update_for_call_superedge (const call_superedge &call_edge, - region_model_context *ctxt) +region_model::update_for_gcall (const gcall *call_stmt, + region_model_context *ctxt) { /* Build a vec of argument svalues, using the current top frame for resolving tree expressions. */ - const gcall *call_stmt = call_edge.get_call_stmt (); auto_vec arg_svals (gimple_call_num_args (call_stmt)); for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++) @@ -3186,33 +3185,58 @@ region_model::update_for_call_superedge (const call_superedge &call_edge, arg_svals.quick_push (get_rvalue (arg, ctxt)); } - push_frame (call_edge.get_callee_function (), &arg_svals, ctxt); + /* Get the function * from the call. */ + tree fn_decl = get_fndecl_for_call (call_stmt,ctxt); + function *fun = DECL_STRUCT_FUNCTION (fn_decl); + push_frame (fun, &arg_svals, ctxt); } /* Pop the top-most frame_region from the stack, and copy the return region's values (if any) into the region for the lvalue of the LHS of the call (if any). */ + void -region_model::update_for_return_superedge (const return_superedge &return_edge, - region_model_context *ctxt) +region_model::update_for_return_gcall (const gcall *call_stmt, + region_model_context *ctxt) { /* Get the region for the result of the call, within the caller frame. */ const region *result_dst_reg = NULL; - const gcall *call_stmt = return_edge.get_call_stmt (); tree lhs = gimple_call_lhs (call_stmt); if (lhs) { /* Normally we access the top-level frame, which is: - path_var (expr, get_stack_depth () - 1) - whereas here we need the caller frame, hence "- 2" here. */ + path_var (expr, get_stack_depth () - 1) + whereas here we need the caller frame, hence "- 2" here. */ gcc_assert (get_stack_depth () >= 2); result_dst_reg = get_lvalue (path_var (lhs, get_stack_depth () - 2), - ctxt); + ctxt); } pop_frame (result_dst_reg, NULL, ctxt); } +/* Extract calling information from the superedge and update the model for the + call */ + +void +region_model::update_for_call_superedge (const call_superedge &call_edge, + region_model_context *ctxt) +{ + const gcall *call_stmt = call_edge.get_call_stmt (); + update_for_gcall (call_stmt,ctxt); +} + +/* Extract calling information from the return superedge and update the model + for the returning call */ + +void +region_model::update_for_return_superedge (const return_superedge &return_edge, + region_model_context *ctxt) +{ + const gcall *call_stmt = return_edge.get_call_stmt (); + update_for_return_gcall (call_stmt, ctxt); +} + /* Update this region_model with a summary of the effect of calling and returning from CG_SEDGE. diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 30f02a0..e40264e 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -608,6 +608,12 @@ class region_model region_model_context *ctxt, rejected_constraint **out); + void update_for_gcall (const gcall *call_stmt, + region_model_context *ctxt); + + void update_for_return_gcall (const gcall *call_stmt, + region_model_context *ctxt); + const region *push_frame (function *fun, const vec *arg_sids, region_model_context *ctxt); const frame_region *get_current_frame () const { return m_current_frame; } diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index bfa48a9..8800570 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -383,18 +383,31 @@ state_purge_per_ssa_name::process_point (const function_point &point, { /* Add any intraprocedually edge for a call. */ if (snode->m_returning_call) - { - cgraph_edge *cedge + { + gcall *returning_call = snode->m_returning_call; + cgraph_edge *cedge = supergraph_call_edge (snode->m_fun, - snode->m_returning_call); - gcc_assert (cedge); - superedge *sedge - = map.get_sg ().get_intraprocedural_edge_for_call (cedge); - gcc_assert (sedge); - add_to_worklist - (function_point::after_supernode (sedge->m_src), - worklist, logger); - } + returning_call); + if(cedge) + { + superedge *sedge + = map.get_sg ().get_intraprocedural_edge_for_call (cedge); + gcc_assert (sedge); + add_to_worklist + (function_point::after_supernode (sedge->m_src), + worklist, logger); + } + else + { + supernode *callernode + = map.get_sg ().get_supernode_for_stmt (returning_call); + + gcc_assert (callernode); + add_to_worklist + (function_point::after_supernode (callernode), + worklist, logger); + } + } } } break; diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 1eb2543..66ef765 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -183,11 +183,33 @@ supergraph::supergraph (logger *logger) m_stmt_to_node_t.put (stmt, node_for_stmts); m_stmt_uids.make_uid_unique (stmt); if (cgraph_edge *edge = supergraph_call_edge (fun, stmt)) - { - m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts); - node_for_stmts = add_node (fun, bb, as_a (stmt), NULL); - m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts); - } + { + m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts); + node_for_stmts = add_node (fun, bb, as_a (stmt), + NULL); + m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts); + } + else + { + // maybe call is via a function pointer + if (gcall *call = dyn_cast (stmt)) + { + cgraph_edge *edge + = cgraph_node::get (fun->decl)->get_edge (stmt); + if (!edge || !edge->callee) + { + supernode *old_node_for_stmts = node_for_stmts; + node_for_stmts = add_node (fun, bb, call, NULL); + + superedge *sedge + = new callgraph_superedge (old_node_for_stmts, + node_for_stmts, + SUPEREDGE_INTRAPROCEDURAL_CALL, + NULL); + add_edge (sedge); + } + } + } } m_bb_to_final_node.put (bb, node_for_stmts); @@ -1139,6 +1161,17 @@ callgraph_superedge::get_callee_decl () const return get_callee_function ()->decl; } +/* Get the gcall * of this interprocedural call/return edge. */ + +gcall * +callgraph_superedge::get_call_stmt () const +{ + if (m_cedge) + return m_cedge->call_stmt; + + return m_src->get_final_call (); +} + /* Get the calling fndecl at this interprocedural call/return edge. */ tree diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index 877958f..335f513 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -268,6 +268,11 @@ class supernode : public dnode return i; } + gcall *get_returning_call () const + { + return m_returning_call; + } + gimple *get_last_stmt () const { if (m_stmts.length () == 0) @@ -400,7 +405,7 @@ class callgraph_superedge : public superedge function *get_caller_function () const; tree get_callee_decl () const; tree get_caller_decl () const; - gcall *get_call_stmt () const { return m_cedge->call_stmt; } + gcall *get_call_stmt () const; tree get_arg_for_parm (tree parm, callsite_expr *out) const; tree get_parm_for_arg (tree arg, callsite_expr *out) const; tree map_expr_from_caller_to_callee (tree caller_expr, -- cgit v1.1 From 1b34248527472496ca3fe2a07183beac8cf69041 Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Sun, 15 Aug 2021 19:19:07 +0530 Subject: analyzer: detect and analyze virtual function calls 2021-08-15 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/97114 * region-model.cc (region_model::get_rvalue_1): Add case for OBJ_TYPE_REF. gcc/testsuite/ChangeLog: PR analyzer/97114 * g++.dg/analyzer/vfunc-2.C: New test. * g++.dg/analyzer/vfunc-3.C: New test. * g++.dg/analyzer/vfunc-4.C: New test. * g++.dg/analyzer/vfunc-5.C: New test. --- gcc/analyzer/region-model.cc | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 2316fbe..822e893 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1841,6 +1841,11 @@ region_model::get_rvalue_1 (path_var pv, region_model_context *ctxt) const const region *ref_reg = get_lvalue (pv, ctxt); return get_store_value (ref_reg, ctxt); } + case OBJ_TYPE_REF: + { + tree expr = OBJ_TYPE_REF_EXPR (pv.m_tree); + return get_rvalue (expr, ctxt); + } } } -- cgit v1.1 From 6e529985d8956f74492e3176026fc02dc8f01b6c Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 19 Aug 2021 00:16:42 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 43e3c63..0b483cc 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,54 @@ +2021-08-18 Ankur Saini + + PR analyzer/97114 + * region-model.cc (region_model::get_rvalue_1): Add case for + OBJ_TYPE_REF. + +2021-08-18 Ankur Saini + + PR analyzer/100546 + * analysis-plan.cc (analysis_plan::use_summary_p): Don't use call + summaries if there is no callgraph edge + * checker-path.cc (call_event::call_event): Handle calls events that + are not represented by a supergraph call edge + (return_event::return_event): Likewise. + (call_event::get_desc): Work with new call_event structure. + (return_event::get_desc): Likeise. + * checker-path.h (call_event::m_src_snode): New field. + (call_event::m_dest_snode): New field. + (return_event::m_src_snode): New field. + (return_event::m_dest_snode): New field. + * diagnostic-manager.cc + (diagnostic_manager::prune_for_sm_diagnostic): + Refactor to work with edges without callgraph edge. + (diagnostic_manager::prune_for_sm_diagnostic): + Likewise. + * engine.cc (dynamic_call_info_t::update_model): New function. + (dynamic_call_info_t::add_events_to_path): New function. + (exploded_graph::create_dynamic_call): New function. + (exploded_graph::process_node): Work with dynamically discovered calls. + * exploded-graph.h (class dynamic_call_info_t): New class. + (exploded_graph::create_dynamic_call): New decl. + * program-point.cc (program_point::push_to_call_stack): New function. + (program_point::pop_from_call_stack): New function. + * program-point.h (program_point::push_to_call_stack): New decl. + (program_point::pop_from_call_stack): New decl. + * program-state.cc (program_state::push_call): New function. + (program_state::returning_call): New function. + * program-state.h (program_state::push_call): New decl. + (program_state::returning_call): New decl. + * region-model.cc (region_model::update_for_gcall) New function. + (region_model::update_for_return_gcall): New function. + (egion_model::update_for_call_superedge): Get the underlying gcall and + update for gcall. + (region_model::update_for_return_superedge): Likewise. + * region-model.h (region_model::update_for_gcall): New decl. + (region_model::update_for_return_gcall): New decl. + * state-purge.cc (state_purge_per_ssa_name::process_point): Update to + work with calls without underlying cgraph edge. + * supergraph.cc (supergraph::supergraph) Split snodes at every callsite. + * supergraph.h (supernode::get_returning_call) New accessor. + 2021-08-04 David Malcolm PR analyzer/101570 -- cgit v1.1 From e92d0ff6b5e6d4b95c04fc3e326d40efeb136086 Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Thu, 19 Aug 2021 19:54:56 +0530 Subject: analyzer: Fix PR analyzer/101980 2021-08-19 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/101980 * diagnostic-manager.cc (diagnostic_manager::prune_for_sm_diagnostic): Use caller_model only when the supergraph_edge doesn't exixt. (diagnostic_manager::prune_for_sm_diagnostic): Likewise. * engine.cc (exploded_graph::create_dynamic_call): Rename to... (exploded_graph::maybe_create_dynamic_call): ...this, return call creation status. (exploded_graph::process_node): Handle calls which were not dynamically discovered. * exploded-graph.h (exploded_graph::create_dynamic_call): Rename to... (exploded_graph::maybe_create_dynamic_call): ...this. * region-model.cc (region_model::update_for_gcall): New param, use it to push call to frame. (region_model::update_for_call_superedge): Pass callee function to update_for_gcall. * region-model.h (region_model::update_for_gcall): New param. gcc/testsuite/ChangeLog: PR analyzer/101980 * gcc.dg/analyzer/function-ptr-2.c : Add issue for double 'free'. * gcc.dg/analyzer/malloc-callbacks.c : Fix xfail testcase. --- gcc/analyzer/diagnostic-manager.cc | 40 +++++++++++++++++++++++++++---- gcc/analyzer/engine.cc | 49 ++++++++++++++++++++------------------ gcc/analyzer/exploded-graph.h | 14 +++++------ gcc/analyzer/region-model.cc | 17 ++++++++----- gcc/analyzer/region-model.h | 3 ++- 5 files changed, 82 insertions(+), 41 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 06e7510..89b5d1e 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2099,7 +2099,22 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, = event->m_eedge.m_src->get_state ().m_region_model; tree callee_var = callee_model->get_representative_tree (sval); callsite_expr expr; - tree caller_var = caller_model->get_representative_tree (sval); + + tree caller_var; + if(event->m_sedge) + { + const callgraph_superedge& cg_superedge + = event->get_callgraph_superedge (); + if (cg_superedge.m_cedge) + caller_var + = cg_superedge.map_expr_from_callee_to_caller (callee_var, + &expr); + else + callee_var = callee_model->get_representative_tree (sval); + } + else + caller_var = caller_model->get_representative_tree (sval); + if (caller_var) { if (get_logger ()) @@ -2121,11 +2136,28 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, if (sval) { return_event *event = (return_event *)base_event; + const region_model *caller_model + = event->m_eedge.m_dest->get_state ().m_region_model; + tree caller_var = caller_model->get_representative_tree (sval); + const region_model *callee_model + = event->m_eedge.m_src->get_state ().m_region_model; callsite_expr expr; - const region_model *callee_model - = event->m_eedge.m_src->get_state ().m_region_model; - tree callee_var = callee_model->get_representative_tree (sval); + tree callee_var; + if (event->m_sedge) + { + const callgraph_superedge& cg_superedge + = event->get_callgraph_superedge (); + if (cg_superedge.m_cedge) + callee_var + = cg_superedge.map_expr_from_caller_to_callee (caller_var, + &expr); + else + callee_var = callee_model->get_representative_tree (sval); + } + else + callee_var = callee_model->get_representative_tree (sval); + if (callee_var) { if (get_logger ()) diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 461de9c..e66ca4e 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -3033,14 +3033,14 @@ state_change_requires_new_enode_p (const program_state &old_state, Some example such calls are dynamically dispatched calls to virtual functions or calls that happen via function pointer. */ -void -exploded_graph::create_dynamic_call (const gcall *call, - tree fn_decl, - exploded_node *node, - program_state next_state, - program_point &next_point, - uncertainty_t *uncertainty, - logger *logger) +bool +exploded_graph::maybe_create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger) { LOG_FUNC (logger); @@ -3049,8 +3049,8 @@ exploded_graph::create_dynamic_call (const gcall *call, if (fun) { const supergraph &sg = this->get_supergraph (); - supernode * sn_entry = sg.get_node_for_function_entry (fun); - supernode * sn_exit = sg.get_node_for_function_exit (fun); + supernode *sn_entry = sg.get_node_for_function_entry (fun); + supernode *sn_exit = sg.get_node_for_function_exit (fun); program_point new_point = program_point::before_supernode (sn_entry, @@ -3075,8 +3075,10 @@ exploded_graph::create_dynamic_call (const gcall *call, if (enode) add_edge (node,enode, NULL, new dynamic_call_info_t (call)); + return true; } - } + } + return false; } /* The core of exploded_graph::process_worklist (the main analysis loop), @@ -3338,22 +3340,23 @@ exploded_graph::process_node (exploded_node *node) point.get_stmt()); region_model *model = state.m_region_model; + bool call_discovered = false; if (tree fn_decl = model->get_fndecl_for_call(call,&ctxt)) - create_dynamic_call (call, - fn_decl, - node, - next_state, - next_point, - &uncertainty, - logger); - else + call_discovered = maybe_create_dynamic_call (call, + fn_decl, + node, + next_state, + next_point, + &uncertainty, + logger); + if (!call_discovered) { - /* An unknown function was called at this point, in such - case, don't terminate the analysis of the current - function. + /* An unknown function or a special function was called + at this point, in such case, don't terminate the + analysis of the current function. - The analyzer handles calls to unknown functions while + The analyzer handles calls to such functions while analysing the stmt itself, so the the function call must have been handled by the anlyzer till now. */ exploded_node *next diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 192a4b3..6890e84 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -816,13 +816,13 @@ public: bool maybe_process_run_of_before_supernode_enodes (exploded_node *node); void process_node (exploded_node *node); - void create_dynamic_call (const gcall *call, - tree fn_decl, - exploded_node *node, - program_state next_state, - program_point &next_point, - uncertainty_t *uncertainty, - logger *logger); + bool maybe_create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger); exploded_node *get_or_create_node (const program_point &point, const program_state &state, diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 822e893..9870007 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -3178,7 +3178,8 @@ region_model::maybe_update_for_edge (const superedge &edge, void region_model::update_for_gcall (const gcall *call_stmt, - region_model_context *ctxt) + region_model_context *ctxt, + function *callee) { /* Build a vec of argument svalues, using the current top frame for resolving tree expressions. */ @@ -3190,10 +3191,14 @@ region_model::update_for_gcall (const gcall *call_stmt, arg_svals.quick_push (get_rvalue (arg, ctxt)); } - /* Get the function * from the call. */ - tree fn_decl = get_fndecl_for_call (call_stmt,ctxt); - function *fun = DECL_STRUCT_FUNCTION (fn_decl); - push_frame (fun, &arg_svals, ctxt); + if(!callee) + { + /* Get the function * from the gcall. */ + tree fn_decl = get_fndecl_for_call (call_stmt,ctxt); + callee = DECL_STRUCT_FUNCTION (fn_decl); + } + + push_frame (callee, &arg_svals, ctxt); } /* Pop the top-most frame_region from the stack, and copy the return @@ -3228,7 +3233,7 @@ region_model::update_for_call_superedge (const call_superedge &call_edge, region_model_context *ctxt) { const gcall *call_stmt = call_edge.get_call_stmt (); - update_for_gcall (call_stmt,ctxt); + update_for_gcall (call_stmt, ctxt, call_edge.get_callee_function ()); } /* Extract calling information from the return superedge and update the model diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index e40264e..a734f9f 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -609,7 +609,8 @@ class region_model rejected_constraint **out); void update_for_gcall (const gcall *call_stmt, - region_model_context *ctxt); + region_model_context *ctxt, + function *callee = NULL); void update_for_return_gcall (const gcall *call_stmt, region_model_context *ctxt); -- cgit v1.1 From 4be4fa4ec7ffa16cb2b0e24f656ee0fcdf23b3e0 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Sun, 22 Aug 2021 00:16:40 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 0b483cc..da90011 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,24 @@ +2021-08-21 Ankur Saini + + PR analyzer/101980 + * diagnostic-manager.cc + (diagnostic_manager::prune_for_sm_diagnostic): Use + caller_model only when the supergraph_edge doesn't exixt. + (diagnostic_manager::prune_for_sm_diagnostic): + Likewise. + * engine.cc (exploded_graph::create_dynamic_call): Rename to... + (exploded_graph::maybe_create_dynamic_call): ...this, return call + creation status. + (exploded_graph::process_node): Handle calls which were not dynamically + discovered. + * exploded-graph.h (exploded_graph::create_dynamic_call): Rename to... + (exploded_graph::maybe_create_dynamic_call): ...this. + * region-model.cc (region_model::update_for_gcall): New param, use it + to push call to frame. + (region_model::update_for_call_superedge): Pass callee function to + update_for_gcall. + * region-model.h (region_model::update_for_gcall): New param. + 2021-08-18 Ankur Saini PR analyzer/97114 -- cgit v1.1 From 537878152ded8b7d271333b803b36c27a9aea8d2 Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Mon, 23 Aug 2021 17:03:29 +0530 Subject: analyzer: Fix PR analyzer/102020 2021-08-23 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/102020 * diagnostic-manager.cc (diagnostic_manager::prune_for_sm_diagnostic): Fix typo. gcc/testsuite/ChangeLog: PR analyzer/102020 * gcc.dg/analyzer/malloc-callbacks.c : Fix faulty test. --- gcc/analyzer/diagnostic-manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 89b5d1e..77dda4d 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2110,7 +2110,7 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, = cg_superedge.map_expr_from_callee_to_caller (callee_var, &expr); else - callee_var = callee_model->get_representative_tree (sval); + caller_var = caller_model->get_representative_tree (sval); } else caller_var = caller_model->get_representative_tree (sval); -- cgit v1.1 From 4892b3087412e6afc261cc9977ef4b54c799660f Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 23 Aug 2021 14:01:01 -0400 Subject: analyzer: fix uninit false positive on overlapping bindings gcc/analyzer/ChangeLog: * store.cc (bit_range::intersects_p): New overload. (bit_range::operator-): New. (binding_cluster::maybe_get_compound_binding): Handle the partial overlap case. (selftest::test_bit_range_intersects_p): Add test coverage for new overload of bit_range::intersects_p. * store.h (bit_range::intersects_p): New overload. (bit_range::operator-): New. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/data-model-22.c: New test. * gcc.dg/analyzer/uninit-6.c: New test. * gcc.dg/analyzer/uninit-6b.c: New test. --- gcc/analyzer/store.cc | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- gcc/analyzer/store.h | 5 ++++ 2 files changed, 79 insertions(+), 3 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index eac1295..3760858 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -253,6 +253,35 @@ bit_range::contains_p (const bit_range &other, bit_range *out) const return false; } +/* If OTHER intersects this, return true and write + the relative range of OTHER within THIS to *OUT_THIS, + and the relative range of THIS within OTHER to *OUT_OTHER. + Otherwise return false. */ + +bool +bit_range::intersects_p (const bit_range &other, + bit_range *out_this, + bit_range *out_other) const +{ + if (get_start_bit_offset () < other.get_next_bit_offset () + && other.get_start_bit_offset () < get_next_bit_offset ()) + { + bit_offset_t overlap_start + = MAX (get_start_bit_offset (), + other.get_start_bit_offset ()); + bit_offset_t overlap_next + = MIN (get_next_bit_offset (), + other.get_next_bit_offset ()); + gcc_assert (overlap_next > overlap_start); + bit_range abs_overlap_bits (overlap_start, overlap_next - overlap_start); + *out_this = abs_overlap_bits - get_start_bit_offset (); + *out_other = abs_overlap_bits - other.get_start_bit_offset (); + return true; + } + else + return false; +} + int bit_range::cmp (const bit_range &br1, const bit_range &br2) { @@ -263,6 +292,14 @@ bit_range::cmp (const bit_range &br1, const bit_range &br2) return wi::cmpu (br1.m_size_in_bits, br2.m_size_in_bits); } +/* Offset this range by OFFSET. */ + +bit_range +bit_range::operator- (bit_offset_t offset) const +{ + return bit_range (m_start_bit_offset - offset, m_size_in_bits); +} + /* If MASK is a contiguous range of set bits, write them to *OUT and return true. Otherwise return false. */ @@ -1570,9 +1607,29 @@ binding_cluster::maybe_get_compound_binding (store_manager *mgr, } else { - /* REG and the bound range partially overlap. - We don't handle this case yet. */ - return NULL; + /* REG and the bound range partially overlap. */ + bit_range reg_subrange (0, 0); + bit_range bound_subrange (0, 0); + reg_range.intersects_p (bound_range, + ®_subrange, &bound_subrange); + + /* Get the bits from the bound value for the bits at the + intersection (relative to the bound value). */ + const svalue *overlap_sval + = sval->extract_bit_range (NULL_TREE, + bound_subrange, + mgr->get_svalue_manager ()); + + /* Get key for overlap, relative to the REG. */ + const concrete_binding *overlap_concrete_key + = mgr->get_concrete_binding (reg_subrange); + result_map.put (overlap_concrete_key, overlap_sval); + + /* Clobber default_map, removing/trimming/spliting where + it overlaps with overlap_concrete_key. */ + default_map.remove_overlapping_bindings (mgr, + overlap_concrete_key, + NULL); } } else @@ -2905,6 +2962,20 @@ test_bit_range_intersects_p () ASSERT_FALSE (b3_to_5.intersects_p (b6_to_7)); ASSERT_FALSE (b6_to_7.intersects_p (b3_to_5)); + + bit_range r1 (0,0); + bit_range r2 (0,0); + ASSERT_TRUE (b1_to_6.intersects_p (b0_to_7, &r1, &r2)); + ASSERT_EQ (r1.get_start_bit_offset (), 0); + ASSERT_EQ (r1.m_size_in_bits, 6); + ASSERT_EQ (r2.get_start_bit_offset (), 1); + ASSERT_EQ (r2.m_size_in_bits, 6); + + ASSERT_TRUE (b0_to_7.intersects_p (b1_to_6, &r1, &r2)); + ASSERT_EQ (r1.get_start_bit_offset (), 1); + ASSERT_EQ (r1.m_size_in_bits, 6); + ASSERT_EQ (r2.get_start_bit_offset (), 0); + ASSERT_EQ (r2.m_size_in_bits, 6); } /* Implementation detail of ASSERT_BIT_RANGE_FROM_MASK_EQ. */ diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index b75691e..da82bd1 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -269,9 +269,14 @@ struct bit_range return (get_start_bit_offset () < other.get_next_bit_offset () && other.get_start_bit_offset () < get_next_bit_offset ()); } + bool intersects_p (const bit_range &other, + bit_range *out_this, + bit_range *out_other) const; static int cmp (const bit_range &br1, const bit_range &br2); + bit_range operator- (bit_offset_t offset) const; + static bool from_mask (unsigned HOST_WIDE_INT mask, bit_range *out); bool as_byte_range (byte_range *out) const; -- cgit v1.1 From e82e0f149b0aba660896ea9aa12c442c07a16d12 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 23 Aug 2021 14:07:39 -0400 Subject: analyzer: assume that POINTER_PLUS_EXPR of non-NULL is non-NULL [PR101962] gcc/analyzer/ChangeLog: PR analyzer/101962 * region-model.cc (region_model::eval_condition_without_cm): Refactor comparison against zero, adding a check for POINTER_PLUS_EXPR of non-NULL. gcc/testsuite/ChangeLog: PR analyzer/101962 * gcc.dg/analyzer/data-model-23.c: New test. * gcc.dg/analyzer/pr101962.c: New test. --- gcc/analyzer/region-model.cc | 73 +++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 28 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 9870007..f54be14e 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -2488,34 +2488,51 @@ region_model::eval_condition_without_cm (const svalue *lhs, if (const constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ()) return constant_svalue::eval_condition (cst_lhs, op, cst_rhs); - /* Handle comparison of a region_svalue against zero. */ - - if (const region_svalue *ptr = lhs->dyn_cast_region_svalue ()) - if (const constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ()) - if (zerop (cst_rhs->get_constant ())) - { - /* A region_svalue is a non-NULL pointer, except in certain - special cases (see the comment for region::non_null_p. */ - const region *pointee = ptr->get_pointee (); - if (pointee->non_null_p ()) - { - switch (op) - { - default: - gcc_unreachable (); - - case EQ_EXPR: - case GE_EXPR: - case LE_EXPR: - return tristate::TS_FALSE; - - case NE_EXPR: - case GT_EXPR: - case LT_EXPR: - return tristate::TS_TRUE; - } - } - } + /* Handle comparison against zero. */ + if (const constant_svalue *cst_rhs = rhs->dyn_cast_constant_svalue ()) + if (zerop (cst_rhs->get_constant ())) + { + if (const region_svalue *ptr = lhs->dyn_cast_region_svalue ()) + { + /* A region_svalue is a non-NULL pointer, except in certain + special cases (see the comment for region::non_null_p). */ + const region *pointee = ptr->get_pointee (); + if (pointee->non_null_p ()) + { + switch (op) + { + default: + gcc_unreachable (); + + case EQ_EXPR: + case GE_EXPR: + case LE_EXPR: + return tristate::TS_FALSE; + + case NE_EXPR: + case GT_EXPR: + case LT_EXPR: + return tristate::TS_TRUE; + } + } + } + else if (const binop_svalue *binop = lhs->dyn_cast_binop_svalue ()) + { + /* Treat offsets from a non-NULL pointer as being non-NULL. This + isn't strictly true, in that eventually ptr++ will wrap + around and be NULL, but it won't occur in practise and thus + can be used to suppress effectively false positives that we + shouldn't warn for. */ + if (binop->get_op () == POINTER_PLUS_EXPR) + { + tristate lhs_ts + = eval_condition_without_cm (binop->get_arg0 (), + op, rhs); + if (lhs_ts.is_known ()) + return lhs_ts; + } + } + } /* Handle rejection of equality for comparisons of the initial values of "external" values (such as params) with the address of locals. */ -- cgit v1.1 From 4b821c7efbe12cfbb129a88541108b39058da526 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 23 Aug 2021 14:09:44 -0400 Subject: analyzer: fix ICE when failing to reconstruct a fn ptr [PR101837] gcc/analyzer/ChangeLog: PR analyzer/101837 * analyzer.cc (maybe_reconstruct_from_def_stmt): Bail if fn is NULL, and assert that it's non-NULL before passing it to build_call_array_loc. gcc/testsuite/ChangeLog: PR analyzer/101837 * gcc.dg/analyzer/pr101837.c: New test. --- gcc/analyzer/analyzer.cc | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index 5578877..f6e9c9d 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -145,6 +145,8 @@ maybe_reconstruct_from_def_stmt (tree ssa_name, tree return_type = gimple_call_return_type (call_stmt); tree fn = fixup_tree_for_diagnostic_1 (gimple_call_fn (call_stmt), visited); + if (fn == NULL_TREE) + return NULL_TREE; unsigned num_args = gimple_call_num_args (call_stmt); auto_vec args (num_args); for (unsigned i = 0; i < num_args; i++) @@ -155,6 +157,7 @@ maybe_reconstruct_from_def_stmt (tree ssa_name, return NULL_TREE; args.quick_push (arg); } + gcc_assert (fn); return build_call_array_loc (gimple_location (call_stmt), return_type, fn, num_args, args.address ()); -- cgit v1.1 From 3d654ca3f421ff9646470d312097602037176352 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 23 Aug 2021 14:11:58 -0400 Subject: analyzer: fix ICE with NULL change.m_expr [PR101875] gcc/analyzer/ChangeLog: PR analyzer/101875 * sm-file.cc (file_diagnostic::describe_state_change): Handle change.m_expr being NULL. gcc/testsuite/ChangeLog: PR analyzer/101875 * gcc.dg/analyzer/pr101875.c: New test. --- gcc/analyzer/sm-file.cc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index 6a17019..0c8cdf0 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -125,11 +125,21 @@ public: return label_text::borrow ("opened here"); if (change.m_old_state == m_sm.m_unchecked && change.m_new_state == m_sm.m_nonnull) - return change.formatted_print ("assuming %qE is non-NULL", - change.m_expr); + { + if (change.m_expr) + return change.formatted_print ("assuming %qE is non-NULL", + change.m_expr); + else + return change.formatted_print ("assuming FILE * is non-NULL"); + } if (change.m_new_state == m_sm.m_null) - return change.formatted_print ("assuming %qE is NULL", - change.m_expr); + { + if (change.m_expr) + return change.formatted_print ("assuming %qE is NULL", + change.m_expr); + else + return change.formatted_print ("assuming FILE * is NULL"); + } return label_text (); } -- cgit v1.1 From 8ca7fa84a3af355c3e2bbda2acc61934c16078b2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 23 Aug 2021 19:27:21 -0400 Subject: analyzer: rewrite of switch handling When investigating false positives on the Linux kernel from -Wanalyzer-use-of-uninitialized-value, I noticed that the existing implementation of switch statements in the analyzer is broken. Specifically, the existing implementation assumes a 1:1 association between CFG out-edges from the basic block and case labels in the gimple switch statement. This happened to be the case in the examples I had tested, but there is no such association in general. In particular, in the motivating example: arch/x86/kernel/cpu/mtrr/if.c: mtrr_ioctl the switch statement has 3 blocks, each covering multiple ranges of ioctl command IDs for which different local variables are initialized, which the existing implementation gets badly wrong. [1] This patch reimplements switch handling in the analyzer to eliminate this false assumption - instead, for each out-edge we gather the set of case labels for that out-edge, and use that to determine the set of value ranges for the edge. Avoiding false positives for the above example requires that we accurately track value ranges for symbolic values, so the patch extends constraint_manager with a new bounded_ranges_constraint, adding just enough information to capture the ranges for switch statements whilst retaining combatility with the existing constraint-handling (ultimately I'd prefer to simply throw all of this into a SAT solver and let it track things). Doing so fixes the false positives seen on the Linux kernel and an existing xfail in the test suite. The patch also fixes a long-standing bug in constraint_manager::add_unknown_constraint when updating constraints due to combining equivalence classes, spotted when debugging the same logic for the new kind of constraints. [1] a reduced version of this code is captured in this patch, in gcc.dg/analyzer/torture/switch-3.c gcc/analyzer/ChangeLog: * analyzer.h (struct rejected_constraint): Convert to... (class rejected_constraint): ...this. (class bounded_ranges): New forward decl. (class bounded_ranges_manager): New forward decl. * constraint-manager.cc: Include "analyzer/analyzer-logging.h" and "tree-pretty-print.h". (can_plus_one_p): New. (plus_one): New. (can_minus_one_p): New. (minus_one): New. (bounded_range::bounded_range): New. (dump_cst): New. (bounded_range::dump_to_pp): New. (bounded_range::dump): New. (bounded_range::to_json): New. (bounded_range::set_json_attr): New. (bounded_range::contains_p): New. (bounded_range::intersects_p): New. (bounded_range::operator==): New. (bounded_range::cmp): New. (bounded_ranges::bounded_ranges): New. (bounded_ranges::bounded_ranges): New. (bounded_ranges::bounded_ranges): New. (bounded_ranges::canonicalize): New. (bounded_ranges::validate): New. (bounded_ranges::operator==): New. (bounded_ranges::dump_to_pp): New. (bounded_ranges::dump): New. (bounded_ranges::to_json): New. (bounded_ranges::eval_condition): New. (bounded_ranges::contain_p): New. (bounded_ranges::cmp): New. (bounded_ranges_manager::~bounded_ranges_manager): New. (bounded_ranges_manager::get_or_create_empty): New. (bounded_ranges_manager::get_or_create_point): New. (bounded_ranges_manager::get_or_create_range): New. (bounded_ranges_manager::get_or_create_union): New. (bounded_ranges_manager::get_or_create_intersection): New. (bounded_ranges_manager::get_or_create_inverse): New. (bounded_ranges_manager::consolidate): New. (bounded_ranges_manager::get_or_create_ranges_for_switch): New. (bounded_ranges_manager::create_ranges_for_switch): New. (bounded_ranges_manager::make_case_label_ranges): New. (bounded_ranges_manager::log_stats): New. (bounded_ranges_constraint::print): New. (bounded_ranges_constraint::to_json): New. (bounded_ranges_constraint::operator==): New. (bounded_ranges_constraint::add_to_hash): New. (constraint_manager::constraint_manager): Update for new field m_bounded_ranges_constraints. (constraint_manager::operator=): Likewise. (constraint_manager::hash): Likewise. (constraint_manager::operator==): Likewise. (constraint_manager::print): Likewise. (constraint_manager::dump_to_pp): Likewise. (constraint_manager::to_json): Likewise. (constraint_manager::add_unknown_constraint): Update the lhs_ec_id if necessary in existing constraints when combining equivalence classes. Add similar code for handling m_bounded_ranges_constraints. (constraint_manager::add_constraint_internal): Add comment. (constraint_manager::add_bounded_ranges): New. (constraint_manager::eval_condition): Use new field m_bounded_ranges_constraints. (constraint_manager::purge): Update bounded_ranges_constraint instances. (constraint_manager::canonicalize): Update for new field. (merger_fact_visitor::on_ranges): New. (constraint_manager::for_each_fact): Use new field m_bounded_ranges_constraints. (constraint_manager::validate): Fix off-by-one error needed due to bug fixed above in add_unknown_constraint. Validate the EC IDs in m_bounded_ranges_constraints. (constraint_manager::get_range_manager): New. (selftest::assert_dump_bounded_range_eq): New. (ASSERT_DUMP_BOUNDED_RANGE_EQ): New. (selftest::test_bounded_range): New. (selftest::assert_dump_bounded_ranges_eq): New. (ASSERT_DUMP_BOUNDED_RANGES_EQ): New. (selftest::test_bounded_ranges): New. (selftest::run_constraint_manager_tests): Call the new selftests. * constraint-manager.h (struct bounded_range): New. (struct bounded_ranges): New. (template <> struct default_hash_traits): New. (class bounded_ranges_manager): New. (fact_visitor::on_ranges): New pure virtual function. (class bounded_ranges_constraint): New. (constraint_manager::add_bounded_ranges): New decl. (constraint_manager::get_range_manager): New decl. (constraint_manager::m_bounded_ranges_constraints): New field. * diagnostic-manager.cc (epath_finder::process_worklist_item): Transfer ownership of rc to add_feasibility_problem. * engine.cc (feasibility_problem::dump_to_pp): Use get_model. * feasible-graph.cc (infeasible_node::dump_dot): Update for conversion of m_rc to a pointer. (feasible_graph::add_feasibility_problem): Pass RC by pointer and take ownership. * feasible-graph.h (infeasible_node::infeasible_node): Pass RC by pointer and take ownership. (infeasible_node::~infeasible_node): New. (infeasible_node::m_rc): Convert to a pointer. (feasible_graph::add_feasibility_problem): Pass RC by pointer and take ownership. * region-model-manager.cc: Include "analyzer/constraint-manager.h". (region_model_manager::region_model_manager): Initializer new field m_range_mgr. (region_model_manager::~region_model_manager): Delete it. (region_model_manager::log_stats): Call log_stats on it. * region-model.cc (region_model::add_constraint): Use new subclass rejected_op_constraint. (region_model::apply_constraints_for_gswitch): Reimplement using bounded_ranges_manager. (rejected_constraint::dump_to_pp): Convert to... (rejected_op_constraint::dump_to_pp): ...this. (rejected_ranges_constraint::dump_to_pp): New. * region-model.h (struct purge_stats): Add field m_num_bounded_ranges_constraints. (region_model_manager::get_range_manager): New. (region_model_manager::m_range_mgr): New. (region_model::get_range_manager): New. (struct rejected_constraint): Split into... (class rejected_constraint):...this new abstract base class, and... (class rejected_op_constraint): ...this new concrete subclass. (class rejected_ranges_constraint): New. * supergraph.cc: Include "tree-cfg.h". (supergraph::supergraph): Drop idx param from add_cfg_edge. (supergraph::add_cfg_edge): Drop idx param. (switch_cfg_superedge::switch_cfg_superedge): Move here from header. Populate m_case_labels with all cases which go to DST. (switch_cfg_superedge::dump_label_to_pp): Reimplement to use m_case_labels. (switch_cfg_superedge::get_case_label): Delete. * supergraph.h (supergraphadd_cfg_edge): Drop "idx" param. (switch_cfg_superedge::switch_cfg_superedge): Drop idx param and move implementation to supergraph.cc. (switch_cfg_superedge::get_case_label): Delete. (switch_cfg_superedge::get_case_labels): New. (switch_cfg_superedge::m_idx): Delete. (switch_cfg_superedge::m_case_labels): New field. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/switch.c: Remove xfail. Add various tests. * gcc.dg/analyzer/torture/switch-2.c: New test. * gcc.dg/analyzer/torture/switch-3.c: New test. * gcc.dg/analyzer/torture/switch-4.c: New test. * gcc.dg/analyzer/torture/switch-5.c: New test. --- gcc/analyzer/analyzer.h | 4 +- gcc/analyzer/constraint-manager.cc | 1365 +++++++++++++++++++++++++++++++++- gcc/analyzer/constraint-manager.h | 191 +++++ gcc/analyzer/diagnostic-manager.cc | 3 +- gcc/analyzer/engine.cc | 2 +- gcc/analyzer/feasible-graph.cc | 7 +- gcc/analyzer/feasible-graph.h | 7 +- gcc/analyzer/region-model-manager.cc | 7 +- gcc/analyzer/region-model.cc | 75 +- gcc/analyzer/region-model.h | 54 +- gcc/analyzer/supergraph.cc | 99 ++- gcc/analyzer/supergraph.h | 15 +- 12 files changed, 1703 insertions(+), 126 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 896b350..05d4751 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -75,10 +75,12 @@ class region_model; class region_model_context; class impl_region_model_context; class call_details; -struct rejected_constraint; +class rejected_constraint; class constraint_manager; class equiv_class; class reachable_regions; +class bounded_ranges; +class bounded_ranges_manager; class pending_diagnostic; class state_change_event; diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index f59929a..dc65c8d 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -42,12 +42,14 @@ along with GCC; see the file COPYING3. If not see #include "sbitmap.h" #include "bitmap.h" #include "tristate.h" +#include "analyzer/analyzer-logging.h" #include "analyzer/call-string.h" #include "analyzer/program-point.h" #include "analyzer/store.h" #include "analyzer/region-model.h" #include "analyzer/constraint-manager.h" #include "analyzer/analyzer-selftests.h" +#include "tree-pretty-print.h" #if ENABLE_ANALYZER @@ -65,6 +67,50 @@ compare_constants (tree lhs_const, enum tree_code op, tree rhs_const) return tristate (tristate::TS_UNKNOWN); } +/* Return true iff CST is below the maximum value for its type. */ + +static bool +can_plus_one_p (tree cst) +{ + gcc_assert (CONSTANT_CLASS_P (cst)); + return tree_int_cst_lt (cst, TYPE_MAX_VALUE (TREE_TYPE (cst))); +} + +/* Return (CST + 1). */ + +static tree +plus_one (tree cst) +{ + gcc_assert (CONSTANT_CLASS_P (cst)); + gcc_assert (can_plus_one_p (cst)); + tree result = fold_build2 (PLUS_EXPR, TREE_TYPE (cst), + cst, integer_one_node); + gcc_assert (CONSTANT_CLASS_P (result)); + return result; +} + +/* Return true iff CST is above the minimum value for its type. */ + +static bool +can_minus_one_p (tree cst) +{ + gcc_assert (CONSTANT_CLASS_P (cst)); + return tree_int_cst_lt (TYPE_MIN_VALUE (TREE_TYPE (cst)), cst); +} + +/* Return (CST - 1). */ + +static tree +minus_one (tree cst) +{ + gcc_assert (CONSTANT_CLASS_P (cst)); + gcc_assert (can_minus_one_p (cst)); + tree result = fold_build2 (MINUS_EXPR, TREE_TYPE (cst), + cst, integer_one_node); + gcc_assert (CONSTANT_CLASS_P (result)); + return result; +} + /* struct bound. */ /* Ensure that this bound is closed by converting an open bound to a @@ -222,37 +268,709 @@ range::eval_condition (enum tree_code op, tree rhs_const) const return tristate (tristate::TS_TRUE); break; - default: - gcc_unreachable (); - break; - } - return tristate (tristate::TS_UNKNOWN); + default: + gcc_unreachable (); + break; + } + return tristate (tristate::TS_UNKNOWN); +} + +/* Return true if RHS_CONST is below the lower bound of this range. */ + +bool +range::below_lower_bound (tree rhs_const) const +{ + if (!m_lower_bound.m_constant) + return false; + + return compare_constants (rhs_const, + m_lower_bound.m_closed ? LT_EXPR : LE_EXPR, + m_lower_bound.m_constant).is_true (); +} + +/* Return true if RHS_CONST is above the upper bound of this range. */ + +bool +range::above_upper_bound (tree rhs_const) const +{ + if (!m_upper_bound.m_constant) + return false; + + return compare_constants (rhs_const, + m_upper_bound.m_closed ? GT_EXPR : GE_EXPR, + m_upper_bound.m_constant).is_true (); +} + +/* struct bounded_range. */ + +bounded_range::bounded_range (const_tree lower, const_tree upper) +: m_lower (const_cast (lower)), + m_upper (const_cast (upper)) +{ + if (lower && upper) + { + gcc_assert (TREE_CODE (m_lower) == INTEGER_CST); + gcc_assert (TREE_CODE (m_upper) == INTEGER_CST); + /* We should have lower <= upper. */ + gcc_assert (!tree_int_cst_lt (m_upper, m_lower)); + } + else + { + /* Purely for pending on-stack values, for + writing back to. */ + gcc_assert (m_lower == NULL_TREE); + gcc_assert (m_lower == NULL_TREE); + } +} + +static void +dump_cst (pretty_printer *pp, tree cst, bool show_types) +{ + gcc_assert (cst); + if (show_types) + { + pp_character (pp, '('); + dump_generic_node (pp, TREE_TYPE (cst), 0, (dump_flags_t)0, false); + pp_character (pp, ')'); + } + dump_generic_node (pp, cst, 0, (dump_flags_t)0, false); +} + +/* Dump this object to PP. */ + +void +bounded_range::dump_to_pp (pretty_printer *pp, bool show_types) const +{ + if (tree_int_cst_equal (m_lower, m_upper)) + dump_cst (pp, m_lower, show_types); + else + { + pp_character (pp, '['); + dump_cst (pp, m_lower, show_types); + pp_string (pp, ", "); + dump_cst (pp, m_upper, show_types); + pp_character (pp, ']'); + } +} + +/* Dump this object to stderr. */ + +void +bounded_range::dump (bool show_types) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, show_types); + pp_newline (&pp); + pp_flush (&pp); +} + +json::object * +bounded_range::to_json () const +{ + json::object *range_obj = new json::object (); + set_json_attr (range_obj, "lower", m_lower); + set_json_attr (range_obj, "upper", m_upper); + return range_obj; +} + +/* Subroutine of bounded_range::to_json. */ + +void +bounded_range::set_json_attr (json::object *obj, const char *name, tree value) +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_printf (&pp, "%E", value); + obj->set (name, new json::string (pp_formatted_text (&pp))); +} + + +/* Return true iff CST is within this range. */ + +bool +bounded_range::contains_p (tree cst) const +{ + /* Reject if below lower bound. */ + if (tree_int_cst_lt (cst, m_lower)) + return false; + /* Reject if above lower bound. */ + if (tree_int_cst_lt (m_upper, cst)) + return false; + return true; +} + +/* If this range intersects OTHER, return true, writing + the intersection to *OUT if OUT is non-NULL. + Return false if they do not intersect. */ + +bool +bounded_range::intersects_p (const bounded_range &other, + bounded_range *out) const +{ + const tree max_lower + = (tree_int_cst_le (m_lower, other.m_lower) + ? other.m_lower : m_lower); + gcc_assert (TREE_CODE (max_lower) == INTEGER_CST); + const tree min_upper + = (tree_int_cst_le (m_upper, other.m_upper) + ? m_upper : other.m_upper); + gcc_assert (TREE_CODE (min_upper) == INTEGER_CST); + + if (tree_int_cst_le (max_lower, min_upper)) + { + if (out) + *out = bounded_range (max_lower, min_upper); + return true; + } + else + return false; +} + +bool +bounded_range::operator== (const bounded_range &other) const +{ + return (tree_int_cst_equal (m_lower, other.m_lower) + && tree_int_cst_equal (m_upper, other.m_upper)); +} + +int +bounded_range::cmp (const bounded_range &br1, const bounded_range &br2) +{ + if (int cmp_lower = tree_int_cst_compare (br1.m_lower, + br2.m_lower)) + return cmp_lower; + return tree_int_cst_compare (br1.m_upper, br2.m_upper); +} + +/* struct bounded_ranges. */ + +/* Construct a bounded_ranges instance from a single range. */ + +bounded_ranges::bounded_ranges (const bounded_range &range) +: m_ranges (1) +{ + m_ranges.quick_push (range); + canonicalize (); + validate (); +} + +/* Construct a bounded_ranges instance from multiple ranges. */ + +bounded_ranges::bounded_ranges (const vec &ranges) +: m_ranges (ranges.length ()) +{ + m_ranges.safe_splice (ranges); + canonicalize (); + validate (); +} + +/* Construct a bounded_ranges instance for values of LHS for which + (LHS OP RHS_CONST) is true (e.g. "(LHS > 3)". */ + +bounded_ranges::bounded_ranges (enum tree_code op, tree rhs_const) +: m_ranges () +{ + gcc_assert (TREE_CODE (rhs_const) == INTEGER_CST); + tree type = TREE_TYPE (rhs_const); + switch (op) + { + default: + gcc_unreachable (); + case EQ_EXPR: + m_ranges.safe_push (bounded_range (rhs_const, rhs_const)); + break; + + case GE_EXPR: + m_ranges.safe_push (bounded_range (rhs_const, TYPE_MAX_VALUE (type))); + break; + + case LE_EXPR: + m_ranges.safe_push (bounded_range (TYPE_MIN_VALUE (type), rhs_const)); + break; + + case NE_EXPR: + if (tree_int_cst_lt (TYPE_MIN_VALUE (type), rhs_const)) + m_ranges.safe_push (bounded_range (TYPE_MIN_VALUE (type), + minus_one (rhs_const))); + if (tree_int_cst_lt (rhs_const, TYPE_MAX_VALUE (type))) + m_ranges.safe_push (bounded_range (plus_one (rhs_const), + TYPE_MAX_VALUE (type))); + break; + case GT_EXPR: + if (tree_int_cst_lt (rhs_const, TYPE_MAX_VALUE (type))) + m_ranges.safe_push (bounded_range (plus_one (rhs_const), + TYPE_MAX_VALUE (type))); + break; + case LT_EXPR: + if (tree_int_cst_lt (TYPE_MIN_VALUE (type), rhs_const)) + m_ranges.safe_push (bounded_range (TYPE_MIN_VALUE (type), + minus_one (rhs_const))); + break; + } + canonicalize (); + validate (); +} + +/* Subroutine of ctors for fixing up m_ranges. + Also, initialize m_hash. */ + +void +bounded_ranges::canonicalize () +{ + /* Sort the ranges. */ + m_ranges.qsort ([](const void *p1, const void *p2) -> int + { + const bounded_range &br1 = *(const bounded_range *)p1; + const bounded_range &br2 = *(const bounded_range *)p2; + return bounded_range::cmp (br1, br2); + }); + + /* Merge ranges that are touching or overlapping. */ + for (unsigned i = 1; i < m_ranges.length (); ) + { + bounded_range *prev = &m_ranges[i - 1]; + const bounded_range *next = &m_ranges[i]; + if (prev->intersects_p (*next, NULL) + || (can_plus_one_p (prev->m_upper) + && tree_int_cst_equal (plus_one (prev->m_upper), + next->m_lower))) + { + prev->m_upper = next->m_upper; + m_ranges.ordered_remove (i); + } + else + i++; + } + + /* Initialize m_hash. */ + inchash::hash hstate (0); + for (const auto &iter : m_ranges) + { + inchash::add_expr (iter.m_lower, hstate); + inchash::add_expr (iter.m_upper, hstate); + } + m_hash = hstate.end (); +} + +/* Assert that this object is valid. */ + +void +bounded_ranges::validate () const +{ + /* Skip this in a release build. */ +#if !CHECKING_P + return; +#endif + + for (unsigned i = 1; i < m_ranges.length (); i++) + { + const bounded_range &prev = m_ranges[i - 1]; + const bounded_range &next = m_ranges[i]; + + /* Give up if we somehow have incompatible different types. */ + if (!types_compatible_p (TREE_TYPE (prev.m_upper), + TREE_TYPE (next.m_lower))) + continue; + + /* Verify sorted. */ + gcc_assert (tree_int_cst_lt (prev.m_upper, next.m_lower)); + + gcc_assert (can_plus_one_p (prev.m_upper)); + /* otherwise there's no room for "next". */ + + /* Verify no ranges touch each other. */ + gcc_assert (tree_int_cst_lt (plus_one (prev.m_upper), next.m_lower)); + } +} + +/* bounded_ranges equality operator. */ + +bool +bounded_ranges::operator== (const bounded_ranges &other) const +{ + if (m_ranges.length () != other.m_ranges.length ()) + return false; + for (unsigned i = 0; i < m_ranges.length (); i++) + { + if (m_ranges[i] != other.m_ranges[i]) + return false; + } + return true; +} + +/* Dump this object to PP. */ + +void +bounded_ranges::dump_to_pp (pretty_printer *pp, bool show_types) const +{ + pp_character (pp, '{'); + for (unsigned i = 0; i < m_ranges.length (); ++i) + { + if (i > 0) + pp_string (pp, ", "); + m_ranges[i].dump_to_pp (pp, show_types); + } + pp_character (pp, '}'); +} + +/* Dump this object to stderr. */ + +DEBUG_FUNCTION void +bounded_ranges::dump (bool show_types) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, show_types); + pp_newline (&pp); + pp_flush (&pp); +} + +json::value * +bounded_ranges::to_json () const +{ + json::array *arr_obj = new json::array (); + + for (unsigned i = 0; i < m_ranges.length (); ++i) + arr_obj->append (m_ranges[i].to_json ()); + + return arr_obj; +} + +/* Determine whether (X OP RHS_CONST) is known to be true or false + for all X in the ranges expressed by this object. */ + +tristate +bounded_ranges::eval_condition (enum tree_code op, + tree rhs_const, + bounded_ranges_manager *mgr) const +{ + /* Convert (X OP RHS_CONST) to a bounded_ranges instance and find + the intersection of that with this object. */ + bounded_ranges other (op, rhs_const); + const bounded_ranges *intersection + = mgr->get_or_create_intersection (this, &other); + + if (intersection->m_ranges.length () > 0) + { + /* We can use pointer equality to check for equality, + due to instance consolidation. */ + if (intersection == this) + return tristate (tristate::TS_TRUE); + else + return tristate (tristate::TS_UNKNOWN); + } + else + /* No intersection. */ + return tristate (tristate::TS_FALSE); +} + +/* Return true if CST is within any of the ranges. */ + +bool +bounded_ranges::contain_p (tree cst) const +{ + gcc_assert (TREE_CODE (cst) == INTEGER_CST); + for (const auto &iter : m_ranges) + { + /* TODO: should we optimize this based on sorting? */ + if (iter.contains_p (cst)) + return true; + } + return false; +} + +int +bounded_ranges::cmp (const bounded_ranges *a, const bounded_ranges *b) +{ + if (int cmp_length = ((int)a->m_ranges.length () + - (int)b->m_ranges.length ())) + return cmp_length; + for (unsigned i = 0; i < a->m_ranges.length (); i++) + { + if (int cmp_range = bounded_range::cmp (a->m_ranges[i], b->m_ranges[i])) + return cmp_range; + } + /* They are equal. They ought to have been consolidated, so we should + have two pointers to the same object. */ + gcc_assert (a == b); + return 0; +} + +/* class bounded_ranges_manager. */ + +/* bounded_ranges_manager's dtor. */ + +bounded_ranges_manager::~bounded_ranges_manager () +{ + /* Delete the managed objects. */ + for (const auto &iter : m_map) + delete iter.second; +} + +/* Get the bounded_ranges instance for the empty set, creating it if + necessary. */ + +const bounded_ranges * +bounded_ranges_manager::get_or_create_empty () +{ + auto_vec empty_vec; + + return consolidate (new bounded_ranges (empty_vec)); +} + +/* Get the bounded_ranges instance for {CST}, creating it if necessary. */ + +const bounded_ranges * +bounded_ranges_manager::get_or_create_point (const_tree cst) +{ + gcc_assert (TREE_CODE (cst) == INTEGER_CST); + + return get_or_create_range (cst, cst); +} + +/* Get the bounded_ranges instance for {[LOWER_BOUND..UPPER_BOUND]}, + creating it if necessary. */ + +const bounded_ranges * +bounded_ranges_manager::get_or_create_range (const_tree lower_bound, + const_tree upper_bound) +{ + gcc_assert (TREE_CODE (lower_bound) == INTEGER_CST); + gcc_assert (TREE_CODE (upper_bound) == INTEGER_CST); + + return consolidate + (new bounded_ranges (bounded_range (lower_bound, upper_bound))); +} + +/* Get the bounded_ranges instance for the union of OTHERS, + creating it if necessary. */ + +const bounded_ranges * +bounded_ranges_manager:: +get_or_create_union (const vec &others) +{ + auto_vec ranges; + for (const auto &r : others) + ranges.safe_splice (r->m_ranges); + return consolidate (new bounded_ranges (ranges)); +} + +/* Get the bounded_ranges instance for the intersection of A and B, + creating it if necessary. */ + +const bounded_ranges * +bounded_ranges_manager::get_or_create_intersection (const bounded_ranges *a, + const bounded_ranges *b) +{ + auto_vec ranges; + unsigned a_idx = 0; + unsigned b_idx = 0; + while (a_idx < a->m_ranges.length () + && b_idx < b->m_ranges.length ()) + { + const bounded_range &r_a = a->m_ranges[a_idx]; + const bounded_range &r_b = b->m_ranges[b_idx]; + + bounded_range intersection (NULL_TREE, NULL_TREE); + if (r_a.intersects_p (r_b, &intersection)) + { + ranges.safe_push (intersection); + } + if (tree_int_cst_lt (r_a.m_lower, r_b.m_lower)) + { + a_idx++; + } + else + { + if (tree_int_cst_lt (r_a.m_upper, r_b.m_upper)) + a_idx++; + else + b_idx++; + } + } + + return consolidate (new bounded_ranges (ranges)); +} + +/* Get the bounded_ranges instance for the inverse of OTHER relative + to TYPE, creating it if necessary. + This is for use when handling "default" in switch statements, where + OTHER represents all the other cases. */ + +const bounded_ranges * +bounded_ranges_manager::get_or_create_inverse (const bounded_ranges *other, + tree type) +{ + tree min_val = TYPE_MIN_VALUE (type); + tree max_val = TYPE_MAX_VALUE (type); + if (other->m_ranges.length () == 0) + return get_or_create_range (min_val, max_val); + auto_vec ranges; + tree first_lb = other->m_ranges[0].m_lower; + if (tree_int_cst_lt (min_val, first_lb) + && can_minus_one_p (first_lb)) + ranges.safe_push (bounded_range (min_val, + minus_one (first_lb))); + for (unsigned i = 1; i < other->m_ranges.length (); i++) + { + tree prev_ub = other->m_ranges[i - 1].m_upper; + tree iter_lb = other->m_ranges[i].m_lower; + gcc_assert (tree_int_cst_lt (prev_ub, iter_lb)); + if (can_plus_one_p (prev_ub) && can_minus_one_p (iter_lb)) + ranges.safe_push (bounded_range (plus_one (prev_ub), + minus_one (iter_lb))); + } + tree last_ub + = other->m_ranges[other->m_ranges.length () - 1].m_upper; + if (tree_int_cst_lt (last_ub, max_val) + && can_plus_one_p (last_ub)) + ranges.safe_push (bounded_range (plus_one (last_ub), max_val)); + + return consolidate (new bounded_ranges (ranges)); +} + +/* If an object equal to INST is already present, delete INST and + return the existing object. + Otherwise add INST and return it. */ + +const bounded_ranges * +bounded_ranges_manager::consolidate (bounded_ranges *inst) +{ + if (bounded_ranges **slot = m_map.get (inst)) + { + delete inst; + return *slot; + } + m_map.put (inst, inst); + return inst; +} + +/* Get the bounded_ranges instance for EDGE of SWITCH_STMT, + creating it if necessary, and caching it by edge. */ + +const bounded_ranges * +bounded_ranges_manager:: +get_or_create_ranges_for_switch (const switch_cfg_superedge *edge, + const gswitch *switch_stmt) +{ + /* Look in per-edge cache. */ + if (const bounded_ranges ** slot = m_edge_cache.get (edge)) + return *slot; + + /* Not yet in cache. */ + const bounded_ranges *all_cases_ranges + = create_ranges_for_switch (*edge, switch_stmt); + m_edge_cache.put (edge, all_cases_ranges); + return all_cases_ranges; } -/* Return true if RHS_CONST is below the lower bound of this range. */ +/* Get the bounded_ranges instance for EDGE of SWITCH_STMT, + creating it if necessary, for edges for which the per-edge + cache has not yet been populated. */ -bool -range::below_lower_bound (tree rhs_const) const +const bounded_ranges * +bounded_ranges_manager:: +create_ranges_for_switch (const switch_cfg_superedge &edge, + const gswitch *switch_stmt) { - if (!m_lower_bound.m_constant) - return false; + /* Get the ranges for each case label. */ + auto_vec case_ranges_vec + (gimple_switch_num_labels (switch_stmt)); - return compare_constants (rhs_const, - m_lower_bound.m_closed ? LT_EXPR : LE_EXPR, - m_lower_bound.m_constant).is_true (); + for (tree case_label : edge.get_case_labels ()) + { + /* Get the ranges for this case label. */ + const bounded_ranges *case_ranges + = make_case_label_ranges (switch_stmt, case_label); + case_ranges_vec.quick_push (case_ranges); + } + + /* Combine all the ranges for each case label into a single collection + of ranges. */ + const bounded_ranges *all_cases_ranges + = get_or_create_union (case_ranges_vec); + return all_cases_ranges; } -/* Return true if RHS_CONST is above the upper bound of this range. */ +/* Get the bounded_ranges instance for CASE_LABEL within + SWITCH_STMT. */ -bool -range::above_upper_bound (tree rhs_const) const +const bounded_ranges * +bounded_ranges_manager:: +make_case_label_ranges (const gswitch *switch_stmt, + tree case_label) { - if (!m_upper_bound.m_constant) - return false; + gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); + tree lower_bound = CASE_LOW (case_label); + tree upper_bound = CASE_HIGH (case_label); + if (lower_bound) + { + if (upper_bound) + /* Range. */ + return get_or_create_range (lower_bound, upper_bound); + else + /* Single-value. */ + return get_or_create_point (lower_bound); + } + else + { + /* The default case. + Add exclusions based on the other cases. */ + auto_vec other_case_ranges + (gimple_switch_num_labels (switch_stmt)); + for (unsigned other_idx = 1; + other_idx < gimple_switch_num_labels (switch_stmt); + other_idx++) + { + tree other_label = gimple_switch_label (switch_stmt, + other_idx); + const bounded_ranges *other_ranges + = make_case_label_ranges (switch_stmt, other_label); + other_case_ranges.quick_push (other_ranges); + } + const bounded_ranges *other_cases_ranges + = get_or_create_union (other_case_ranges); + tree type = TREE_TYPE (gimple_switch_index (switch_stmt)); + return get_or_create_inverse (other_cases_ranges, type); + } +} - return compare_constants (rhs_const, - m_upper_bound.m_closed ? GT_EXPR : GE_EXPR, - m_upper_bound.m_constant).is_true (); +/* Dump the number of objects of each class that were managed by this + manager to LOGGER. + If SHOW_OBJS is true, also dump the objects themselves. */ + +void +bounded_ranges_manager::log_stats (logger *logger, bool show_objs) const +{ + LOG_SCOPE (logger); + logger->log (" # %s: %li", "ranges", m_map.elements ()); + if (!show_objs) + return; + + auto_vec vec_objs (m_map.elements ()); + for (const auto &iter : m_map) + vec_objs.quick_push (iter.second); + vec_objs.qsort + ([](const void *p1, const void *p2) -> int + { + const bounded_ranges *br1 = *(const bounded_ranges * const *)p1; + const bounded_ranges *br2 = *(const bounded_ranges * const *)p2; + return bounded_ranges::cmp (br1, br2); + }); + + for (const auto &iter : vec_objs) + { + logger->start_log_line (); + pretty_printer *pp = logger->get_printer (); + pp_string (pp, " "); + iter->dump_to_pp (pp, true); + logger->end_log_line (); + } } /* class equiv_class. */ @@ -576,6 +1294,49 @@ constraint::implied_by (const constraint &other, return false; } +/* class bounded_ranges_constraint. */ + +void +bounded_ranges_constraint::print (pretty_printer *pp, + const constraint_manager &cm) const +{ + m_ec_id.print (pp); + pp_string (pp, ": "); + m_ec_id.get_obj (cm).print (pp); + pp_string (pp, ": "); + m_ranges->dump_to_pp (pp, true); +} + +json::object * +bounded_ranges_constraint::to_json () const +{ + json::object *con_obj = new json::object (); + + con_obj->set ("ec", new json::integer_number (m_ec_id.as_int ())); + con_obj->set ("ranges", m_ranges->to_json ()); + + return con_obj; +} + +bool +bounded_ranges_constraint:: +operator== (const bounded_ranges_constraint &other) const +{ + if (m_ec_id != other.m_ec_id) + return false; + + /* We can compare by pointer, since the bounded_ranges_manager + consolidates instances. */ + return m_ranges == other.m_ranges; +} + +void +bounded_ranges_constraint::add_to_hash (inchash::hash *hstate) const +{ + hstate->add_int (m_ec_id.m_idx); + hstate->merge_hash (m_ranges->get_hash ()); +} + /* class equiv_class_id. */ /* Get the underlying equiv_class for this ID from CM. */ @@ -612,6 +1373,7 @@ equiv_class_id::print (pretty_printer *pp) const constraint_manager::constraint_manager (const constraint_manager &other) : m_equiv_classes (other.m_equiv_classes.length ()), m_constraints (other.m_constraints.length ()), + m_bounded_ranges_constraints (other.m_bounded_ranges_constraints.length ()), m_mgr (other.m_mgr) { int i; @@ -621,6 +1383,8 @@ constraint_manager::constraint_manager (const constraint_manager &other) constraint *c; FOR_EACH_VEC_ELT (other.m_constraints, i, c) m_constraints.quick_push (*c); + for (const auto &iter : other.m_bounded_ranges_constraints) + m_bounded_ranges_constraints.quick_push (iter); } /* constraint_manager's assignment operator. */ @@ -630,6 +1394,7 @@ constraint_manager::operator= (const constraint_manager &other) { gcc_assert (m_equiv_classes.length () == 0); gcc_assert (m_constraints.length () == 0); + gcc_assert (m_bounded_ranges_constraints.length () == 0); int i; equiv_class *ec; @@ -640,6 +1405,8 @@ constraint_manager::operator= (const constraint_manager &other) m_constraints.reserve (other.m_constraints.length ()); FOR_EACH_VEC_ELT (other.m_constraints, i, c) m_constraints.quick_push (*c); + for (const auto &iter : other.m_bounded_ranges_constraints) + m_bounded_ranges_constraints.quick_push (iter); return *this; } @@ -658,6 +1425,8 @@ constraint_manager::hash () const hstate.merge_hash (ec->hash ()); FOR_EACH_VEC_ELT (m_constraints, i, c) hstate.merge_hash (c->hash ()); + for (const auto &iter : m_bounded_ranges_constraints) + iter.add_to_hash (&hstate); return hstate.end (); } @@ -670,6 +1439,9 @@ constraint_manager::operator== (const constraint_manager &other) const return false; if (m_constraints.length () != other.m_constraints.length ()) return false; + if (m_bounded_ranges_constraints.length () + != other.m_bounded_ranges_constraints.length ()) + return false; int i; equiv_class *ec; @@ -684,6 +1456,13 @@ constraint_manager::operator== (const constraint_manager &other) const if (!(*c == other.m_constraints[i])) return false; + for (unsigned i = 0; i < m_bounded_ranges_constraints.length (); i++) + { + if (m_bounded_ranges_constraints[i] + != other.m_bounded_ranges_constraints[i]) + return false; + } + return true; } @@ -711,6 +1490,18 @@ constraint_manager::print (pretty_printer *pp) const pp_string (pp, " && "); c->print (pp, *this); } + if (m_bounded_ranges_constraints.length ()) + { + pp_string (pp, " | "); + i = 0; + for (const auto &iter : m_bounded_ranges_constraints) + { + if (i > 0) + pp_string (pp, " && "); + iter.print (pp, *this); + i++; + } + } pp_printf (pp, "}"); } @@ -762,6 +1553,30 @@ constraint_manager::dump_to_pp (pretty_printer *pp, bool multiline) const } if (!multiline) pp_string (pp, "}"); + if (m_bounded_ranges_constraints.length ()) + { + if (multiline) + pp_string (pp, " "); + pp_string (pp, "ranges:"); + if (multiline) + pp_newline (pp); + else + pp_string (pp, "{"); + i = 0; + for (const auto &iter : m_bounded_ranges_constraints) + { + if (multiline) + pp_string (pp, " "); + else if (i > 0) + pp_string (pp, " && "); + iter.print (pp, *this); + if (multiline) + pp_newline (pp); + i++; + } + if (!multiline) + pp_string (pp, "}"); + } } /* Dump a multiline representation of this constraint_manager to FP. */ @@ -818,6 +1633,14 @@ constraint_manager::to_json () const cm_obj->set ("constraints", con_arr); } + /* m_bounded_ranges_constraints. */ + { + json::array *con_arr = new json::array (); + for (const auto &c : m_bounded_ranges_constraints) + con_arr->append (c.to_json ()); + cm_obj->set ("bounded_ranges_constraints", con_arr); + } + return cm_obj; } @@ -936,6 +1759,8 @@ constraint_manager::add_unknown_constraint (equiv_class_id lhs_ec_id, if (final_ec != old_ec) m_equiv_classes[rhs_ec_id.m_idx] = final_ec; delete old_ec; + if (lhs_ec_id == final_ec_id) + lhs_ec_id = rhs_ec_id; /* Update the constraints. */ constraint *c; @@ -955,6 +1780,14 @@ constraint_manager::add_unknown_constraint (equiv_class_id lhs_ec_id, if (c->m_rhs == final_ec_id) c->m_rhs = rhs_ec_id; } + bounded_ranges_constraint *brc; + FOR_EACH_VEC_ELT (m_bounded_ranges_constraints, i, brc) + { + if (brc->m_ec_id == rhs_ec_id) + brc->m_ec_id = lhs_ec_id; + if (brc->m_ec_id == final_ec_id) + brc->m_ec_id = rhs_ec_id; + } /* We may now have self-comparisons due to the merger; these constraints should be removed. */ @@ -1008,6 +1841,8 @@ constraint_manager::add_constraint_internal (equiv_class_id lhs_id, /* Add the constraint. */ m_constraints.safe_push (new_c); + /* We don't yet update m_bounded_ranges_constraints here yet. */ + if (!flag_analyzer_transitivity) return; @@ -1141,6 +1976,80 @@ constraint_manager::add_constraint_internal (equiv_class_id lhs_id, } } +/* Attempt to add the constraint that SVAL is within RANGES to this + constraint_manager. + + Return true if the constraint was successfully added (or is already + known to be true). + Return false if the constraint contradicts existing knowledge. */ + +bool +constraint_manager::add_bounded_ranges (const svalue *sval, + const bounded_ranges *ranges) +{ + sval = sval->unwrap_any_unmergeable (); + + /* Nothing can be known about unknown/poisoned values. */ + if (!sval->can_have_associated_state_p ()) + /* Not a contradiction. */ + return true; + + /* If SVAL is a constant, then we can look at RANGES directly. */ + if (tree cst = sval->maybe_get_constant ()) + { + /* If the ranges contain CST, then it's a successful no-op; + otherwise it's a contradiction. */ + return ranges->contain_p (cst); + } + + equiv_class_id ec_id = get_or_add_equiv_class (sval); + + /* If the EC has a constant, it's either true or false. */ + const equiv_class &ec = ec_id.get_obj (*this); + if (tree ec_cst = ec.get_any_constant ()) + { + if (ranges->contain_p (ec_cst)) + /* We already have SVAL == EC_CST, within RANGES, so + we can discard RANGES and succeed. */ + return true; + else + /* We already have SVAL == EC_CST, not within RANGES, so + we can reject RANGES as a contradiction. */ + return false; + } + + /* We have at most one per ec_id. */ + /* Iterate through each range in RANGES. */ + for (auto iter : m_bounded_ranges_constraints) + { + if (iter.m_ec_id == ec_id) + { + /* Update with intersection, or fail if empty. */ + bounded_ranges_manager *mgr = get_range_manager (); + const bounded_ranges *intersection + = mgr->get_or_create_intersection (iter.m_ranges, ranges); + if (intersection->empty_p ()) + { + /* No intersection; fail. */ + return false; + } + else + { + /* Update with intersection; succeed. */ + iter.m_ranges = intersection; + validate (); + return true; + } + } + } + m_bounded_ranges_constraints.safe_push + (bounded_ranges_constraint (ec_id, ranges)); + + validate (); + + return true; +} + /* Look for SVAL within the equivalence classes of this constraint_manager; if found, return true, writing the id to *OUT if OUT is non-NULL, otherwise return false. */ @@ -1279,6 +2188,8 @@ constraint_manager::eval_condition (equiv_class_id lhs_ec, } } + /* We don't use m_bounded_ranges_constraints here yet. */ + return tristate (tristate::TS_UNKNOWN); } @@ -1404,6 +2315,12 @@ constraint_manager::eval_condition (equiv_class_id lhs_ec, } } } + + bounded_ranges_manager *mgr = get_range_manager (); + for (const auto &iter : m_bounded_ranges_constraints) + if (iter.m_ec_id == lhs_ec) + return iter.m_ranges->eval_condition (op, rhs_const, mgr); + /* Look at existing bounds on LHS_EC. */ range lhs_bounds = get_ec_bounds (lhs_ec); return lhs_bounds.eval_condition (op, rhs_const); @@ -1552,6 +2469,29 @@ constraint_manager::purge (const PurgeCriteria &p, purge_stats *stats) con_idx++; } } + + /* Update bounded_ranges_constraint instances. */ + for (unsigned r_idx = 0; + r_idx < m_bounded_ranges_constraints.length (); ) + { + bounded_ranges_constraint *brc + = &m_bounded_ranges_constraints[r_idx]; + + /* Remove if it refers to the deleted EC. */ + if (brc->m_ec_id == ec_idx) + { + m_bounded_ranges_constraints.ordered_remove (r_idx); + if (stats) + stats->m_num_bounded_ranges_constraints++; + } + else + { + /* Renumber any EC ids that refer to ECs that have + had their idx changed. */ + brc->m_ec_id.update_for_removal (ec_idx); + r_idx++; + } + } } else ec_idx++; @@ -1610,6 +2550,17 @@ constraint_manager::purge (const PurgeCriteria &p, purge_stats *stats) c->m_lhs.update_for_removal (ec_idx); c->m_rhs.update_for_removal (ec_idx); } + + /* Likewise for m_bounded_ranges_constraints. */ + for (unsigned r_idx = 0; + r_idx < m_bounded_ranges_constraints.length (); + r_idx++) + { + bounded_ranges_constraint *brc + = &m_bounded_ranges_constraints[r_idx]; + brc->m_ec_id.update_for_removal (ec_idx); + } + continue; } } @@ -1751,6 +2702,9 @@ constraint_manager::canonicalize () used_ecs.add (m_equiv_classes[c->m_rhs.as_int ()]); } + for (const auto &iter : m_bounded_ranges_constraints) + used_ecs.add (m_equiv_classes[iter.m_ec_id.as_int ()]); + /* Purge unused ECs: those that aren't used by constraints and that effectively have only one svalue (either in m_constant or in m_vars). */ @@ -1791,6 +2745,9 @@ constraint_manager::canonicalize () ec_id_map.update (&c->m_rhs); } + for (auto &iter : m_bounded_ranges_constraints) + ec_id_map.update (&iter.m_ec_id); + /* Finally, sort the constraints. */ m_constraints.qsort (constraint_cmp); } @@ -1835,6 +2792,32 @@ public: } } + void on_ranges (const svalue *lhs_sval, + const bounded_ranges *ranges) FINAL OVERRIDE + { + for (const auto &iter : m_cm_b->m_bounded_ranges_constraints) + { + const equiv_class &ec_rhs = iter.m_ec_id.get_obj (*m_cm_b); + for (unsigned i = 0; i < ec_rhs.m_vars.length (); i++) + { + const svalue *rhs_sval = ec_rhs.m_vars[i]; + if (lhs_sval == rhs_sval) + { + /* Union of the two ranges. */ + auto_vec pair (2); + pair.quick_push (ranges); + pair.quick_push (iter.m_ranges); + bounded_ranges_manager *ranges_mgr + = m_cm_b->get_range_manager (); + const bounded_ranges *union_ + = ranges_mgr->get_or_create_union (pair); + bool sat = m_out->add_bounded_ranges (lhs_sval, union_); + gcc_assert (sat); + } + } + } + } + private: const constraint_manager *m_cm_b; constraint_manager *m_out; @@ -1908,6 +2891,16 @@ constraint_manager::for_each_fact (fact_visitor *visitor) const visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_vars[j]); } } + + for (const auto &iter : m_bounded_ranges_constraints) + { + const equiv_class &ec_lhs = iter.m_ec_id.get_obj (*this); + for (unsigned i = 0; i < ec_lhs.m_vars.length (); i++) + { + const svalue *lhs_sval = ec_lhs.m_vars[i]; + visitor->on_ranges (lhs_sval, iter.m_ranges); + } + } } /* Assert that this object is valid. */ @@ -1945,10 +2938,22 @@ constraint_manager::validate () const FOR_EACH_VEC_ELT (m_constraints, i, c) { gcc_assert (!c->m_lhs.null_p ()); - gcc_assert (c->m_lhs.as_int () <= (int)m_equiv_classes.length ()); + gcc_assert (c->m_lhs.as_int () < (int)m_equiv_classes.length ()); gcc_assert (!c->m_rhs.null_p ()); - gcc_assert (c->m_rhs.as_int () <= (int)m_equiv_classes.length ()); + gcc_assert (c->m_rhs.as_int () < (int)m_equiv_classes.length ()); } + + for (const auto &iter : m_bounded_ranges_constraints) + { + gcc_assert (!iter.m_ec_id.null_p ()); + gcc_assert (iter.m_ec_id.as_int () < (int)m_equiv_classes.length ()); + } +} + +bounded_ranges_manager * +constraint_manager::get_range_manager () const +{ + return m_mgr->get_range_manager (); } #if CHECKING_P @@ -2696,6 +3701,318 @@ test_many_constants () } } +/* Implementation detail of ASSERT_DUMP_BOUNDED_RANGES_EQ. */ + +static void +assert_dump_bounded_range_eq (const location &loc, + const bounded_range &range, + const char *expected) +{ + auto_fix_quotes sentinel; + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + range.dump_to_pp (&pp, false); + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected); +} + +/* Assert that BR.dump (false) is EXPECTED. */ + +#define ASSERT_DUMP_BOUNDED_RANGE_EQ(BR, EXPECTED) \ + SELFTEST_BEGIN_STMT \ + assert_dump_bounded_range_eq ((SELFTEST_LOCATION), (BR), (EXPECTED)); \ + SELFTEST_END_STMT + +/* Verify that bounded_range works as expected. */ + +static void +test_bounded_range () +{ + tree u8_0 = build_int_cst (unsigned_char_type_node, 0); + tree u8_1 = build_int_cst (unsigned_char_type_node, 1); + tree u8_64 = build_int_cst (unsigned_char_type_node, 64); + tree u8_128 = build_int_cst (unsigned_char_type_node, 128); + tree u8_255 = build_int_cst (unsigned_char_type_node, 255); + + tree s8_0 = build_int_cst (signed_char_type_node, 0); + tree s8_1 = build_int_cst (signed_char_type_node, 1); + tree s8_2 = build_int_cst (signed_char_type_node, 2); + + bounded_range br_u8_0 (u8_0, u8_0); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_u8_0, "0"); + ASSERT_TRUE (br_u8_0.contains_p (u8_0)); + ASSERT_FALSE (br_u8_0.contains_p (u8_1)); + ASSERT_TRUE (br_u8_0.contains_p (s8_0)); + ASSERT_FALSE (br_u8_0.contains_p (s8_1)); + + bounded_range br_u8_0_1 (u8_0, u8_1); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_u8_0_1, "[0, 1]"); + + bounded_range tmp (NULL_TREE, NULL_TREE); + ASSERT_TRUE (br_u8_0.intersects_p (br_u8_0_1, &tmp)); + ASSERT_DUMP_BOUNDED_RANGE_EQ (tmp, "0"); + + bounded_range br_u8_64_128 (u8_64, u8_128); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_u8_64_128, "[64, 128]"); + + ASSERT_FALSE (br_u8_0.intersects_p (br_u8_64_128, NULL)); + ASSERT_FALSE (br_u8_64_128.intersects_p (br_u8_0, NULL)); + + bounded_range br_u8_128_255 (u8_128, u8_255); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_u8_128_255, "[128, 255]"); + ASSERT_TRUE (br_u8_128_255.intersects_p (br_u8_64_128, &tmp)); + ASSERT_DUMP_BOUNDED_RANGE_EQ (tmp, "128"); + + bounded_range br_s8_2 (s8_2, s8_2); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_s8_2, "2"); + bounded_range br_s8_2_u8_255 (s8_2, u8_255); + ASSERT_DUMP_BOUNDED_RANGE_EQ (br_s8_2_u8_255, "[2, 255]"); +} + +/* Implementation detail of ASSERT_DUMP_BOUNDED_RANGES_EQ. */ + +static void +assert_dump_bounded_ranges_eq (const location &loc, + const bounded_ranges *ranges, + const char *expected) +{ + auto_fix_quotes sentinel; + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + ranges->dump_to_pp (&pp, false); + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected); +} + +/* Implementation detail of ASSERT_DUMP_BOUNDED_RANGES_EQ. */ + +static void +assert_dump_bounded_ranges_eq (const location &loc, + const bounded_ranges &ranges, + const char *expected) +{ + auto_fix_quotes sentinel; + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + ranges.dump_to_pp (&pp, false); + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected); +} + +/* Assert that BRS.dump (false) is EXPECTED. */ + +#define ASSERT_DUMP_BOUNDED_RANGES_EQ(BRS, EXPECTED) \ + SELFTEST_BEGIN_STMT \ + assert_dump_bounded_ranges_eq ((SELFTEST_LOCATION), (BRS), (EXPECTED)); \ + SELFTEST_END_STMT + +/* Verify that the bounded_ranges class works as expected. */ + +static void +test_bounded_ranges () +{ + bounded_ranges_manager mgr; + + tree ch0 = build_int_cst (unsigned_char_type_node, 0); + tree ch1 = build_int_cst (unsigned_char_type_node, 1); + tree ch2 = build_int_cst (unsigned_char_type_node, 2); + tree ch3 = build_int_cst (unsigned_char_type_node, 3); + tree ch128 = build_int_cst (unsigned_char_type_node, 128); + tree ch129 = build_int_cst (unsigned_char_type_node, 129); + tree ch254 = build_int_cst (unsigned_char_type_node, 254); + tree ch255 = build_int_cst (unsigned_char_type_node, 255); + + const bounded_ranges *empty = mgr.get_or_create_empty (); + ASSERT_DUMP_BOUNDED_RANGES_EQ (empty, "{}"); + + const bounded_ranges *point0 = mgr.get_or_create_point (ch0); + ASSERT_DUMP_BOUNDED_RANGES_EQ (point0, "{0}"); + + const bounded_ranges *point1 = mgr.get_or_create_point (ch1); + ASSERT_DUMP_BOUNDED_RANGES_EQ (point1, "{1}"); + + const bounded_ranges *point2 = mgr.get_or_create_point (ch2); + ASSERT_DUMP_BOUNDED_RANGES_EQ (point2, "{2}"); + + const bounded_ranges *range0_128 = mgr.get_or_create_range (ch0, ch128); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range0_128, "{[0, 128]}"); + + const bounded_ranges *range0_255 = mgr.get_or_create_range (ch0, ch255); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range0_255, "{[0, 255]}"); + + ASSERT_FALSE (empty->contain_p (ch0)); + ASSERT_FALSE (empty->contain_p (ch1)); + ASSERT_FALSE (empty->contain_p (ch255)); + + ASSERT_TRUE (point0->contain_p (ch0)); + ASSERT_FALSE (point0->contain_p (ch1)); + ASSERT_FALSE (point0->contain_p (ch255)); + + ASSERT_FALSE (point1->contain_p (ch0)); + ASSERT_TRUE (point1->contain_p (ch1)); + ASSERT_FALSE (point0->contain_p (ch255)); + + ASSERT_TRUE (range0_128->contain_p (ch0)); + ASSERT_TRUE (range0_128->contain_p (ch1)); + ASSERT_TRUE (range0_128->contain_p (ch128)); + ASSERT_FALSE (range0_128->contain_p (ch129)); + ASSERT_FALSE (range0_128->contain_p (ch254)); + ASSERT_FALSE (range0_128->contain_p (ch255)); + + const bounded_ranges *inv0_128 + = mgr.get_or_create_inverse (range0_128, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (inv0_128, "{[129, 255]}"); + + const bounded_ranges *range128_129 = mgr.get_or_create_range (ch128, ch129); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range128_129, "{[128, 129]}"); + + const bounded_ranges *inv128_129 + = mgr.get_or_create_inverse (range128_129, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (inv128_129, "{[0, 127], [130, 255]}"); + + /* Intersection. */ + { + /* Intersection of disjoint ranges should be empty set. */ + const bounded_ranges *intersect0_1 + = mgr.get_or_create_intersection (point0, point1); + ASSERT_DUMP_BOUNDED_RANGES_EQ (intersect0_1, "{}"); + } + + /* Various tests of "union of ranges". */ + { + { + /* Touching points should be merged into a range. */ + auto_vec v; + v.safe_push (point0); + v.safe_push (point1); + const bounded_ranges *union_0_and_1 = mgr.get_or_create_union (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (union_0_and_1, "{[0, 1]}"); + } + + { + /* Overlapping and out-of-order. */ + auto_vec v; + v.safe_push (inv0_128); // {[129, 255]} + v.safe_push (range128_129); + const bounded_ranges *union_129_255_and_128_129 + = mgr.get_or_create_union (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (union_129_255_and_128_129, "{[128, 255]}"); + } + + { + /* Union of R and inverse(R) should be full range of type. */ + auto_vec v; + v.safe_push (range128_129); + v.safe_push (inv128_129); + const bounded_ranges *union_ = mgr.get_or_create_union (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (union_, "{[0, 255]}"); + } + + /* Union with an endpoint. */ + { + const bounded_ranges *range2_to_255 + = mgr.get_or_create_range (ch2, ch255); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range2_to_255, "{[2, 255]}"); + auto_vec v; + v.safe_push (point0); + v.safe_push (point2); + v.safe_push (range2_to_255); + const bounded_ranges *union_ = mgr.get_or_create_union (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (union_, "{0, [2, 255]}"); + } + + /* Construct from vector of bounded_range. */ + { + auto_vec v; + v.safe_push (bounded_range (ch2, ch2)); + v.safe_push (bounded_range (ch0, ch0)); + v.safe_push (bounded_range (ch2, ch255)); + bounded_ranges br (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (&br, "{0, [2, 255]}"); + } + } + + /* Various tests of "inverse". */ + { + { + const bounded_ranges *range_1_to_3 = mgr.get_or_create_range (ch1, ch3); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range_1_to_3, "{[1, 3]}"); + const bounded_ranges *inv + = mgr.get_or_create_inverse (range_1_to_3, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (inv, "{0, [4, 255]}"); + } + { + const bounded_ranges *range_1_to_255 + = mgr.get_or_create_range (ch1, ch255); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range_1_to_255, "{[1, 255]}"); + const bounded_ranges *inv + = mgr.get_or_create_inverse (range_1_to_255, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (inv, "{0}"); + } + { + const bounded_ranges *range_0_to_254 + = mgr.get_or_create_range (ch0, ch254); + ASSERT_DUMP_BOUNDED_RANGES_EQ (range_0_to_254, "{[0, 254]}"); + const bounded_ranges *inv + = mgr.get_or_create_inverse (range_0_to_254, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (inv, "{255}"); + } + } + + /* "case 'a'-'z': case 'A-Z':" vs "default:", for ASCII. */ + { + tree ch65 = build_int_cst (unsigned_char_type_node, 65); + tree ch90 = build_int_cst (unsigned_char_type_node, 90); + + tree ch97 = build_int_cst (unsigned_char_type_node, 97); + tree ch122 = build_int_cst (unsigned_char_type_node, 122); + + const bounded_ranges *A_to_Z = mgr.get_or_create_range (ch65, ch90); + ASSERT_DUMP_BOUNDED_RANGES_EQ (A_to_Z, "{[65, 90]}"); + const bounded_ranges *a_to_z = mgr.get_or_create_range (ch97, ch122); + ASSERT_DUMP_BOUNDED_RANGES_EQ (a_to_z, "{[97, 122]}"); + auto_vec v; + v.safe_push (A_to_Z); + v.safe_push (a_to_z); + const bounded_ranges *label_ranges = mgr.get_or_create_union (v); + ASSERT_DUMP_BOUNDED_RANGES_EQ (label_ranges, "{[65, 90], [97, 122]}"); + const bounded_ranges *default_ranges + = mgr.get_or_create_inverse (label_ranges, unsigned_char_type_node); + ASSERT_DUMP_BOUNDED_RANGES_EQ (default_ranges, + "{[0, 64], [91, 96], [123, 255]}"); + } + + /* Verify ranges from ops. */ + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (EQ_EXPR, ch128), + "{128}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (NE_EXPR, ch128), + "{[0, 127], [129, 255]}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (LT_EXPR, ch128), + "{[0, 127]}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (LE_EXPR, ch128), + "{[0, 128]}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (GE_EXPR, ch128), + "{[128, 255]}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (GT_EXPR, ch128), + "{[129, 255]}"); + /* Ops at endpoints of type ranges. */ + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (LE_EXPR, ch0), + "{0}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (LT_EXPR, ch0), + "{}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (NE_EXPR, ch0), + "{[1, 255]}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (GE_EXPR, ch255), + "{255}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (GT_EXPR, ch255), + "{}"); + ASSERT_DUMP_BOUNDED_RANGES_EQ (bounded_ranges (NE_EXPR, ch255), + "{[0, 254]}"); + + /* Verify that instances are consolidated by mgr. */ + ASSERT_EQ (mgr.get_or_create_point (ch0), + mgr.get_or_create_point (ch0)); + ASSERT_NE (mgr.get_or_create_point (ch0), + mgr.get_or_create_point (ch1)); +} + /* Run the selftests in this file, temporarily overriding flag_analyzer_transitivity with TRANSITIVITY. */ @@ -2715,6 +4032,8 @@ run_constraint_manager_tests (bool transitivity) test_constraint_impl (); test_equality (); test_many_constants (); + test_bounded_range (); + test_bounded_ranges (); flag_analyzer_transitivity = saved_flag_analyzer_transitivity; } diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h index 2bb3215..0a430ea 100644 --- a/gcc/analyzer/constraint-manager.h +++ b/gcc/analyzer/constraint-manager.h @@ -64,6 +64,164 @@ struct range bound m_upper_bound; }; +/* A closed range of values with constant integer bounds + e.g. [3, 5] for the set {3, 4, 5}. */ + +struct bounded_range +{ + bounded_range (const_tree lower, const_tree upper); + + void dump_to_pp (pretty_printer *pp, bool show_types) const; + void dump (bool show_types) const; + + json::object *to_json () const; + + bool contains_p (tree cst) const; + + bool intersects_p (const bounded_range &other, + bounded_range *out) const; + + bool operator== (const bounded_range &other) const; + bool operator!= (const bounded_range &other) const + { + return !(*this == other); + } + + static int cmp (const bounded_range &a, const bounded_range &b); + + tree m_lower; + tree m_upper; + +private: + static void set_json_attr (json::object *obj, const char *name, tree value); +}; + +/* A collection of bounded_range instances, suitable + for representing the ranges on a case label within a switch + statement. */ + +struct bounded_ranges +{ +public: + typedef bounded_ranges key_t; + + bounded_ranges (const bounded_range &range); + bounded_ranges (const vec &ranges); + bounded_ranges (enum tree_code op, tree rhs_const); + + bool operator== (const bounded_ranges &other) const; + + hashval_t get_hash () const { return m_hash; } + + void dump_to_pp (pretty_printer *pp, bool show_types) const; + void dump (bool show_types) const; + + json::value *to_json () const; + + tristate eval_condition (enum tree_code op, + tree rhs_const, + bounded_ranges_manager *mgr) const; + + bool contain_p (tree cst) const; + bool empty_p () const { return m_ranges.length () == 0; } + + static int cmp (const bounded_ranges *a, const bounded_ranges *b); + +private: + void canonicalize (); + void validate () const; + + friend class bounded_ranges_manager; + + auto_vec m_ranges; + hashval_t m_hash; +}; + +} // namespace ana + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = true; +}; + +namespace ana { + +/* An object to own and consolidate bounded_ranges instances. + This also caches the mapping from switch_cfg_superedge + bounded_ranges instances, so that get_or_create_ranges_for_switch is + memoized. */ + +class bounded_ranges_manager +{ +public: + ~bounded_ranges_manager (); + + const bounded_ranges * + get_or_create_ranges_for_switch (const switch_cfg_superedge *edge, + const gswitch *switch_stmt); + + const bounded_ranges *get_or_create_empty (); + const bounded_ranges *get_or_create_point (const_tree value); + const bounded_ranges *get_or_create_range (const_tree lower_bound, + const_tree upper_bound); + const bounded_ranges * + get_or_create_union (const vec &others); + const bounded_ranges * + get_or_create_intersection (const bounded_ranges *a, + const bounded_ranges *b); + const bounded_ranges * + get_or_create_inverse (const bounded_ranges *other, tree type); + + void log_stats (logger *logger, bool show_objs) const; + +private: + const bounded_ranges * + create_ranges_for_switch (const switch_cfg_superedge &edge, + const gswitch *switch_stmt); + + const bounded_ranges * + make_case_label_ranges (const gswitch *switch_stmt, + tree case_label); + + const bounded_ranges *consolidate (bounded_ranges *); + + struct hash_traits_t : public typed_noop_remove + { + typedef bounded_ranges *key_type; + typedef bounded_ranges *value_type; + + static inline bool + equal (const key_type &k1, const key_type &k2) + { + return *k1 == *k2; + } + static inline hashval_t + hash (const key_type &k) + { + return k->get_hash (); + } + static inline bool is_empty (key_type k) { return k == NULL; } + static inline void mark_empty (key_type &k) { k = NULL; } + static inline bool is_deleted (key_type k) + { + return k == reinterpret_cast (1); + } + + static const bool empty_zero_p = true; + }; + struct traits_t : public simple_hashmap_traits + { + }; + typedef hash_map map_t; + map_t m_map; + + typedef hash_map edge_cache_t; + edge_cache_t m_edge_cache; +}; + /* An equivalence class within a constraint manager: a set of svalues that are known to all be equal to each other, together with an optional tree constant that they are equal to. */ @@ -190,6 +348,33 @@ class fact_visitor virtual void on_fact (const svalue *lhs, enum tree_code, const svalue *rhs) = 0; + virtual void on_ranges (const svalue *lhs, + const bounded_ranges *ranges) = 0; +}; + +class bounded_ranges_constraint +{ +public: + bounded_ranges_constraint (equiv_class_id ec_id, + const bounded_ranges *ranges) + : m_ec_id (ec_id), m_ranges (ranges) + { + } + + void print (pretty_printer *pp, const constraint_manager &cm) const; + + json::object *to_json () const; + + bool operator== (const bounded_ranges_constraint &other) const; + bool operator!= (const bounded_ranges_constraint &other) const + { + return !(*this == other); + } + + void add_to_hash (inchash::hash *hstate) const; + + equiv_class_id m_ec_id; + const bounded_ranges *m_ranges; }; /* A collection of equivalence classes and constraints on them. @@ -248,6 +433,9 @@ public: enum tree_code op, equiv_class_id rhs_ec_id); + bool add_bounded_ranges (const svalue *sval, + const bounded_ranges *ranges); + bool get_equiv_class_by_svalue (const svalue *sval, equiv_class_id *out) const; equiv_class_id get_or_add_equiv_class (const svalue *sval); @@ -281,8 +469,11 @@ public: void validate () const; + bounded_ranges_manager *get_range_manager () const; + auto_delete_vec m_equiv_classes; auto_vec m_constraints; + auto_vec m_bounded_ranges_constraints; private: void add_constraint_internal (equiv_class_id lhs_id, diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 77dda4d..7ffe000 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -520,8 +520,7 @@ epath_finder::process_worklist_item (feasible_worklist *worklist, gcc_assert (rc); fg->add_feasibility_problem (fnode, succ_eedge, - *rc); - delete rc; + rc); /* Give up if there have been too many infeasible edges. */ if (fg->get_num_infeasible () diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index e66ca4e..4ee9279 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -3842,7 +3842,7 @@ feasibility_problem::dump_to_pp (pretty_printer *pp) const pp_string (pp, "; rejected constraint: "); m_rc->dump_to_pp (pp); pp_string (pp, "; rmodel: "); - m_rc->m_model.dump_to_pp (pp, true, false); + m_rc->get_model ().dump_to_pp (pp, true, false); } } diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc index 675bda9..3b85896 100644 --- a/gcc/analyzer/feasible-graph.cc +++ b/gcc/analyzer/feasible-graph.cc @@ -129,7 +129,7 @@ infeasible_node::dump_dot (graphviz_out *gv, pp_string (pp, "rejected constraint:"); pp_newline (pp); - m_rc.dump_to_pp (pp); + m_rc->dump_to_pp (pp); pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true); @@ -178,12 +178,13 @@ feasible_graph::add_node (const exploded_node *enode, } /* Add an infeasible_node to this graph and an infeasible_edge connecting - to it from SRC_FNODE, capturing a failure of RC along EEDGE. */ + to it from SRC_FNODE, capturing a failure of RC along EEDGE. + Takes ownership of RC. */ void feasible_graph::add_feasibility_problem (feasible_node *src_fnode, const exploded_edge *eedge, - const rejected_constraint &rc) + rejected_constraint *rc) { infeasible_node *dst_fnode = new infeasible_node (eedge->m_dest, m_nodes.length (), rc); diff --git a/gcc/analyzer/feasible-graph.h b/gcc/analyzer/feasible-graph.h index 5a580f4..07696fa 100644 --- a/gcc/analyzer/feasible-graph.h +++ b/gcc/analyzer/feasible-graph.h @@ -115,17 +115,18 @@ class infeasible_node : public base_feasible_node { public: infeasible_node (const exploded_node *inner_node, unsigned index, - const rejected_constraint &rc) + rejected_constraint *rc) : base_feasible_node (inner_node, index), m_rc (rc) { } + ~infeasible_node () { delete m_rc; } void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE; private: - rejected_constraint m_rc; + rejected_constraint *m_rc; }; /* Base class of edge within a feasible_graph. */ @@ -192,7 +193,7 @@ class feasible_graph : public digraph void add_feasibility_problem (feasible_node *src_fnode, const exploded_edge *eedge, - const rejected_constraint &rc); + rejected_constraint *rc); exploded_path *make_epath (feasible_node *fnode) const; diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 9e4644f..1cdec1b 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -56,6 +56,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/program-point.h" #include "analyzer/store.h" #include "analyzer/region-model.h" +#include "analyzer/constraint-manager.h" #if ENABLE_ANALYZER @@ -77,7 +78,8 @@ region_model_manager::region_model_manager () m_fndecls_map (), m_labels_map (), m_globals_region (alloc_region_id (), &m_root_region), m_globals_map (), - m_store_mgr (this) + m_store_mgr (this), + m_range_mgr (new bounded_ranges_manager ()) { } @@ -142,6 +144,8 @@ region_model_manager::~region_model_manager () for (string_map_t::iterator iter = m_string_map.begin (); iter != m_string_map.end (); ++iter) delete (*iter).second; + + delete m_range_mgr; } /* Return true if C exceeds the complexity limit for svalues. */ @@ -1574,6 +1578,7 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const logger->log (" # managed dynamic regions: %i", m_managed_dynamic_regions.length ()); m_store_mgr.log_stats (logger, show_objs); + m_range_mgr->log_stats (logger, show_objs); } /* Dump the number of objects of each class that were managed by this diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index f54be14e..787f2ed 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -2773,7 +2773,7 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs, { bool sat = add_constraint (lhs, op, rhs, ctxt); if (!sat && out) - *out = new rejected_constraint (*this, lhs, op, rhs); + *out = new rejected_op_constraint (*this, lhs, op, rhs); return sat; } @@ -3329,56 +3329,15 @@ region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge, region_model_context *ctxt, rejected_constraint **out) { + bounded_ranges_manager *ranges_mgr = get_range_manager (); + const bounded_ranges *all_cases_ranges + = ranges_mgr->get_or_create_ranges_for_switch (&edge, switch_stmt); tree index = gimple_switch_index (switch_stmt); - tree case_label = edge.get_case_label (); - gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); - tree lower_bound = CASE_LOW (case_label); - tree upper_bound = CASE_HIGH (case_label); - if (lower_bound) - { - if (upper_bound) - { - /* Range. */ - if (!add_constraint (index, GE_EXPR, lower_bound, ctxt, out)) - return false; - return add_constraint (index, LE_EXPR, upper_bound, ctxt, out); - } - else - /* Single-value. */ - return add_constraint (index, EQ_EXPR, lower_bound, ctxt, out); - } - else - { - /* The default case. - Add exclusions based on the other cases. */ - for (unsigned other_idx = 1; - other_idx < gimple_switch_num_labels (switch_stmt); - other_idx++) - { - tree other_label = gimple_switch_label (switch_stmt, - other_idx); - tree other_lower_bound = CASE_LOW (other_label); - tree other_upper_bound = CASE_HIGH (other_label); - gcc_assert (other_lower_bound); - if (other_upper_bound) - { - /* Exclude this range-valued case. - For now, we just exclude the boundary values. - TODO: exclude the values within the region. */ - if (!add_constraint (index, NE_EXPR, other_lower_bound, - ctxt, out)) - return false; - if (!add_constraint (index, NE_EXPR, other_upper_bound, - ctxt, out)) - return false; - } - else - /* Exclude this single-valued case. */ - if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt, out)) - return false; - } - return true; - } + const svalue *index_sval = get_rvalue (index, ctxt); + bool sat = m_constraints->add_bounded_ranges (index_sval, all_cases_ranges); + if (!sat && out) + *out = new rejected_ranges_constraint (*this, index, all_cases_ranges); + return sat; } /* Apply any constraints due to an exception being thrown at LAST_STMT. @@ -3860,10 +3819,10 @@ debug (const region_model &rmodel) rmodel.dump (false); } -/* struct rejected_constraint. */ +/* class rejected_op_constraint : public rejected_constraint. */ void -rejected_constraint::dump_to_pp (pretty_printer *pp) const +rejected_op_constraint::dump_to_pp (pretty_printer *pp) const { region_model m (m_model); const svalue *lhs_sval = m.get_rvalue (m_lhs, NULL); @@ -3873,6 +3832,18 @@ rejected_constraint::dump_to_pp (pretty_printer *pp) const rhs_sval->dump_to_pp (pp, true); } +/* class rejected_ranges_constraint : public rejected_constraint. */ + +void +rejected_ranges_constraint::dump_to_pp (pretty_printer *pp) const +{ + region_model m (m_model); + const svalue *sval = m.get_rvalue (m_expr, NULL); + sval->dump_to_pp (pp, true); + pp_string (pp, " in "); + m_ranges->dump_to_pp (pp, true); +} + /* class engine. */ /* Dump the managed objects by class to LOGGER, and the per-class totals. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index a734f9f..f2c82b0 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -189,6 +189,7 @@ struct purge_stats m_num_regions (0), m_num_equiv_classes (0), m_num_constraints (0), + m_num_bounded_ranges_constraints (0), m_num_client_items (0) {} @@ -196,6 +197,7 @@ struct purge_stats int m_num_regions; int m_num_equiv_classes; int m_num_constraints; + int m_num_bounded_ranges_constraints; int m_num_client_items; }; @@ -320,6 +322,7 @@ public: unsigned alloc_region_id () { return m_next_region_id++; } store_manager *get_store_manager () { return &m_store_mgr; } + bounded_ranges_manager *get_range_manager () const { return m_range_mgr; } /* Dynamically-allocated region instances. The number of these within the analysis can grow arbitrarily. @@ -456,6 +459,8 @@ private: store_manager m_store_mgr; + bounded_ranges_manager *m_range_mgr; + /* "Dynamically-allocated" region instances. The number of these within the analysis can grow arbitrarily. They are still owned by the manager. */ @@ -698,6 +703,10 @@ class region_model void unset_dynamic_extents (const region *reg); region_model_manager *get_manager () const { return m_mgr; } + bounded_ranges_manager *get_range_manager () const + { + return m_mgr->get_range_manager (); + } void unbind_region_and_descendents (const region *reg, enum poison_kind pkind); @@ -945,21 +954,54 @@ struct model_merger /* A record that can (optionally) be written out when region_model::add_constraint fails. */ -struct rejected_constraint +class rejected_constraint { - rejected_constraint (const region_model &model, - tree lhs, enum tree_code op, tree rhs) - : m_model (model), m_lhs (lhs), m_op (op), m_rhs (rhs) - {} +public: + virtual ~rejected_constraint () {} + virtual void dump_to_pp (pretty_printer *pp) const = 0; - void dump_to_pp (pretty_printer *pp) const; + const region_model &get_model () const { return m_model; } + +protected: + rejected_constraint (const region_model &model) + : m_model (model) + {} region_model m_model; +}; + +class rejected_op_constraint : public rejected_constraint +{ +public: + rejected_op_constraint (const region_model &model, + tree lhs, enum tree_code op, tree rhs) + : rejected_constraint (model), + m_lhs (lhs), m_op (op), m_rhs (rhs) + {} + + void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE; + tree m_lhs; enum tree_code m_op; tree m_rhs; }; +class rejected_ranges_constraint : public rejected_constraint +{ +public: + rejected_ranges_constraint (const region_model &model, + tree expr, const bounded_ranges *ranges) + : rejected_constraint (model), + m_expr (expr), m_ranges (ranges) + {} + + void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE; + +private: + tree m_expr; + const bounded_ranges *m_ranges; +}; + /* A bundle of state. */ class engine diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 66ef765..85acf44 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -50,6 +50,7 @@ along with GCC; see the file COPYING3. If not see #include "cgraph.h" #include "cfg.h" #include "digraph.h" +#include "tree-cfg.h" #include "analyzer/supergraph.h" #include "analyzer/analyzer-logging.h" @@ -246,7 +247,7 @@ supergraph::supergraph (logger *logger) supernode *dest_supernode = *m_bb_to_initial_node.get (dest_cfg_block); cfg_superedge *cfg_sedge - = add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx); + = add_cfg_edge (src_supernode, dest_supernode, cfg_edge); m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge); } } @@ -505,17 +506,16 @@ supergraph::add_node (function *fun, basic_block bb, gcall *returning_call, adding it to this supergraph. If the edge is for a switch statement, create a switch_cfg_superedge - subclass using IDX (the index of E within the out-edges from SRC's - underlying basic block). */ + subclass. */ cfg_superedge * -supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx) +supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e) { /* Special-case switch edges. */ gimple *stmt = src->get_last_stmt (); cfg_superedge *new_edge; if (stmt && stmt->code == GIMPLE_SWITCH) - new_edge = new switch_cfg_superedge (src, dest, e, idx); + new_edge = new switch_cfg_superedge (src, dest, e); else new_edge = new cfg_superedge (src, dest, e); add_edge (new_edge); @@ -1072,6 +1072,23 @@ cfg_superedge::get_phi_arg (const gphi *phi) const return gimple_phi_arg_def (phi, index); } +switch_cfg_superedge::switch_cfg_superedge (supernode *src, + supernode *dst, + ::edge e) +: cfg_superedge (src, dst, e) +{ + /* Populate m_case_labels with all cases which go to DST. */ + const gswitch *gswitch = get_switch_stmt (); + for (unsigned i = 0; i < gimple_switch_num_labels (gswitch); i++) + { + tree case_ = gimple_switch_label (gswitch, i); + basic_block bb = label_to_block (src->get_function (), + CASE_LABEL (case_)); + if (bb == dst->m_bb) + m_case_labels.safe_push (case_); + } +} + /* Implementation of superedge::dump_label_to_pp for CFG superedges for "switch" statements. @@ -1081,31 +1098,63 @@ void switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp, bool user_facing ATTRIBUTE_UNUSED) const { - tree case_label = get_case_label (); - gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); - tree lower_bound = CASE_LOW (case_label); - tree upper_bound = CASE_HIGH (case_label); - if (lower_bound) + if (user_facing) { - pp_printf (pp, "case "); - dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false); - if (upper_bound) + for (unsigned i = 0; i < m_case_labels.length (); ++i) { - pp_printf (pp, " ... "); - dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, false); + if (i > 0) + pp_string (pp, ", "); + tree case_label = m_case_labels[i]; + gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); + tree lower_bound = CASE_LOW (case_label); + tree upper_bound = CASE_HIGH (case_label); + if (lower_bound) + { + pp_printf (pp, "case "); + dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false); + if (upper_bound) + { + pp_printf (pp, " ... "); + dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, + false); + } + pp_printf (pp, ":"); + } + else + pp_printf (pp, "default:"); } - pp_printf (pp, ":"); } else - pp_printf (pp, "default:"); -} - -/* Get the case label for this "switch" superedge. */ - -tree -switch_cfg_superedge::get_case_label () const -{ - return gimple_switch_label (get_switch_stmt (), m_idx); + { + pp_character (pp, '{'); + for (unsigned i = 0; i < m_case_labels.length (); ++i) + { + if (i > 0) + pp_string (pp, ", "); + tree case_label = m_case_labels[i]; + gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); + tree lower_bound = CASE_LOW (case_label); + tree upper_bound = CASE_HIGH (case_label); + if (lower_bound) + { + if (upper_bound) + { + pp_character (pp, '['); + dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, + false); + pp_string (pp, ", "); + dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, + false); + pp_character (pp, ']'); + } + else + dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false); + } + else + pp_printf (pp, "default"); + } + pp_character (pp, '}'); + } } /* Implementation of superedge::dump_label_to_pp for interprocedural diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index 335f513..09a12be 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -181,7 +181,7 @@ public: private: supernode *add_node (function *fun, basic_block bb, gcall *returning_call, gimple_seq phi_nodes); - cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx); + cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e); call_superedge *add_call_superedge (supernode *src, supernode *dest, cgraph_edge *cedge); return_superedge *add_return_superedge (supernode *src, supernode *dest, @@ -539,15 +539,12 @@ is_a_helper ::test (const superedge *sedge) namespace ana { /* A subclass for edges from switch statements, retaining enough - information to identify the pertinent case, and for adding labels + information to identify the pertinent cases, and for adding labels when rendering via graphviz. */ class switch_cfg_superedge : public cfg_superedge { public: - switch_cfg_superedge (supernode *src, supernode *dst, ::edge e, int idx) - : cfg_superedge (src, dst, e), - m_idx (idx) - {} + switch_cfg_superedge (supernode *src, supernode *dst, ::edge e); const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const FINAL OVERRIDE @@ -563,10 +560,10 @@ class switch_cfg_superedge : public cfg_superedge { return as_a (m_src->get_last_stmt ()); } - tree get_case_label () const; + const vec &get_case_labels () const { return m_case_labels; } - private: - const int m_idx; +private: + auto_vec m_case_labels; }; } // namespace ana -- cgit v1.1 From 38b19c5b0805f9acfcf52430cebca025fc3cdea6 Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 24 Aug 2021 00:17:00 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index da90011..211f34c 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,184 @@ +2021-08-23 David Malcolm + + * analyzer.h (struct rejected_constraint): Convert to... + (class rejected_constraint): ...this. + (class bounded_ranges): New forward decl. + (class bounded_ranges_manager): New forward decl. + * constraint-manager.cc: Include "analyzer/analyzer-logging.h" and + "tree-pretty-print.h". + (can_plus_one_p): New. + (plus_one): New. + (can_minus_one_p): New. + (minus_one): New. + (bounded_range::bounded_range): New. + (dump_cst): New. + (bounded_range::dump_to_pp): New. + (bounded_range::dump): New. + (bounded_range::to_json): New. + (bounded_range::set_json_attr): New. + (bounded_range::contains_p): New. + (bounded_range::intersects_p): New. + (bounded_range::operator==): New. + (bounded_range::cmp): New. + (bounded_ranges::bounded_ranges): New. + (bounded_ranges::bounded_ranges): New. + (bounded_ranges::bounded_ranges): New. + (bounded_ranges::canonicalize): New. + (bounded_ranges::validate): New. + (bounded_ranges::operator==): New. + (bounded_ranges::dump_to_pp): New. + (bounded_ranges::dump): New. + (bounded_ranges::to_json): New. + (bounded_ranges::eval_condition): New. + (bounded_ranges::contain_p): New. + (bounded_ranges::cmp): New. + (bounded_ranges_manager::~bounded_ranges_manager): New. + (bounded_ranges_manager::get_or_create_empty): New. + (bounded_ranges_manager::get_or_create_point): New. + (bounded_ranges_manager::get_or_create_range): New. + (bounded_ranges_manager::get_or_create_union): New. + (bounded_ranges_manager::get_or_create_intersection): New. + (bounded_ranges_manager::get_or_create_inverse): New. + (bounded_ranges_manager::consolidate): New. + (bounded_ranges_manager::get_or_create_ranges_for_switch): New. + (bounded_ranges_manager::create_ranges_for_switch): New. + (bounded_ranges_manager::make_case_label_ranges): New. + (bounded_ranges_manager::log_stats): New. + (bounded_ranges_constraint::print): New. + (bounded_ranges_constraint::to_json): New. + (bounded_ranges_constraint::operator==): New. + (bounded_ranges_constraint::add_to_hash): New. + (constraint_manager::constraint_manager): Update for new field + m_bounded_ranges_constraints. + (constraint_manager::operator=): Likewise. + (constraint_manager::hash): Likewise. + (constraint_manager::operator==): Likewise. + (constraint_manager::print): Likewise. + (constraint_manager::dump_to_pp): Likewise. + (constraint_manager::to_json): Likewise. + (constraint_manager::add_unknown_constraint): Update the lhs_ec_id + if necessary in existing constraints when combining equivalence + classes. Add similar code for handling + m_bounded_ranges_constraints. + (constraint_manager::add_constraint_internal): Add comment. + (constraint_manager::add_bounded_ranges): New. + (constraint_manager::eval_condition): Use new field + m_bounded_ranges_constraints. + (constraint_manager::purge): Update bounded_ranges_constraint + instances. + (constraint_manager::canonicalize): Update for new field. + (merger_fact_visitor::on_ranges): New. + (constraint_manager::for_each_fact): Use new field + m_bounded_ranges_constraints. + (constraint_manager::validate): Fix off-by-one error needed due + to bug fixed above in add_unknown_constraint. Validate the EC IDs + in m_bounded_ranges_constraints. + (constraint_manager::get_range_manager): New. + (selftest::assert_dump_bounded_range_eq): New. + (ASSERT_DUMP_BOUNDED_RANGE_EQ): New. + (selftest::test_bounded_range): New. + (selftest::assert_dump_bounded_ranges_eq): New. + (ASSERT_DUMP_BOUNDED_RANGES_EQ): New. + (selftest::test_bounded_ranges): New. + (selftest::run_constraint_manager_tests): Call the new selftests. + * constraint-manager.h (struct bounded_range): New. + (struct bounded_ranges): New. + (template <> struct default_hash_traits): New. + (class bounded_ranges_manager): New. + (fact_visitor::on_ranges): New pure virtual function. + (class bounded_ranges_constraint): New. + (constraint_manager::add_bounded_ranges): New decl. + (constraint_manager::get_range_manager): New decl. + (constraint_manager::m_bounded_ranges_constraints): New field. + * diagnostic-manager.cc (epath_finder::process_worklist_item): + Transfer ownership of rc to add_feasibility_problem. + * engine.cc (feasibility_problem::dump_to_pp): Use get_model. + * feasible-graph.cc (infeasible_node::dump_dot): Update for + conversion of m_rc to a pointer. + (feasible_graph::add_feasibility_problem): Pass RC by pointer and + take ownership. + * feasible-graph.h (infeasible_node::infeasible_node): Pass RC by + pointer and take ownership. + (infeasible_node::~infeasible_node): New. + (infeasible_node::m_rc): Convert to a pointer. + (feasible_graph::add_feasibility_problem): Pass RC by pointer and + take ownership. + * region-model-manager.cc: Include + "analyzer/constraint-manager.h". + (region_model_manager::region_model_manager): Initializer new + field m_range_mgr. + (region_model_manager::~region_model_manager): Delete it. + (region_model_manager::log_stats): Call log_stats on it. + * region-model.cc (region_model::add_constraint): Use new subclass + rejected_op_constraint. + (region_model::apply_constraints_for_gswitch): Reimplement using + bounded_ranges_manager. + (rejected_constraint::dump_to_pp): Convert to... + (rejected_op_constraint::dump_to_pp): ...this. + (rejected_ranges_constraint::dump_to_pp): New. + * region-model.h (struct purge_stats): Add field + m_num_bounded_ranges_constraints. + (region_model_manager::get_range_manager): New. + (region_model_manager::m_range_mgr): New. + (region_model::get_range_manager): New. + (struct rejected_constraint): Split into... + (class rejected_constraint):...this new abstract base class, + and... + (class rejected_op_constraint): ...this new concrete subclass. + (class rejected_ranges_constraint): New. + * supergraph.cc: Include "tree-cfg.h". + (supergraph::supergraph): Drop idx param from add_cfg_edge. + (supergraph::add_cfg_edge): Drop idx param. + (switch_cfg_superedge::switch_cfg_superedge): Move here from + header. Populate m_case_labels with all cases which go to DST. + (switch_cfg_superedge::dump_label_to_pp): Reimplement to use + m_case_labels. + (switch_cfg_superedge::get_case_label): Delete. + * supergraph.h (supergraphadd_cfg_edge): Drop "idx" param. + (switch_cfg_superedge::switch_cfg_superedge): Drop idx param and + move implementation to supergraph.cc. + (switch_cfg_superedge::get_case_label): Delete. + (switch_cfg_superedge::get_case_labels): New. + (switch_cfg_superedge::m_idx): Delete. + (switch_cfg_superedge::m_case_labels): New field. + +2021-08-23 David Malcolm + + PR analyzer/101875 + * sm-file.cc (file_diagnostic::describe_state_change): Handle + change.m_expr being NULL. + +2021-08-23 David Malcolm + + PR analyzer/101837 + * analyzer.cc (maybe_reconstruct_from_def_stmt): Bail if fn is + NULL, and assert that it's non-NULL before passing it to + build_call_array_loc. + +2021-08-23 David Malcolm + + PR analyzer/101962 + * region-model.cc (region_model::eval_condition_without_cm): + Refactor comparison against zero, adding a check for + POINTER_PLUS_EXPR of non-NULL. + +2021-08-23 David Malcolm + + * store.cc (bit_range::intersects_p): New overload. + (bit_range::operator-): New. + (binding_cluster::maybe_get_compound_binding): Handle the partial + overlap case. + (selftest::test_bit_range_intersects_p): Add test coverage for + new overload of bit_range::intersects_p. + * store.h (bit_range::intersects_p): New overload. + (bit_range::operator-): New. + +2021-08-23 Ankur Saini + + PR analyzer/102020 + * diagnostic-manager.cc + (diagnostic_manager::prune_for_sm_diagnostic): Fix typo. + 2021-08-21 Ankur Saini PR analyzer/101980 -- cgit v1.1 From 43a5d46feabd93ba78983919234f05f5fc9a0982 Mon Sep 17 00:00:00 2001 From: Ankur Saini Date: Wed, 25 Aug 2021 12:33:06 +0530 Subject: analyzer: Impose recursion limit on indirect calls. 2021-08-25 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/101980 * engine.cc (exploded_graph::maybe_create_dynamic_call): Don't create calls if max recursion limit is reached. --- gcc/analyzer/engine.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 4ee9279..9c604d1 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -3059,6 +3059,20 @@ exploded_graph::maybe_create_dynamic_call (const gcall *call, new_point.push_to_call_stack (sn_exit, next_point.get_supernode()); + + /* Impose a maximum recursion depth and don't analyze paths + that exceed it further. + This is something of a blunt workaround, but it only + applies to recursion (and mutual recursion), not to + general call stacks. */ + if (new_point.get_call_string ().calc_recursion_depth () + > param_analyzer_max_recursion_depth) + { + if (logger) + logger->log ("rejecting call edge: recursion limit exceeded"); + return false; + } + next_state.push_call (*this, node, call, uncertainty); if (next_state.m_valid) -- cgit v1.1 From 85d77ac4745c6263520c8fe66c0dfced8404003f Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 26 Aug 2021 00:17:03 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 211f34c..c7e8ba92 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,9 @@ +2021-08-25 Ankur Saini + + PR analyzer/101980 + * engine.cc (exploded_graph::maybe_create_dynamic_call): Don't create + calls if max recursion limit is reached. + 2021-08-23 David Malcolm * analyzer.h (struct rejected_constraint): Convert to... -- cgit v1.1 From eafa9d969237fd8f712c4b25a8c58932c01f44b4 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 30 Aug 2021 18:36:31 -0400 Subject: analyzer: support "bifurcation"; reimplement realloc [PR99260] Most of the state-management code in the analyzer involves modifying state objects in-place, which implies a single outcome. (I originally implemented in-place modification because I wanted to avoid having to create copies of state objects, and it's now very difficult to change this aspect of the analyzer's design) However, there are various special-cases such as "realloc" for which it's best to split the state into multiple outcomes. This patch adds a mechanism for "bifurcating" the analysis in places where there isn't a split in the CFG, and uses it to implement realloc, in this case treating it as having 3 possible outcomes: - failure, returning NULL - success, growing the buffer in-place without moving it - success, allocating a new buffer, copying the content of the old buffer to it, and freeing the old buffer. gcc/ChangeLog: PR analyzer/99260 * Makefile.in (ANALYZER_OBJS): Add analyzer/call-info.o. gcc/analyzer/ChangeLog: PR analyzer/99260 * analyzer.h (class custom_edge_info): New class, adapted from exploded_edge::custom_info_t. Make member functions const. Make update_model return bool, converting edge param from reference to a pointer, and adding a ctxt param. (class path_context): New class. * call-info.cc: New file. * call-info.h: New file. * engine.cc: Include "analyzer/call-info.h" and . (impl_region_model_context::impl_region_model_context): Update for new m_path_ctxt field. (impl_region_model_context::bifurcate): New. (impl_region_model_context::terminate_path): New. (impl_region_model_context::get_malloc_map): New. (impl_sm_context::impl_sm_context): Update for new m_path_ctxt field. (impl_sm_context::get_fndecl_for_call): Likewise. (impl_sm_context::set_next_state): Likewise. (impl_sm_context::warn): Likewise. (impl_sm_context::is_zero_assignment): Likewise. (impl_sm_context::get_path_context): New. (impl_sm_context::m_path_ctxt): New. (impl_region_model_context::on_condition): Update for new path_ctxt param. Handle m_enode_for_diag being NULL. (impl_region_model_context::on_phi): Update for new path_ctxt param. (exploded_node::on_stmt): Add path_ctxt param, updating ctor calls to use it as necessary. Use it to bail out after sm-handling, if needed. (exploded_node::detect_leaks): Update for new path_ctxt param. (dynamic_call_info_t::update_model): Update for conversion of exploded_edge::custom_info_t to custom_edge_info. (dynamic_call_info_t::add_events_to_path): Likewise. (rewind_info_t::update_model): Likewise. (rewind_info_t::add_events_to_path): Likewise. (exploded_edge::exploded_edge): Likewise. (exploded_graph::add_edge): Likewise. (exploded_graph::maybe_process_run_of_before_supernode_enodes): Update for new path_ctxt param. (class impl_path_context): New. (exploded_graph::process_node): Update for new path_ctxt param. Create an impl_path_context and pass it to exploded_node::on_stmt. Use it to terminate iterating stmts if terminate_path is called on it. After processing a run of stmts, query path_ctxt to potentially terminate the analysis path, and/or to "bifurcate" the analysis into multiple additional paths. (feasibility_state::maybe_update_for_edge): Update for new update_model ctxt param. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Add path_ctxt param. (impl_region_model_context::bifurcate): New. (impl_region_model_context::terminate_path): New (impl_region_model_context::get_ext_state): New. (impl_region_model_context::get_malloc_map): New. (impl_region_model_context::m_path_ctxt): New field. (exploded_node::on_stmt): Add path_ctxt param. (class exploded_edge::custom_info_t): Move to analyzer.h, renaming to custom_edge_info, and making the changes as noted in analyzer.h above. (exploded_edge::exploded_edge): Update for these changes to exploded_edge::custom_info_t. (exploded_edge::m_custom_info): Likewise. (class dynamic_call_info_t): Likewise. (class rewind_info_t): Likewise. (exploded_graph::add_edge): Likewise. * program-state.cc (program_state::on_edge): Update for new path_ctxt param. (program_state::push_call): Likewise. (program_state::returning_call): Likewise. (program_state::prune_for_point): Likewise. * region-model-impl-calls.cc: Include "analyzer/call-info.h". (call_details::get_fndecl_for_call): New. (region_model::impl_call_realloc): Reimplement. * region-model.cc (region_model::on_call_pre): Move call to impl_call_realloc to... (region_model::on_call_post): ...here. Consolidate creation of call_details instance. (noop_region_model_context::bifurcate): New. (noop_region_model_context::terminate_path): New. * region-model.h (call_details::get_call_stmt): New. (call_details::get_fndecl_for_call): New. (region_model::on_realloc_with_move): New. (region_model_context::bifurcate): New. (region_model_context::terminate_path): New. (region_model_context::get_ext_state): New. (region_model_context::get_malloc_map): New. (noop_region_model_context::bifurcate): New. (noop_region_model_context::terminate_path): New. (noop_region_model_context::get_ext_state): New. (noop_region_model_context::get_malloc_map): New. * sm-malloc.cc: Include "analyzer/program-state.h". (malloc_state_machine::on_realloc_call): Reimplement. (malloc_state_machine::on_realloc_with_move): New. (region_model::on_realloc_with_move): New. * sm-signal.cc (class signal_delivery_edge_info_t): Update for conversion from exploded_edge::custom_info_t to custom_edge_info. * sm.h (sm_context::get_path_context): New. * svalue.cc (svalue::maybe_get_constant): Call unwrap_any_unmergeable. gcc/testsuite/ChangeLog: PR analyzer/99260 * gcc.dg/analyzer/capacity-2.c: Update for changes to realloc analysis. * gcc.dg/analyzer/pr99193-1.c: Likewise. * gcc.dg/analyzer/pr99193-3.c: Likewise. * gcc.dg/analyzer/realloc-1.c: Likewise. Add test coverage for realloc of non-heap pointer, realloc from mismatching allocator, and realloc on a freed pointer. * gcc.dg/analyzer/realloc-2.c: New test. --- gcc/analyzer/analyzer.h | 51 ++++++ gcc/analyzer/call-info.cc | 162 +++++++++++++++++++ gcc/analyzer/call-info.h | 83 ++++++++++ gcc/analyzer/engine.cc | 271 +++++++++++++++++++++++++++----- gcc/analyzer/exploded-graph.h | 62 ++++---- gcc/analyzer/program-state.cc | 6 +- gcc/analyzer/region-model-impl-calls.cc | 176 ++++++++++++++++++++- gcc/analyzer/region-model.cc | 28 +++- gcc/analyzer/region-model.h | 36 +++++ gcc/analyzer/sm-malloc.cc | 136 ++++++++++++---- gcc/analyzer/sm-signal.cc | 15 +- gcc/analyzer/sm.h | 5 + gcc/analyzer/svalue.cc | 3 +- 13 files changed, 911 insertions(+), 123 deletions(-) create mode 100644 gcc/analyzer/call-info.cc create mode 100644 gcc/analyzer/call-info.h (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 05d4751..7ad1081 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -220,6 +220,57 @@ enum access_direction DIR_WRITE }; +/* Abstract base class for associating custom data with an + exploded_edge, for handling non-standard edges such as + rewinding from a longjmp, signal handlers, etc. + Also used when "bifurcating" state: splitting the execution + path in non-standard ways (e.g. for simulating the various + outcomes of "realloc"). */ + +class custom_edge_info +{ +public: + virtual ~custom_edge_info () {} + + /* Hook for making .dot label more readable. */ + virtual void print (pretty_printer *pp) const = 0; + + /* Hook for updating MODEL within exploded_path::feasible_p + and when handling bifurcation. */ + virtual bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const = 0; + + virtual void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const = 0; +}; + +/* Abstract base class for splitting state. + + Most of the state-management code in the analyzer involves + modifying state objects in-place, which assumes a single outcome. + + This class provides an escape hatch to allow for multiple outcomes + for such updates e.g. for modelling multiple outcomes from function + calls, such as the various outcomes of "realloc". */ + +class path_context +{ +public: + virtual ~path_context () {} + + /* Hook for clients to split state with a non-standard path. + Take ownership of INFO. */ + virtual void bifurcate (custom_edge_info *info) = 0; + + /* Hook for clients to terminate the standard path. */ + virtual void terminate_path () = 0; + + /* Hook for clients to determine if the standard path has been + terminated. */ + virtual bool terminate_path_p () const = 0; +}; + } // namespace ana extern bool is_special_named_call_p (const gcall *call, const char *funcname, diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc new file mode 100644 index 0000000..1d44cb8 --- /dev/null +++ b/gcc/analyzer/call-info.cc @@ -0,0 +1,162 @@ +/* Subclasses of custom_edge_info for describing outcomes of function calls. + Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "diagnostic-core.h" +#include "options.h" +#include "cgraph.h" +#include "tree-pretty-print.h" +#include "tristate.h" +#include "bitmap.h" +#include "selftest.h" +#include "function.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "ordered-hash-map.h" +#include "cfg.h" +#include "digraph.h" +#include "analyzer/supergraph.h" +#include "sbitmap.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/constraint-manager.h" +#include "diagnostic-event-id.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" +#include "analyzer/region-model-reachability.h" +#include "analyzer/analyzer-selftests.h" +#include "analyzer/program-state.h" +#include "diagnostic-path.h" +#include "analyzer/checker-path.h" +#include "analyzer/diagnostic-manager.h" +#include "alloc-pool.h" +#include "fibonacci_heap.h" +#include "shortest-paths.h" +#include "analyzer/exploded-graph.h" +#include "analyzer/call-info.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* class call_info : public custom_eedge_info_t. */ + +/* Implementation of custom_edge_info::print vfunc for call_info: + use get_desc to get a label_text, and print it to PP. */ + +void +call_info::print (pretty_printer *pp) const +{ + label_text desc (get_desc (pp_show_color (pp))); + pp_string (pp, desc.m_buffer); + desc.maybe_free (); +} + +/* Implementation of custom_edge_info::add_events_to_path vfunc for + call_info: add a custom_event using call_info::get_desc as its + description. */ + +void +call_info::add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const +{ + class call_event : public custom_event + { + public: + call_event (location_t loc, tree fndecl, int depth, + const call_info *call_info) + : custom_event (loc, fndecl, depth), + m_call_info (call_info) + {} + + label_text get_desc (bool can_colorize) const + { + return m_call_info->get_desc (can_colorize); + } + + private: + const call_info *m_call_info; + }; + + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + tree caller_fndecl = src_point.get_fndecl (); + const int stack_depth = src_point.get_stack_depth (); + + emission_path->add_event (new call_event (get_call_stmt ()->location, + caller_fndecl, + stack_depth, + this)); +} + +/* Recreate a call_details instance from this call_info. */ + +call_details +call_info::get_call_details (region_model *model, + region_model_context *ctxt) const +{ + return call_details (m_call_stmt, model, ctxt); +} + +/* call_info's ctor. + + The call_info instance will outlive the call_details instance; + call_details instances are typically created on the stack. */ + +call_info::call_info (const call_details &cd) +: m_call_stmt (cd.get_call_stmt ()), + m_fndecl (cd.get_fndecl_for_call ()) +{ + gcc_assert (m_fndecl); +} + +/* class success_call_info : public call_info. */ + +/* Implementation of call_info::get_desc vfunc for success_call_info. */ + +label_text +success_call_info::get_desc (bool can_colorize) const +{ + return make_label_text (can_colorize, "when %qE succeeds", get_fndecl ()); +} + +/* class failed_call_info : public call_info. */ + +/* Implementation of call_info::get_desc vfunc for failed_call_info. */ + +label_text +failed_call_info::get_desc (bool can_colorize) const +{ + return make_label_text (can_colorize, "when %qE fails", get_fndecl ()); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h new file mode 100644 index 0000000..369d217 --- /dev/null +++ b/gcc/analyzer/call-info.h @@ -0,0 +1,83 @@ +/* Subclasses of custom_edge_info for describing outcomes of function calls. + Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_ANALYZER_CALL_INFO_H +#define GCC_ANALYZER_CALL_INFO_H + +namespace ana { + +/* Subclass of custom_edge_info for an outcome of a call. + This is still abstract; the update_model and get_desc vfuncs must be + implemented. */ + +class call_info : public custom_edge_info +{ +public: + void print (pretty_printer *pp) const FINAL OVERRIDE; + void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const FINAL OVERRIDE; + + const gcall *get_call_stmt () const { return m_call_stmt; } + tree get_fndecl () const { return m_fndecl; } + + virtual label_text get_desc (bool can_colorize) const = 0; + + call_details get_call_details (region_model *model, + region_model_context *ctxt) const; + +protected: + call_info (const call_details &cd); + +private: + const gcall *m_call_stmt; + tree m_fndecl; +}; + +/* Subclass of call_info for a "success" outcome of a call, + adding a "when `FNDECL' succeeds" message. + This is still abstract: the custom_edge_info::update_model vfunc + must be implemented. */ + +class success_call_info : public call_info +{ +public: + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + +protected: + success_call_info (const call_details &cd) : call_info (cd) {} +}; + +/* Subclass of call_info for a "failure" outcome of a call, + adding a "when `FNDECL' fails" message. + This is still abstract: the custom_edge_info::update_model vfunc + must be implemented. */ + +class failed_call_info : public call_info +{ +public: + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + +protected: + failed_call_info (const call_details &cd) : call_info (cd) {} +}; + +} // namespace ana + +#endif /* GCC_ANALYZER_CALL_INFO_H */ diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 9c604d1..24f0931 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -62,9 +62,11 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/checker-path.h" #include "analyzer/state-purge.h" #include "analyzer/bar-chart.h" +#include "analyzer/call-info.h" #include #include "plugin.h" #include "target.h" +#include /* For an overview, see gcc/doc/analyzer.texi. */ @@ -80,6 +82,7 @@ impl_region_model_context (exploded_graph &eg, const program_state *old_state, program_state *new_state, uncertainty_t *uncertainty, + path_context *path_ctxt, const gimple *stmt, stmt_finder *stmt_finder) : m_eg (&eg), m_logger (eg.get_logger ()), @@ -89,7 +92,8 @@ impl_region_model_context (exploded_graph &eg, m_stmt (stmt), m_stmt_finder (stmt_finder), m_ext_state (eg.get_ext_state ()), - m_uncertainty (uncertainty) + m_uncertainty (uncertainty), + m_path_ctxt (path_ctxt) { } @@ -104,7 +108,8 @@ impl_region_model_context (program_state *state, m_stmt (NULL), m_stmt_finder (NULL), m_ext_state (ext_state), - m_uncertainty (uncertainty) + m_uncertainty (uncertainty), + m_path_ctxt (NULL) { } @@ -183,6 +188,37 @@ impl_region_model_context::purge_state_involving (const svalue *sval) smap->purge_state_involving (sval, m_ext_state); } +void +impl_region_model_context::bifurcate (custom_edge_info *info) +{ + if (m_path_ctxt) + m_path_ctxt->bifurcate (info); + else + delete info; +} + +void +impl_region_model_context::terminate_path () +{ + if (m_path_ctxt) + return m_path_ctxt->terminate_path (); +} + +bool +impl_region_model_context::get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) +{ + unsigned malloc_sm_idx; + if (!m_ext_state.get_sm_idx_by_name ("malloc", &malloc_sm_idx)) + return false; + + *out_smap = m_new_state->m_checker_states[malloc_sm_idx]; + *out_sm = &m_ext_state.get_sm (malloc_sm_idx); + *out_sm_idx = malloc_sm_idx; + return true; +} + /* struct setjmp_record. */ int @@ -237,12 +273,14 @@ public: program_state *new_state, const sm_state_map *old_smap, sm_state_map *new_smap, + path_context *path_ctxt, stmt_finder *stmt_finder = NULL) : sm_context (sm_idx, sm), m_logger (eg.get_logger ()), m_eg (eg), m_enode_for_diag (enode_for_diag), m_old_state (old_state), m_new_state (new_state), m_old_smap (old_smap), m_new_smap (new_smap), + m_path_ctxt (path_ctxt), m_stmt_finder (stmt_finder) { } @@ -252,7 +290,7 @@ public: tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE { impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, + (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/, NULL, call); region_model *model = m_new_state->m_region_model; return model->get_fndecl_for_call (call, &old_ctxt); @@ -292,7 +330,7 @@ public: LOG_FUNC (logger); impl_region_model_context new_ctxt (m_eg, m_enode_for_diag, m_old_state, m_new_state, - NULL, + NULL, NULL, stmt); const svalue *var_new_sval = m_new_state->m_region_model->get_rvalue (var, &new_ctxt); @@ -320,12 +358,12 @@ 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 ()*/, + (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/, NULL, stmt); impl_region_model_context new_ctxt (m_eg, m_enode_for_diag, m_old_state, m_new_state, - NULL, + NULL, NULL, stmt); const svalue *origin_new_sval = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt); @@ -353,7 +391,7 @@ public: LOG_FUNC (get_logger ()); gcc_assert (d); // take ownership impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL); const svalue *var_old_sval = m_old_state->m_region_model->get_rvalue (var, &old_ctxt); @@ -418,7 +456,7 @@ public: if (!assign_stmt) return NULL_TREE; impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, stmt); if (const svalue *sval = m_new_state->m_region_model->get_gassign_result (assign_stmt, &old_ctxt)) @@ -428,6 +466,11 @@ public: return NULL_TREE; } + path_context *get_path_context () const FINAL OVERRIDE + { + return m_path_ctxt; + } + log_user m_logger; exploded_graph &m_eg; exploded_node *m_enode_for_diag; @@ -435,6 +478,7 @@ public: program_state *m_new_state; const sm_state_map *m_old_smap; sm_state_map *m_new_smap; + path_context *m_path_ctxt; stmt_finder *m_stmt_finder; }; @@ -751,9 +795,13 @@ impl_region_model_context::on_condition (const svalue *lhs, impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag, m_old_state, m_new_state, m_old_state->m_checker_states[sm_idx], - m_new_state->m_checker_states[sm_idx]); + m_new_state->m_checker_states[sm_idx], + m_path_ctxt); sm.on_condition (&sm_ctxt, - m_enode_for_diag->get_supernode (), m_stmt, + (m_enode_for_diag + ? m_enode_for_diag->get_supernode () + : NULL), + m_stmt, lhs, op, rhs); } } @@ -773,7 +821,8 @@ impl_region_model_context::on_phi (const gphi *phi, tree rhs) impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag, m_old_state, m_new_state, m_old_state->m_checker_states[sm_idx], - m_new_state->m_checker_states[sm_idx]); + m_new_state->m_checker_states[sm_idx], + m_path_ctxt); sm.on_phi (&sm_ctxt, m_enode_for_diag->get_supernode (), phi, rhs); } } @@ -1190,7 +1239,8 @@ exploded_node::on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, program_state *state, - uncertainty_t *uncertainty) + uncertainty_t *uncertainty, + path_context *path_ctxt) { logger *logger = eg.get_logger (); LOG_SCOPE (logger); @@ -1215,7 +1265,7 @@ exploded_node::on_stmt (exploded_graph &eg, impl_region_model_context ctxt (eg, this, &old_state, state, uncertainty, - stmt); + path_ctxt, stmt); bool unknown_side_effects = false; bool terminate_path = false; @@ -1235,13 +1285,16 @@ exploded_node::on_stmt (exploded_graph &eg, = old_state.m_checker_states[sm_idx]; sm_state_map *new_smap = state->m_checker_states[sm_idx]; impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state, - old_smap, new_smap); + old_smap, new_smap, path_ctxt); /* Allow the state_machine to handle the stmt. */ if (sm.on_stmt (&sm_ctxt, snode, stmt)) unknown_side_effects = false; } + if (path_ctxt->terminate_path_p ()) + return on_stmt_flags::terminate_path (); + on_stmt_post (stmt, state, unknown_side_effects, &ctxt); return on_stmt_flags (); @@ -1592,7 +1645,7 @@ exploded_node::detect_leaks (exploded_graph &eg) uncertainty_t uncertainty; impl_region_model_context ctxt (eg, this, - &old_state, &new_state, &uncertainty, + &old_state, &new_state, &uncertainty, NULL, get_stmt ()); const svalue *result = NULL; new_state.m_region_model->pop_frame (NULL, &result, &ctxt); @@ -1627,27 +1680,30 @@ exploded_node::dump_succs_and_preds (FILE *outf) const } } -/* class dynamic_call_info_t : public exploded_edge::custom_info_t. */ +/* class dynamic_call_info_t : public custom_edge_info. */ -/* Implementation of exploded_edge::custom_info_t::update_model vfunc +/* Implementation of custom_edge_info::update_model vfunc for dynamic_call_info_t. Update state for the dynamically discorverd calls */ -void +bool dynamic_call_info_t::update_model (region_model *model, - const exploded_edge &eedge) + const exploded_edge *eedge, + region_model_context *) const { - const program_state &dest_state = eedge.m_dest->get_state (); + gcc_assert (eedge); + const program_state &dest_state = eedge->m_dest->get_state (); *model = *dest_state.m_region_model; + return true; } -/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc +/* Implementation of custom_edge_info::add_events_to_path vfunc for dynamic_call_info_t. */ void dynamic_call_info_t::add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) + const exploded_edge &eedge) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); @@ -1671,21 +1727,23 @@ dynamic_call_info_t::add_events_to_path (checker_path *emission_path, } -/* class rewind_info_t : public exploded_edge::custom_info_t. */ +/* class rewind_info_t : public custom_edge_info. */ -/* Implementation of exploded_edge::custom_info_t::update_model vfunc +/* Implementation of custom_edge_info::update_model vfunc for rewind_info_t. Update state for the special-case of a rewind of a longjmp to a setjmp (which doesn't have a superedge, but does affect state). */ -void +bool rewind_info_t::update_model (region_model *model, - const exploded_edge &eedge) + const exploded_edge *eedge, + region_model_context *) const { - const program_point &longjmp_point = eedge.m_src->get_point (); - const program_point &setjmp_point = eedge.m_dest->get_point (); + gcc_assert (eedge); + const program_point &longjmp_point = eedge->m_src->get_point (); + const program_point &setjmp_point = eedge->m_dest->get_point (); gcc_assert (longjmp_point.get_stack_depth () >= setjmp_point.get_stack_depth ()); @@ -1693,14 +1751,15 @@ rewind_info_t::update_model (region_model *model, model->on_longjmp (get_longjmp_call (), get_setjmp_call (), setjmp_point.get_stack_depth (), NULL); + return true; } -/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc +/* Implementation of custom_edge_info::add_events_to_path vfunc for rewind_info_t. */ void rewind_info_t::add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) + const exploded_edge &eedge) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); @@ -1727,7 +1786,7 @@ rewind_info_t::add_events_to_path (checker_path *emission_path, exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - custom_info_t *custom_info) + custom_edge_info *custom_info) : dedge (src, dest), m_sedge (sedge), m_custom_info (custom_info) { @@ -2432,7 +2491,7 @@ exploded_graph::get_or_create_node (const program_point &point, exploded_edge * exploded_graph::add_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - exploded_edge::custom_info_t *custom_info) + custom_edge_info *custom_info) { if (get_logger ()) get_logger ()->log ("creating edge EN: %i -> EN: %i", @@ -2866,7 +2925,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) uncertainty_t uncertainty; impl_region_model_context ctxt (*this, iter_enode, &state, next_state, - &uncertainty, NULL); + &uncertainty, NULL, NULL); const cfg_superedge *last_cfg_superedge = iter_sedge->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -3095,6 +3154,72 @@ exploded_graph::maybe_create_dynamic_call (const gcall *call, return false; } +/* Subclass of path_context for use within exploded_graph::process_node, + so that we can split states e.g. at "realloc" calls. */ + +class impl_path_context : public path_context +{ +public: + impl_path_context (const program_state *cur_state) + : m_cur_state (cur_state), + m_terminate_path (false) + { + } + + bool bifurcation_p () const + { + return m_custom_eedge_infos.length () > 0; + } + + const program_state &get_state_at_bifurcation () const + { + gcc_assert (m_state_at_bifurcation); + return *m_state_at_bifurcation; + } + + void + bifurcate (custom_edge_info *info) FINAL OVERRIDE + { + if (m_state_at_bifurcation) + /* Verify that the state at bifurcation is consistent when we + split into multiple out-edges. */ + gcc_assert (*m_state_at_bifurcation == *m_cur_state); + else + /* Take a copy of the cur_state at the moment when bifurcation + happens. */ + m_state_at_bifurcation + = std::unique_ptr (new program_state (*m_cur_state)); + + /* Take ownership of INFO. */ + m_custom_eedge_infos.safe_push (info); + } + + void terminate_path () FINAL OVERRIDE + { + m_terminate_path = true; + } + + bool terminate_path_p () const FINAL OVERRIDE + { + return m_terminate_path; + } + + const vec & get_custom_eedge_infos () + { + return m_custom_eedge_infos; + } + +private: + const program_state *m_cur_state; + + /* Lazily-created copy of the state before the split. */ + std::unique_ptr m_state_at_bifurcation; + + auto_vec m_custom_eedge_infos; + + bool m_terminate_path; +}; + /* The core of exploded_graph::process_worklist (the main analysis loop), handling one node in the worklist. @@ -3150,7 +3275,7 @@ exploded_graph::process_node (exploded_node *node) { impl_region_model_context ctxt (*this, node, &state, &next_state, - &uncertainty, NULL); + &uncertainty, NULL, NULL); const cfg_superedge *last_cfg_superedge = point.get_from_edge ()->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -3188,6 +3313,9 @@ exploded_graph::process_node (exploded_node *node) the sm-state-change occurs on an edge where the src enode has exactly one stmt, the one that caused the change. */ program_state next_state (state); + + impl_path_context path_ctxt (&next_state); + uncertainty_t uncertainty; const supernode *snode = point.get_supernode (); unsigned stmt_idx; @@ -3210,7 +3338,8 @@ exploded_graph::process_node (exploded_node *node) /* Process the stmt. */ exploded_node::on_stmt_flags flags - = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty); + = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty, + &path_ctxt); node->m_num_processed_stmts++; /* If flags.m_terminate_path, stop analyzing; any nodes/edges @@ -3222,7 +3351,7 @@ exploded_graph::process_node (exploded_node *node) { impl_region_model_context ctxt (*this, node, &old_state, &next_state, - &uncertainty, stmt); + &uncertainty, NULL, stmt); program_state::detect_leaks (old_state, next_state, NULL, get_ext_state (), &ctxt); } @@ -3238,7 +3367,9 @@ exploded_graph::process_node (exploded_node *node) &uncertainty); if (flag_analyzer_fine_grained - || state_change_requires_new_enode_p (old_state, next_state)) + || state_change_requires_new_enode_p (old_state, next_state) + || path_ctxt.bifurcation_p () + || path_ctxt.terminate_path_p ()) { program_point split_point = program_point::before_stmt (point.get_supernode (), @@ -3282,9 +3413,66 @@ exploded_graph::process_node (exploded_node *node) point.get_call_string ()) : program_point::after_supernode (point.get_supernode (), point.get_call_string ())); - exploded_node *next = get_or_create_node (next_point, next_state, node); - if (next) - add_edge (node, next, NULL); + if (path_ctxt.terminate_path_p ()) + { + if (logger) + logger->log ("not adding node: terminating path"); + } + else + { + exploded_node *next + = get_or_create_node (next_point, next_state, node); + if (next) + add_edge (node, next, NULL); + } + + /* If we have custom edge infos, "bifurcate" the state + accordingly, potentially creating a new state/enode/eedge + instances. For example, to handle a "realloc" call, we + might split into 3 states, for the "failure", + "resizing in place", and "moving to a new buffer" cases. */ + for (auto edge_info : path_ctxt.get_custom_eedge_infos ()) + { + if (logger) + { + logger->start_log_line (); + logger->log_partial ("bifurcating for edge: "); + edge_info->print (logger->get_printer ()); + logger->end_log_line (); + } + program_state bifurcated_new_state + (path_ctxt.get_state_at_bifurcation ()); + + /* Apply edge_info to state. */ + impl_region_model_context + bifurcation_ctxt (*this, + NULL, // enode_for_diag + &path_ctxt.get_state_at_bifurcation (), + &bifurcated_new_state, + NULL, // uncertainty_t *uncertainty + NULL, // path_context *path_ctxt + stmt); + if (edge_info->update_model (bifurcated_new_state.m_region_model, + NULL, /* no exploded_edge yet. */ + &bifurcation_ctxt)) + { + exploded_node *next2 + = get_or_create_node (next_point, bifurcated_new_state, node); + if (next2) + { + /* Take ownership of edge_info. */ + add_edge (node, next2, NULL, edge_info); + } + else + delete edge_info; + } + else + { + if (logger) + logger->log ("infeasible state, not adding node"); + delete edge_info; + } + } } break; case PK_AFTER_SUPERNODE: @@ -3351,6 +3539,7 @@ exploded_graph::process_node (exploded_node *node) &state, &next_state, &uncertainty, + NULL, point.get_stmt()); region_model *model = state.m_region_model; @@ -3968,7 +4157,7 @@ feasibility_state::maybe_update_for_edge (logger *logger, } else if (eedge->m_custom_info) { - eedge->m_custom_info->update_model (&m_model, *eedge); + eedge->m_custom_info->update_model (&m_model, eedge, NULL); } } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 6890e84..b9c1767 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -37,6 +37,7 @@ class impl_region_model_context : public region_model_context const program_state *old_state, program_state *new_state, uncertainty_t *uncertainty, + path_context *path_ctxt, const gimple *stmt, stmt_finder *stmt_finder = NULL); @@ -76,6 +77,16 @@ class impl_region_model_context : public region_model_context void purge_state_involving (const svalue *sval) FINAL OVERRIDE; + void bifurcate (custom_edge_info *info) FINAL OVERRIDE; + void terminate_path () FINAL OVERRIDE; + const extrinsic_state *get_ext_state () const FINAL OVERRIDE + { + return &m_ext_state; + } + bool get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) FINAL OVERRIDE; + exploded_graph *m_eg; log_user m_logger; exploded_node *m_enode_for_diag; @@ -85,6 +96,7 @@ class impl_region_model_context : public region_model_context stmt_finder *m_stmt_finder; const extrinsic_state &m_ext_state; uncertainty_t *m_uncertainty; + path_context *m_path_ctxt; }; /* A pair, used internally by @@ -224,7 +236,8 @@ class exploded_node : public dnode const supernode *snode, const gimple *stmt, program_state *state, - uncertainty_t *uncertainty); + uncertainty_t *uncertainty, + path_context *path_ctxt); void on_stmt_pre (exploded_graph &eg, const gimple *stmt, program_state *state, @@ -319,28 +332,9 @@ public: class exploded_edge : public dedge { public: - /* Abstract base class for associating custom data with an - exploded_edge, for handling non-standard edges such as - rewinding from a longjmp, signal handlers, etc. */ - class custom_info_t - { - public: - virtual ~custom_info_t () {} - - /* Hook for making .dot label more readable . */ - virtual void print (pretty_printer *pp) = 0; - - /* Hook for updating MODEL within exploded_path::feasible_p. */ - virtual void update_model (region_model *model, - const exploded_edge &eedge) = 0; - - virtual void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) = 0; - }; - exploded_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - custom_info_t *custom_info); + custom_edge_info *custom_info); ~exploded_edge (); void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE; @@ -356,7 +350,7 @@ class exploded_edge : public dedge a signal is delivered to a signal-handler. Owned by this class. */ - custom_info_t *m_custom_info; + custom_edge_info *m_custom_info; private: DISABLE_COPY_AND_ASSIGN (exploded_edge); @@ -365,7 +359,7 @@ private: /* Extra data for an exploded_edge that represents dynamic call info ( calls that doesn't have an underlying superedge representing the call ). */ -class dynamic_call_info_t : public exploded_edge::custom_info_t +class dynamic_call_info_t : public custom_edge_info { public: dynamic_call_info_t (const gcall *dynamic_call, @@ -374,7 +368,7 @@ public: m_is_returning_call (is_returning_call) {} - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { if (m_is_returning_call) pp_string (pp, "dynamic_return"); @@ -382,11 +376,12 @@ public: pp_string (pp, "dynamic_call"); } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE; + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const FINAL OVERRIDE; void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) FINAL OVERRIDE; + const exploded_edge &eedge) const FINAL OVERRIDE; private: const gcall *m_dynamic_call; const bool m_is_returning_call; @@ -396,7 +391,7 @@ private: /* Extra data for an exploded_edge that represents a rewind from a longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */ -class rewind_info_t : public exploded_edge::custom_info_t +class rewind_info_t : public custom_edge_info { public: rewind_info_t (const setjmp_record &setjmp_record, @@ -405,16 +400,17 @@ public: m_longjmp_call (longjmp_call) {} - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { pp_string (pp, "rewind"); } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE; + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const FINAL OVERRIDE; void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) FINAL OVERRIDE; + const exploded_edge &eedge) const FINAL OVERRIDE; const program_point &get_setjmp_point () const { @@ -829,7 +825,7 @@ public: exploded_node *enode_for_diag); exploded_edge *add_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - exploded_edge::custom_info_t *custom = NULL); + custom_edge_info *custom = NULL); per_program_point_data * get_or_create_per_program_point_data (const program_point &); diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index ea53c61..c1ff0d8 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1013,7 +1013,7 @@ program_state::on_edge (exploded_graph &eg, impl_region_model_context ctxt (eg, enode, &enode->get_state (), this, - uncertainty, + uncertainty, NULL, last_stmt); if (!m_region_model->maybe_update_for_edge (*succ, last_stmt, @@ -1052,6 +1052,7 @@ program_state::push_call (exploded_graph &eg, &enode->get_state (), this, uncertainty, + NULL, last_stmt); m_region_model->update_for_gcall (call_stmt, &ctxt); } @@ -1074,6 +1075,7 @@ program_state::returning_call (exploded_graph &eg, &enode->get_state (), this, uncertainty, + NULL, last_stmt); m_region_model->update_for_return_gcall (call_stmt, &ctxt); } @@ -1152,7 +1154,7 @@ program_state::prune_for_point (exploded_graph &eg, impl_region_model_context ctxt (eg, enode_for_diag, this, &new_state, - uncertainty, + uncertainty, NULL, point.get_stmt ()); detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt); } diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index e5a6cb2..875719f 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -56,6 +56,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/program-point.h" #include "analyzer/store.h" #include "analyzer/region-model.h" +#include "analyzer/call-info.h" #include "gimple-pretty-print.h" #if ENABLE_ANALYZER @@ -158,6 +159,15 @@ call_details::get_arg_string_literal (unsigned idx) const return NULL; } +/* Attempt to get the fndecl used at this call, if known, or NULL_TREE + otherwise. */ + +tree +call_details::get_fndecl_for_call () const +{ + return m_model->get_fndecl_for_call (m_call, m_ctxt); +} + /* Dump a multiline representation of this call to PP. */ void @@ -486,15 +496,169 @@ region_model::impl_call_operator_delete (const call_details &cd) } } -/* Handle the on_call_pre part of "realloc". */ +/* Handle the on_call_post part of "realloc": + + void *realloc(void *ptr, size_t size); + + realloc(3) is awkward, since it has various different outcomes + that are best modelled as separate exploded nodes/edges. + + We first check for sm-state, in + malloc_state_machine::on_realloc_call, so that we + can complain about issues such as realloc of a non-heap + pointer, and terminate the path for such cases (and issue + the complaints at the call's exploded node). + + Assuming that these checks pass, we split the path here into + three special cases (and terminate the "standard" path): + (A) failure, returning NULL + (B) success, growing the buffer in-place without moving it + (C) success, allocating a new buffer, copying the content + of the old buffer to it, and freeing the old buffer. + + Each of these has a custom_edge_info subclass, which updates + the region_model and sm-state of the destination state. */ void -region_model::impl_call_realloc (const call_details &) +region_model::impl_call_realloc (const call_details &cd) { - /* Currently we don't support bifurcating state, so there's no good - way to implement realloc(3). - For now, malloc_state_machine::on_realloc_call has a minimal - implementation to suppress false positives. */ + /* Three custom subclasses of custom_edge_info, for handling the various + outcomes of "realloc". */ + + /* Concrete custom_edge_info: a realloc call that fails, returning NULL. */ + class failure : public failed_call_info + { + public: + failure (const call_details &cd) + : failed_call_info (cd) + { + } + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + /* Return NULL; everything else is unchanged. */ + const call_details cd (get_call_details (model, ctxt)); + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + model->set_value (cd.get_lhs_region (), + zero, + cd.get_ctxt ()); + return true; + } + }; + + /* Concrete custom_edge_info: a realloc call that succeeds, growing + the existing buffer without moving it. */ + class success_no_move : public call_info + { + public: + success_no_move (const call_details &cd) + : call_info (cd) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "when %qE succeeds, without moving buffer", + get_fndecl ()); + } + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + /* Update size of buffer and return the ptr unchanged. */ + const call_details cd (get_call_details (model, ctxt)); + const svalue *ptr_sval = cd.get_arg_svalue (0); + const svalue *size_sval = cd.get_arg_svalue (1); + if (const region *buffer_reg = ptr_sval->maybe_get_region ()) + model->set_dynamic_extents (buffer_reg, size_sval); + model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ()); + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ()); + } + }; + + /* Concrete custom_edge_info: a realloc call that succeeds, freeing + the existing buffer and moving the content to a freshly allocated + buffer. */ + class success_with_move : public call_info + { + public: + success_with_move (const call_details &cd) + : call_info (cd) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "when %qE succeeds, moving buffer", + get_fndecl ()); + } + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + const call_details cd (get_call_details (model, ctxt)); + const svalue *old_ptr_sval = cd.get_arg_svalue (0); + const svalue *new_size_sval = cd.get_arg_svalue (1); + + /* Create the new region. */ + const region *new_reg + = model->create_region_for_heap_alloc (new_size_sval); + const svalue *new_ptr_sval + = model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); + if (cd.get_lhs_type ()) + cd.maybe_set_lhs (new_ptr_sval); + + if (const region *freed_reg = old_ptr_sval->maybe_get_region ()) + { + /* Copy the data. */ + const svalue *old_size_sval = model->get_dynamic_extents (freed_reg); + if (old_size_sval) + { + const region *sized_old_reg + = model->m_mgr->get_sized_region (freed_reg, NULL, + old_size_sval); + const svalue *buffer_content_sval + = model->get_store_value (sized_old_reg, cd.get_ctxt ()); + model->set_value (new_reg, buffer_content_sval, cd.get_ctxt ()); + } + + /* Free the old region, so that pointers to the old buffer become + invalid. */ + + /* If the ptr points to an underlying heap region, delete it, + poisoning pointers. */ + model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); + model->m_dynamic_extents.remove (freed_reg); + } + + /* Update the sm-state: mark the old_ptr_sval as "freed", + and the new_ptr_sval as "nonnull". */ + model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval); + + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (new_ptr_sval, NE_EXPR, zero, + cd.get_ctxt ()); + } + }; + + /* Body of region_model::impl_call_realloc. */ + + if (cd.get_ctxt ()) + { + cd.get_ctxt ()->bifurcate (new failure (cd)); + cd.get_ctxt ()->bifurcate (new success_no_move (cd)); + cd.get_ctxt ()->bifurcate (new success_with_move (cd)); + cd.get_ctxt ()->terminate_path (); + } } /* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk". */ diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 787f2ed..3bfc4ba 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1132,7 +1132,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, return false; break; case BUILT_IN_REALLOC: - impl_call_realloc (cd); return false; case BUILT_IN_STRCPY: case BUILT_IN_STRCPY_CHK: @@ -1276,9 +1275,9 @@ region_model::on_call_post (const gcall *call, { if (tree callee_fndecl = get_fndecl_for_call (call, ctxt)) { + call_details cd (call, this, ctxt); if (is_named_call_p (callee_fndecl, "free", call, 1)) { - call_details cd (call, this, ctxt); impl_call_free (cd); return; } @@ -1286,7 +1285,6 @@ region_model::on_call_post (const gcall *call, || is_named_call_p (callee_fndecl, "operator delete", call, 2) || is_named_call_p (callee_fndecl, "operator delete []", call, 1)) { - call_details cd (call, this, ctxt); impl_call_operator_delete (cd); return; } @@ -1294,10 +1292,19 @@ region_model::on_call_post (const gcall *call, __attribute__((malloc(FOO)))? */ if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl))) { - call_details cd (call, this, ctxt); impl_deallocation_call (cd); return; } + if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL) + && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) + switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) + { + default: + break; + case BUILT_IN_REALLOC: + impl_call_realloc (cd); + return; + } } if (unknown_side_effects) @@ -3765,6 +3772,19 @@ region_model::unset_dynamic_extents (const region *reg) m_dynamic_extents.remove (reg); } +/* class noop_region_model_context : public region_model_context. */ + +void +noop_region_model_context::bifurcate (custom_edge_info *info) +{ + delete info; +} + +void +noop_region_model_context::terminate_path () +{ +} + /* struct model_merger. */ /* Dump a multiline representation of this merger to PP. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index f2c82b0..5fabf78 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -487,11 +487,15 @@ public: unsigned num_args () const; + const gcall *get_call_stmt () const { return m_call; } + tree get_arg_tree (unsigned idx) const; tree get_arg_type (unsigned idx) const; const svalue *get_arg_svalue (unsigned idx) const; const char *get_arg_string_literal (unsigned idx) const; + tree get_fndecl_for_call () const; + void dump_to_pp (pretty_printer *pp, bool simple) const; void dump (bool simple) const; @@ -732,6 +736,11 @@ class region_model const svalue *get_capacity (const region *reg) const; + /* Implemented in sm-malloc.cc */ + void on_realloc_with_move (const call_details &cd, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval); + private: const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const; const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const; @@ -867,6 +876,21 @@ class region_model_context /* Hook for clients to purge state involving SVAL. */ virtual void purge_state_involving (const svalue *sval) = 0; + + /* Hook for clients to split state with a non-standard path. + Take ownership of INFO. */ + virtual void bifurcate (custom_edge_info *info) = 0; + + /* Hook for clients to terminate the standard path. */ + virtual void terminate_path () = 0; + + virtual const extrinsic_state *get_ext_state () const = 0; + + /* Hook for clients to access the "malloc" state machine in + any underlying program_state. */ + virtual bool get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) = 0; }; /* A "do nothing" subclass of region_model_context. */ @@ -899,6 +923,18 @@ public: uncertainty_t *get_uncertainty () OVERRIDE { return NULL; } void purge_state_involving (const svalue *sval ATTRIBUTE_UNUSED) OVERRIDE {} + + void bifurcate (custom_edge_info *info) OVERRIDE; + void terminate_path () OVERRIDE; + + const extrinsic_state *get_ext_state () const OVERRIDE { return NULL; } + + bool get_malloc_map (sm_state_map **, + const state_machine **, + unsigned *) OVERRIDE + { + return false; + } }; /* A subclass of region_model_context for determining if operations fail diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 74c6fee..bf5e3c3 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "analyzer/function-set.h" +#include "analyzer/program-state.h" #if ENABLE_ANALYZER @@ -387,6 +388,12 @@ public: static bool unaffected_by_call_p (tree fndecl); + void on_realloc_with_move (region_model *model, + sm_state_map *smap, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval, + const extrinsic_state &ext_state) const; + standard_deallocator_set m_free; standard_deallocator_set m_scalar_delete; standard_deallocator_set m_vector_delete; @@ -1836,54 +1843,65 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, } } -/* Implementation of realloc(3): - - void *realloc(void *ptr, size_t size); - - realloc(3) is awkward. +/* Handle a call to "realloc". + Check for free of non-heap or mismatching allocators, + transitioning to the "stop" state for such cases. - We currently don't have a way to express multiple possible outcomes - from a function call, "bifurcating" the state such as: - - success: non-NULL is returned - - failure: NULL is returned, existing buffer is not freed. - or even an N-way state split e.g.: - - buffer grew successfully in-place - - buffer was successfully moved to a larger allocation - - buffer was successfully contracted - - realloc failed, returning NULL, without freeing existing buffer. - (PR analyzer/99260 tracks this) - - Given that we can currently only express one outcome, eliminate - false positives by dropping state from the buffer. */ + Otherwise, region_model::impl_call_realloc will later + get called (which will handle other sm-state transitions + when the state is bifurcated). */ void malloc_state_machine::on_realloc_call (sm_context *sm_ctxt, - const supernode *node ATTRIBUTE_UNUSED, + const supernode *node, const gcall *call) const { - tree ptr = gimple_call_arg (call, 0); + const unsigned argno = 0; + const deallocator *d = &m_realloc; + + tree arg = gimple_call_arg (call, argno); - state_t state = sm_ctxt->get_state (call, ptr); + state_t state = sm_ctxt->get_state (call, arg); - /* Detect mismatches. */ if (unchecked_p (state) || nonnull_p (state)) { const allocation_state *astate = as_a_allocation_state (state); gcc_assert (astate->m_deallocators); - if (astate->m_deallocators != &m_free) + if (!astate->m_deallocators->contains_p (&m_free.m_deallocator)) { /* Wrong allocator. */ - tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr); + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); pending_diagnostic *pd - = new mismatching_deallocation (*this, diag_ptr, + = new mismatching_deallocation (*this, diag_arg, astate->m_deallocators, - &m_realloc); - sm_ctxt->warn (node, call, ptr, pd); + d); + sm_ctxt->warn (node, call, arg, pd); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); } } - - /* Transition ptr to "stop" state. */ - sm_ctxt->set_next_state (call, ptr, m_stop); + else if (state == m_free.m_deallocator.m_freed) + { + /* freed -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, call, arg, + new double_free (*this, diag_arg, "free")); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); + } + else if (state == m_non_heap) + { + /* non-heap -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, call, arg, + new free_of_non_heap (*this, diag_arg, + d->m_name)); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); + } } /* Implementation of state_machine::on_phi vfunc for malloc_state_machine. */ @@ -2015,6 +2033,30 @@ malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt, sm_ctxt->set_next_state (stmt, lhs, m_null); } +/* Special-case hook for handling realloc, for the "success with move to + a new buffer" case, marking OLD_PTR_SVAL as freed and NEW_PTR_SVAL as + non-null. + + This is similar to on_deallocator_call and on_allocator_call, + but the checks happen in on_realloc_call, and by splitting the states. */ + +void +malloc_state_machine:: +on_realloc_with_move (region_model *model, + sm_state_map *smap, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval, + const extrinsic_state &ext_state) const +{ + smap->set_state (model, old_ptr_sval, + m_free.m_deallocator.m_freed, + NULL, ext_state); + + smap->set_state (model, new_ptr_sval, + m_free.m_nonnull, + NULL, ext_state); +} + } // anonymous namespace /* Internal interface to this file. */ @@ -2025,6 +2067,40 @@ make_malloc_state_machine (logger *logger) return new malloc_state_machine (logger); } +/* Specialcase hook for handling realloc, for use by + region_model::impl_call_realloc::success_with_move::update_model. */ + +void +region_model::on_realloc_with_move (const call_details &cd, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval) +{ + region_model_context *ctxt = cd.get_ctxt (); + if (!ctxt) + return; + const extrinsic_state *ext_state = ctxt->get_ext_state (); + if (!ext_state) + return; + + sm_state_map *smap; + const state_machine *sm; + unsigned sm_idx; + if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx)) + return; + + gcc_assert (smap); + gcc_assert (sm); + + const malloc_state_machine &malloc_sm + = (const malloc_state_machine &)*sm; + + malloc_sm.on_realloc_with_move (this, + smap, + old_ptr_sval, + new_ptr_sval, + *ext_state); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc index fcbf322..e8cbe2d 100644 --- a/gcc/analyzer/sm-signal.cc +++ b/gcc/analyzer/sm-signal.cc @@ -206,10 +206,10 @@ update_model_for_signal_handler (region_model *model, /* Custom exploded_edge info: entry into a signal-handler. */ -class signal_delivery_edge_info_t : public exploded_edge::custom_info_t +class signal_delivery_edge_info_t : public custom_edge_info { public: - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { pp_string (pp, "signal delivered"); } @@ -220,15 +220,18 @@ public: return custom_obj; } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *) const FINAL OVERRIDE { - update_model_for_signal_handler (model, eedge.m_dest->get_function ()); + gcc_assert (eedge); + update_model_for_signal_handler (model, eedge->m_dest->get_function ()); + return true; } void add_events_to_path (checker_path *emission_path, const exploded_edge &eedge ATTRIBUTE_UNUSED) - FINAL OVERRIDE + const FINAL OVERRIDE { emission_path->add_event (new precanned_custom_event diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index 6bb036e..02faffb 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -257,6 +257,11 @@ public: Otherwise return NULL_TREE. */ virtual tree is_zero_assignment (const gimple *stmt) = 0; + virtual path_context *get_path_context () const + { + return NULL; + } + protected: sm_context (int sm_idx, const state_machine &sm) : m_sm_idx (sm_idx), m_sm (sm) {} diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 6913161..5f2fe4c 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -105,7 +105,8 @@ svalue::to_json () const tree svalue::maybe_get_constant () const { - if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ()) + const svalue *sval = unwrap_any_unmergeable (); + if (const constant_svalue *cst_sval = sval->dyn_cast_constant_svalue ()) return cst_sval->get_constant (); else return NULL_TREE; -- cgit v1.1 From 1e2f030b80cb650708b02086dbd5431cd231495f Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Tue, 31 Aug 2021 00:16:50 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index c7e8ba92..3f0c046 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,106 @@ +2021-08-30 David Malcolm + + PR analyzer/99260 + * analyzer.h (class custom_edge_info): New class, adapted from + exploded_edge::custom_info_t. Make member functions const. + Make update_model return bool, converting edge param from + reference to a pointer, and adding a ctxt param. + (class path_context): New class. + * call-info.cc: New file. + * call-info.h: New file. + * engine.cc: Include "analyzer/call-info.h" and . + (impl_region_model_context::impl_region_model_context): Update for + new m_path_ctxt field. + (impl_region_model_context::bifurcate): New. + (impl_region_model_context::terminate_path): New. + (impl_region_model_context::get_malloc_map): New. + (impl_sm_context::impl_sm_context): Update for new m_path_ctxt + field. + (impl_sm_context::get_fndecl_for_call): Likewise. + (impl_sm_context::set_next_state): Likewise. + (impl_sm_context::warn): Likewise. + (impl_sm_context::is_zero_assignment): Likewise. + (impl_sm_context::get_path_context): New. + (impl_sm_context::m_path_ctxt): New. + (impl_region_model_context::on_condition): Update for new + path_ctxt param. Handle m_enode_for_diag being NULL. + (impl_region_model_context::on_phi): Update for new path_ctxt + param. + (exploded_node::on_stmt): Add path_ctxt param, updating ctor calls + to use it as necessary. Use it to bail out after sm-handling, + if needed. + (exploded_node::detect_leaks): Update for new path_ctxt param. + (dynamic_call_info_t::update_model): Update for conversion of + exploded_edge::custom_info_t to custom_edge_info. + (dynamic_call_info_t::add_events_to_path): Likewise. + (rewind_info_t::update_model): Likewise. + (rewind_info_t::add_events_to_path): Likewise. + (exploded_edge::exploded_edge): Likewise. + (exploded_graph::add_edge): Likewise. + (exploded_graph::maybe_process_run_of_before_supernode_enodes): + Update for new path_ctxt param. + (class impl_path_context): New. + (exploded_graph::process_node): Update for new path_ctxt param. + Create an impl_path_context and pass it to exploded_node::on_stmt. + Use it to terminate iterating stmts if terminate_path is called + on it. After processing a run of stmts, query path_ctxt to + potentially terminate the analysis path, and/or to "bifurcate" the + analysis into multiple additional paths. + (feasibility_state::maybe_update_for_edge): Update for new + update_model ctxt param. + * exploded-graph.h + (impl_region_model_context::impl_region_model_context): Add + path_ctxt param. + (impl_region_model_context::bifurcate): New. + (impl_region_model_context::terminate_path): New + (impl_region_model_context::get_ext_state): New. + (impl_region_model_context::get_malloc_map): New. + (impl_region_model_context::m_path_ctxt): New field. + (exploded_node::on_stmt): Add path_ctxt param. + (class exploded_edge::custom_info_t): Move to analyzer.h, renaming + to custom_edge_info, and making the changes as noted in analyzer.h + above. + (exploded_edge::exploded_edge): Update for these changes to + exploded_edge::custom_info_t. + (exploded_edge::m_custom_info): Likewise. + (class dynamic_call_info_t): Likewise. + (class rewind_info_t): Likewise. + (exploded_graph::add_edge): Likewise. + * program-state.cc (program_state::on_edge): Update for new + path_ctxt param. + (program_state::push_call): Likewise. + (program_state::returning_call): Likewise. + (program_state::prune_for_point): Likewise. + * region-model-impl-calls.cc: Include "analyzer/call-info.h". + (call_details::get_fndecl_for_call): New. + (region_model::impl_call_realloc): Reimplement. + * region-model.cc (region_model::on_call_pre): Move call to + impl_call_realloc to... + (region_model::on_call_post): ...here. Consolidate creation + of call_details instance. + (noop_region_model_context::bifurcate): New. + (noop_region_model_context::terminate_path): New. + * region-model.h (call_details::get_call_stmt): New. + (call_details::get_fndecl_for_call): New. + (region_model::on_realloc_with_move): New. + (region_model_context::bifurcate): New. + (region_model_context::terminate_path): New. + (region_model_context::get_ext_state): New. + (region_model_context::get_malloc_map): New. + (noop_region_model_context::bifurcate): New. + (noop_region_model_context::terminate_path): New. + (noop_region_model_context::get_ext_state): New. + (noop_region_model_context::get_malloc_map): New. + * sm-malloc.cc: Include "analyzer/program-state.h". + (malloc_state_machine::on_realloc_call): Reimplement. + (malloc_state_machine::on_realloc_with_move): New. + (region_model::on_realloc_with_move): New. + * sm-signal.cc (class signal_delivery_edge_info_t): Update for + conversion from exploded_edge::custom_info_t to custom_edge_info. + * sm.h (sm_context::get_path_context): New. + * svalue.cc (svalue::maybe_get_constant): Call + unwrap_any_unmergeable. + 2021-08-25 Ankur Saini PR analyzer/101980 -- cgit v1.1 From e66b9f6779f46433b0e2c093b58403604ed131cc Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Wed, 8 Sep 2021 14:37:19 -0400 Subject: analyzer: fix ICE when discarding result of realloc [PR102225] gcc/analyzer/ChangeLog: PR analyzer/102225 * analyzer.h (compat_types_p): New decl. * constraint-manager.cc (constraint_manager::get_or_add_equiv_class): Guard against NULL type when checking for pointer types. * region-model-impl-calls.cc (region_model::impl_call_realloc): Guard against NULL lhs type/region. Guard against the size value not being of a compatible type for dynamic extents. * region-model.cc (compat_types_p): Make non-static. gcc/testsuite/ChangeLog: PR analyzer/102225 * gcc.dg/analyzer/realloc-1.c (test_10): New. * gcc.dg/analyzer/torture/pr102225.c: New test. --- gcc/analyzer/analyzer.h | 2 ++ gcc/analyzer/constraint-manager.cc | 9 +++---- gcc/analyzer/region-model-impl-calls.cc | 42 ++++++++++++++++++++++----------- gcc/analyzer/region-model.cc | 2 +- 4 files changed, 36 insertions(+), 19 deletions(-) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 7ad1081..3ba4e21 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -203,6 +203,8 @@ private: extern location_t get_stmt_location (const gimple *stmt, function *fun); +extern bool compat_types_p (tree src_type, tree dst_type); + /* Passed by pointer to PLUGIN_ANALYZER_INIT callbacks. */ class plugin_analyzer_init_iface diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc index dc65c8d..6df23fb 100644 --- a/gcc/analyzer/constraint-manager.cc +++ b/gcc/analyzer/constraint-manager.cc @@ -2088,10 +2088,11 @@ constraint_manager::get_or_add_equiv_class (const svalue *sval) /* Convert all NULL pointers to (void *) to avoid state explosions involving all of the various (foo *)NULL vs (bar *)NULL. */ - if (POINTER_TYPE_P (sval->get_type ())) - if (tree cst = sval->maybe_get_constant ()) - if (zerop (cst)) - sval = m_mgr->get_or_create_constant_svalue (null_pointer_node); + if (sval->get_type ()) + if (POINTER_TYPE_P (sval->get_type ())) + if (tree cst = sval->maybe_get_constant ()) + if (zerop (cst)) + sval = m_mgr->get_or_create_constant_svalue (null_pointer_node); /* Try svalue match. */ if (get_equiv_class_by_svalue (sval, &result)) diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 875719f..ff2ae9c 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -540,11 +540,14 @@ region_model::impl_call_realloc (const call_details &cd) { /* Return NULL; everything else is unchanged. */ const call_details cd (get_call_details (model, ctxt)); - const svalue *zero - = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); - model->set_value (cd.get_lhs_region (), - zero, - cd.get_ctxt ()); + if (cd.get_lhs_type ()) + { + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + model->set_value (cd.get_lhs_region (), + zero, + cd.get_ctxt ()); + } return true; } }; @@ -575,11 +578,17 @@ region_model::impl_call_realloc (const call_details &cd) const svalue *ptr_sval = cd.get_arg_svalue (0); const svalue *size_sval = cd.get_arg_svalue (1); if (const region *buffer_reg = ptr_sval->maybe_get_region ()) - model->set_dynamic_extents (buffer_reg, size_sval); - model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ()); - const svalue *zero - = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); - return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ()); + if (compat_types_p (size_sval->get_type (), size_type_node)) + model->set_dynamic_extents (buffer_reg, size_sval); + if (cd.get_lhs_region ()) + { + model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ()); + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ()); + } + else + return true; } }; @@ -643,10 +652,15 @@ region_model::impl_call_realloc (const call_details &cd) and the new_ptr_sval as "nonnull". */ model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval); - const svalue *zero - = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); - return model->add_constraint (new_ptr_sval, NE_EXPR, zero, - cd.get_ctxt ()); + if (cd.get_lhs_type ()) + { + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (new_ptr_sval, NE_EXPR, zero, + cd.get_ctxt ()); + } + else + return true; } }; diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 3bfc4ba..a14d107 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1713,7 +1713,7 @@ assert_compat_types (tree src_type, tree dst_type) /* Return true if SRC_TYPE can be converted to DST_TYPE as a no-op. */ -static bool +bool compat_types_p (tree src_type, tree dst_type) { if (src_type && dst_type && !VOID_TYPE_P (dst_type)) -- cgit v1.1 From b6db7cd41ccf821ffb10ff4f18845465e98803cd Mon Sep 17 00:00:00 2001 From: GCC Administrator Date: Thu, 9 Sep 2021 00:16:32 +0000 Subject: Daily bump. --- gcc/analyzer/ChangeLog | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'gcc/analyzer') diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog index 3f0c046..03ba64f 100644 --- a/gcc/analyzer/ChangeLog +++ b/gcc/analyzer/ChangeLog @@ -1,3 +1,15 @@ +2021-09-08 David Malcolm + + PR analyzer/102225 + * analyzer.h (compat_types_p): New decl. + * constraint-manager.cc + (constraint_manager::get_or_add_equiv_class): Guard against NULL + type when checking for pointer types. + * region-model-impl-calls.cc (region_model::impl_call_realloc): + Guard against NULL lhs type/region. Guard against the size value + not being of a compatible type for dynamic extents. + * region-model.cc (compat_types_p): Make non-static. + 2021-08-30 David Malcolm PR analyzer/99260 -- cgit v1.1