diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 857d43450d1..5b519fdf385 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 25666411480..c67f7b70605 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 347cb290d10..f8094621309 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 71b6f0187a4..898c57ff833 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 f83c12b5cb7..4052bb393f8 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 087185b4e45..e165cda014f 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 c245bfe0512..2d3880bf8cc 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 54977f947ee..4123500f3bc 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 53b6e21aa75..b1874a5a2d3 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 2bcef6c398a..dc22d96f186 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); diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99042.c b/gcc/testsuite/gcc.dg/analyzer/pr99042.c new file mode 100644 index 00000000000..c3d124f3e31 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr99042.c @@ -0,0 +1,53 @@ +#include + +struct foo { + FILE *file; +}; + +extern void unknown_fn (); +extern void unknown_fn2 (const struct foo *f); + +int test_1 (struct foo *p) +{ + if ((p->file = fopen("test.txt", "w")) == NULL) + return 1; + unknown_fn (); + return 0; /* { dg-bogus "leak" } */ +} + +int test_2 (struct foo *p) +{ + if ((p->file = fopen("test.txt", "w")) == NULL) + return 1; + return 0; /* { dg-bogus "leak" } */ +} + +int test_3 (void) +{ + struct foo f; + struct foo *p = &f; + if ((p->file = fopen("test.txt", "w")) == NULL) + return 1; + unknown_fn (); + return 0; /* { dg-warning "leak" } */ +} + +int test_4 (void) +{ + struct foo f; + struct foo *p = &f; + if ((p->file = fopen("test.txt", "w")) == NULL) + return 1; + return 0; /* { dg-warning "leak" } */ +} + +int test_5 (void) +{ + struct foo f; + struct foo *p = &f; + if ((p->file = fopen("test.txt", "w")) == NULL) + return 1; + /* Although p is const, the underlying FILE * is not and could be closed. */ + unknown_fn2 (p); + return 0; /* { dg-bogus "leak" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c new file mode 100644 index 00000000000..620cf6571ed --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr99774-1.c @@ -0,0 +1,61 @@ +/* Reproducer for report from -Wanalyzer-malloc-leak + Reduced from + https://git.qemu.org/?p=qemu.git;a=blob;f=subprojects/libvhost-user/libvhost-user.c;h=fab7ca17ee1fb27bcfc338527d1aeb9f923aade5;hb=HEAD#l1184 + which is licensed under GNU GPLv2 or later. */ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint64_t; +typedef unsigned long uint64_t; +typedef long unsigned int size_t; + +extern void *calloc(size_t __nmemb, size_t __size) + __attribute__((__nothrow__, __leaf__)) + __attribute__((__malloc__)) + __attribute__((__alloc_size__(1, 2))) + __attribute__((__warn_unused_result__)); + +typedef struct VuDescStateSplit { + uint8_t inflight; + uint64_t counter; +} VuDescStateSplit; + +typedef struct VuVirtqInflight { + uint16_t desc_num; + VuDescStateSplit desc[]; +} VuVirtqInflight; + +typedef struct VuVirtqInflightDesc { + uint16_t index; + uint64_t counter; +} VuVirtqInflightDesc; + +typedef struct VuVirtq { + VuVirtqInflight *inflight; + VuVirtqInflightDesc *resubmit_list; + uint16_t resubmit_num; + uint64_t counter; + int inuse; +} VuVirtq; + +int vu_check_queue_inflights(VuVirtq *vq) { + int i = 0; + + if (vq->inuse) { + vq->resubmit_list = calloc(vq->inuse, sizeof(VuVirtqInflightDesc)); + if (!vq->resubmit_list) { + return -1; + } + + for (i = 0; i < vq->inflight->desc_num; i++) { + if (vq->inflight->desc[i].inflight) { + vq->resubmit_list[vq->resubmit_num].index = i; /* { dg-bogus "leak" } */ + vq->resubmit_list[vq->resubmit_num].counter = + vq->inflight->desc[i].counter; + vq->resubmit_num++; + } + } + } + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c new file mode 100644 index 00000000000..d9704dee6f2 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr99774-2.c @@ -0,0 +1,144 @@ +#include + +struct st +{ + void *m_f; +}; + +struct node +{ + struct node *m_next; +}; + +extern void unknown_fn (void *); +extern void const_unknown_fn (const void *); + +void +test_1 (struct st *p, struct st *q) +{ + p->m_f = malloc (1024); + q->m_f = NULL; /* { dg-bogus "leak" } */ + free (p->m_f); +} + +void +test_2 (void) +{ + struct st s; + s.m_f = malloc (1024); + unknown_fn (&s); + free (s.m_f); +} + +void +test_3 (void) +{ + struct st s; + s.m_f = malloc (1024); + const_unknown_fn (&s); + free (s.m_f); +} + +void +test_4 (void) +{ + struct st s; + s.m_f = malloc (1024); + unknown_fn (&s); +} /* { dg-bogus "leak" } */ + +void +test_5 (void) +{ + struct st s; + s.m_f = malloc (1024); + /* s is const, but the pointer could still be freed; hence not a leak. */ + const_unknown_fn (&s); +} /* { dg-bogus "leak" } */ + +void +test_6 (void) +{ + struct st s; + s.m_f = malloc (1024); +} /* { dg-warning "leak" } */ + +struct st +test_7 (void) +{ + struct st s; + s.m_f = malloc (1024); + return s; +} /* { dg-bogus "leak" } */ + +struct node * +test_8 (void) +{ + struct node *n1 = malloc (sizeof (struct node)); + if (!n1) + return NULL; + n1->m_next = malloc (sizeof (struct node)); + return n1; +} + +void +test_9 (void) +{ + struct node *n1 = malloc (sizeof (struct node)); + if (!n1) + return; + n1->m_next = malloc (sizeof (struct node)); + /* Could free n1 and n1->m_next. */ + unknown_fn (n1); +} + +void +test_10 (void) +{ + struct node *n1 = malloc (sizeof (struct node)); + if (!n1) + return; + n1->m_next = malloc (sizeof (struct node)); + /* Could free n1->m_next, but not n1. */ + const_unknown_fn (n1); /* { dg-warning "leak of 'n1'" } */ +} + +void +test_11 (void) +{ + struct node *n1 = malloc (sizeof (struct node)); + if (!n1) + return; + n1->m_next = malloc (sizeof (struct node)); + /* Could free n1->m_next, but not n1. */ + unknown_fn (n1->m_next); /* { dg-warning "leak of 'n1'" } */ +} + +void +test_12a (void) +{ + int *ip = malloc (sizeof (int)); + *ip = 42; /* { dg-warning "dereference of possibly-NULL 'ip'" } */ + free (ip); +} + +void +test_12b (void) +{ + int *ip = malloc (sizeof (int)); + unknown_fn (ip); + /* Might not be a null-deref, as unknown_fn could abort on NULL. */ + *ip = 42; + free (ip); +} + +void +test_12c (void) +{ + int *ip = malloc (sizeof (int)); + /* Might not be a null-deref, as const_unknown_fn could abort on NULL. + Right now we don't have a great way of handling this. */ + const_unknown_fn (ip); + *ip = 42; /* { dg-bogus "dereference of possibly-NULL 'ip'" "" { xfail *-*-* } } */ + free (ip); +}