| /* Operations within the code being analyzed. |
| Copyright (C) 2019-2025 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "analyzer/common.h" |
| |
| #include "gimple-pretty-print.h" |
| #include "gimple-iterator.h" |
| #include "tree-cfg.h" |
| #include "tree-dfa.h" |
| #include "fold-const.h" |
| #include "cgraph.h" |
| #include "text-art/dump.h" |
| #include "text-art/tree-widget.h" |
| |
| #include "analyzer/ops.h" |
| #include "analyzer/call-details.h" |
| #include "analyzer/exploded-graph.h" |
| #include "analyzer/checker-path.h" |
| #include "analyzer/impl-sm-context.h" |
| #include "analyzer/constraint-manager.h" |
| #include "analyzer/call-summary.h" |
| #include "analyzer/call-info.h" |
| #include "analyzer/analysis-plan.h" |
| |
| #if ENABLE_ANALYZER |
| |
| namespace ana { |
| |
| event_loc_info::event_loc_info (const exploded_node *enode) |
| { |
| if (enode) |
| { |
| m_loc = enode->get_location (); |
| m_fndecl = enode->get_point ().get_fndecl (); |
| m_depth = enode->get_stack_depth (); |
| } |
| else |
| { |
| m_loc = UNKNOWN_LOCATION; |
| m_fndecl = NULL_TREE; |
| m_depth = 0; |
| } |
| } |
| |
| event_loc_info::event_loc_info (const program_point &point) |
| { |
| m_loc = point.get_location (); |
| m_fndecl = point.get_fndecl (); |
| m_depth = point.get_stack_depth (); |
| } |
| |
| // struct operation_context |
| |
| void |
| operation_context::dump () const |
| { |
| fprintf (stderr, "src enode: EN: %i\n", m_src_enode.m_index); |
| m_src_enode.dump (m_eg.get_ext_state ()); |
| |
| fprintf (stderr, "superedge\n"); |
| pretty_printer pp; |
| pp.set_output_stream (stderr); |
| m_sedge.dump (&pp); |
| } |
| |
| logger * |
| operation_context::get_logger () const |
| { |
| return m_eg.get_logger (); |
| } |
| |
| const extrinsic_state & |
| operation_context::get_ext_state () const |
| { |
| return m_eg.get_ext_state (); |
| } |
| |
| const program_point & |
| operation_context::get_initial_point () const |
| { |
| return m_src_enode.get_point (); |
| } |
| |
| const program_state & |
| operation_context::get_initial_state () const |
| { |
| return m_src_enode.get_state (); |
| } |
| |
| const supergraph & |
| operation_context::get_supergraph () const |
| { |
| return m_eg.get_supergraph (); |
| } |
| |
| program_point |
| operation_context::get_next_intraprocedural_point () const |
| { |
| /* All edges are intraprocedural. */ |
| gcc_assert (m_sedge.m_src->get_function () |
| == m_sedge.m_dest->get_function ()); |
| return program_point (m_sedge.m_dest, |
| m_src_enode.get_point ().get_call_string ()); |
| } |
| |
| void |
| operation_context::add_outcome (const program_point &dst_point, |
| program_state dst_state, |
| bool could_do_work, |
| uncertainty_t *uncertainty, |
| std::unique_ptr<custom_edge_info> info) |
| { |
| const program_state &src_state = get_initial_state (); |
| impl_region_model_context ctxt (m_eg, &m_src_enode, |
| &src_state, &dst_state, |
| uncertainty, nullptr); |
| program_state::detect_leaks (src_state, dst_state, nullptr, |
| get_ext_state (), &ctxt); |
| |
| if (exploded_node *dst_enode |
| = m_eg.get_or_create_node (dst_point, dst_state, &m_src_enode)) |
| { |
| m_eg.add_edge (&m_src_enode, dst_enode, &m_sedge, could_do_work, |
| std::move (info)); |
| m_eg.detect_infinite_recursion (dst_enode); |
| } |
| } |
| |
| class op_region_model_context : public impl_region_model_context |
| { |
| public: |
| op_region_model_context (operation_context &op_ctxt, |
| program_state &dst_state) |
| : impl_region_model_context (op_ctxt.m_eg, |
| &op_ctxt.m_src_enode, |
| &op_ctxt.get_initial_state (), |
| &dst_state, |
| nullptr, |
| &m_path_context) |
| { |
| } |
| |
| bool terminate_path_p () const |
| { |
| return m_path_context.terminate_path_p (); |
| } |
| |
| private: |
| class op_path_context : public path_context |
| { |
| public: |
| op_path_context () |
| : m_terminate_path (false) |
| { |
| } |
| |
| void bifurcate (std::unique_ptr<custom_edge_info>) final override |
| { |
| gcc_unreachable (); |
| } |
| |
| void terminate_path () final override |
| { |
| m_terminate_path = true; |
| } |
| |
| bool terminate_path_p () const final override |
| { |
| return m_terminate_path; |
| } |
| private: |
| bool m_terminate_path; |
| } m_path_context; |
| }; |
| |
| // class gimple_stmt_op : public operation |
| |
| void |
| gimple_stmt_op::print_as_edge_label (pretty_printer *pp, |
| bool /*user_facing*/) const |
| { |
| pp_gimple_stmt_1 (pp, &m_stmt, 0, (dump_flags_t)0); |
| } |
| |
| bool |
| gimple_stmt_op::defines_ssa_name_p (const_tree ssa_name) const |
| { |
| return &m_stmt == SSA_NAME_DEF_STMT (ssa_name); |
| } |
| |
| bool |
| gimple_stmt_op::supports_bulk_merge_p () const |
| { |
| return false; |
| } |
| |
| /* Subclass of path_context for use within operation::execute implementations |
| 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, |
| logger *logger) |
| : m_cur_state (cur_state), |
| m_logger (logger), |
| 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 (std::unique_ptr<custom_edge_info> info) final override |
| { |
| if (m_logger) |
| m_logger->log ("bifurcating path"); |
| |
| 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<program_state> (new program_state (*m_cur_state)); |
| |
| /* Take ownership of INFO. */ |
| m_custom_eedge_infos.safe_push (info.release ()); |
| } |
| |
| void terminate_path () final override |
| { |
| if (m_logger) |
| m_logger->log ("terminating path"); |
| m_terminate_path = true; |
| } |
| |
| bool terminate_path_p () const final override |
| { |
| return m_terminate_path; |
| } |
| |
| const vec<custom_edge_info *> & get_custom_eedge_infos () |
| { |
| return m_custom_eedge_infos; |
| } |
| |
| private: |
| const program_state *m_cur_state; |
| |
| logger *m_logger; |
| |
| /* Lazily-created copy of the state before the split. */ |
| std::unique_ptr<program_state> m_state_at_bifurcation; |
| |
| auto_vec <custom_edge_info *> m_custom_eedge_infos; |
| |
| bool m_terminate_path; |
| }; |
| |
| DEBUG_FUNCTION void |
| operation::dump () const |
| { |
| tree_dump_pretty_printer pp (stderr); |
| print_as_edge_label (&pp, false); |
| pp_newline (&pp); |
| } |
| |
| void |
| operation::handle_on_stmt_for_state_machines (operation_context &op_ctxt, |
| program_state &dst_state, |
| path_context *path_ctxt, |
| bool &unknown_side_effects, |
| const gimple &stmt) |
| { |
| const program_state &old_state = op_ctxt.get_initial_state (); |
| int sm_idx; |
| sm_state_map *smap; |
| FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap) |
| { |
| const state_machine &sm = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx); |
| const sm_state_map *old_smap |
| = old_state.m_checker_states[sm_idx]; |
| sm_state_map *new_smap = dst_state.m_checker_states[sm_idx]; |
| impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm, |
| &op_ctxt.m_src_enode, |
| &old_state, |
| &dst_state, |
| old_smap, new_smap, path_ctxt, |
| unknown_side_effects); |
| |
| /* Allow the state_machine to handle the stmt. */ |
| if (sm.on_stmt (sm_ctxt, &stmt)) |
| unknown_side_effects = false; |
| } |
| } |
| |
| void |
| gimple_stmt_op:: |
| walk_load_store_addr_ops (void *data, |
| walk_stmt_load_store_addr_fn load_cb, |
| walk_stmt_load_store_addr_fn store_cb, |
| walk_stmt_load_store_addr_fn addr_cb) const |
| { |
| walk_stmt_load_store_addr_ops (const_cast<gimple *>(&m_stmt), data, |
| load_cb, store_cb, addr_cb); |
| } |
| |
| void |
| gimple_stmt_op::execute (operation_context &op_ctxt) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| if (logger) |
| { |
| logger->start_log_line (); |
| pp_gimple_stmt_1 (logger->get_printer (), &get_stmt (), 0, |
| (dump_flags_t)0); |
| logger->end_log_line (); |
| } |
| execute_on_state (op_ctxt, |
| /* Pass in a copy. */ |
| op_ctxt.get_initial_state ()); |
| } |
| |
| void |
| gimple_stmt_op::execute_on_state (operation_context &op_ctxt, |
| program_state dst_state) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| |
| auto dst_point (op_ctxt.get_next_intraprocedural_point ()); |
| const program_state &old_state = op_ctxt.get_initial_state (); |
| |
| bool unknown_side_effects = false; |
| bool could_have_done_work = false; |
| |
| impl_path_context path_ctxt (&dst_state, logger); |
| uncertainty_t uncertainty; |
| impl_region_model_context ctxt (op_ctxt.m_eg, |
| &op_ctxt.m_src_enode, |
| &old_state, |
| &dst_state, |
| &uncertainty, |
| &path_ctxt, |
| &m_stmt, |
| &could_have_done_work); |
| |
| dst_state.m_region_model->on_stmt_pre (&get_stmt (), |
| &unknown_side_effects, |
| &ctxt); |
| |
| handle_on_stmt_for_state_machines (op_ctxt, |
| dst_state, |
| &path_ctxt, |
| unknown_side_effects, |
| m_stmt); |
| |
| if (path_ctxt.terminate_path_p ()) |
| return; |
| |
| if (const gcall *call = dyn_cast <const gcall *> (&m_stmt)) |
| dst_state.m_region_model->on_call_post (*call, unknown_side_effects, &ctxt); |
| |
| if (!path_ctxt.terminate_path_p ()) |
| op_ctxt.add_outcome (dst_point, dst_state, could_have_done_work, |
| &uncertainty); |
| |
| /* 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_iter : path_ctxt.get_custom_eedge_infos ()) |
| { |
| /* Take ownership of the edge infos from the path_ctxt. */ |
| std::unique_ptr<custom_edge_info> edge_info (edge_info_iter); |
| 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 (op_ctxt.m_eg, |
| &op_ctxt.m_src_enode, |
| &path_ctxt.get_state_at_bifurcation (), |
| &bifurcated_new_state, |
| nullptr, // uncertainty_t *uncertainty |
| nullptr, // path_context *path_ctxt |
| &m_stmt); |
| if (edge_info->update_state (&bifurcated_new_state, |
| nullptr, /* no exploded_edge yet. */ |
| &bifurcation_ctxt)) |
| { |
| if (exploded_node *next2 |
| = edge_info->create_enode |
| (op_ctxt.m_eg, |
| dst_point, |
| std::move (bifurcated_new_state), |
| &op_ctxt.m_src_enode, |
| &bifurcation_ctxt)) |
| { |
| op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode, next2, nullptr, |
| true /* assume that work could be done */, |
| std::move (edge_info)); |
| } |
| } |
| } |
| } |
| |
| bool |
| gimple_stmt_op:: |
| execute_for_feasibility (const exploded_edge &, |
| feasibility_state &fstate, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> */*out_rc*/) const |
| { |
| region_model &model = fstate.get_model (); |
| bool unknown_side_effects; |
| model.on_stmt_pre (&m_stmt, &unknown_side_effects, ctxt); |
| |
| if (const gcall *call = dyn_cast <const gcall *> (&m_stmt)) |
| model.on_call_post (*call, unknown_side_effects, ctxt); |
| |
| return true; |
| } |
| |
| /* An sm_context for adding state_change_event on assignments to NULL, |
| where the default state isn't m_start. Storing such state in the |
| sm_state_map would lead to bloat of the exploded_graph, so we want |
| to leave it as a default state, and inject state change events here |
| when we have a diagnostic. |
| Find transitions of constants, for handling on_zero_assignment. */ |
| |
| struct null_assignment_sm_context : public sm_context |
| { |
| null_assignment_sm_context (int sm_idx, |
| const state_machine &sm, |
| const program_state *old_state, |
| const program_state *new_state, |
| const gimple *stmt, |
| const program_point *point, |
| checker_path *emission_path, |
| const extrinsic_state &ext_state) |
| : sm_context (sm_idx, sm), m_old_state (old_state), m_new_state (new_state), |
| m_stmt (stmt), m_point (point), m_emission_path (emission_path), |
| m_ext_state (ext_state) |
| { |
| } |
| |
| tree get_fndecl_for_call (const gcall &/*call*/) final override |
| { |
| return NULL_TREE; |
| } |
| |
| state_machine::state_t get_state (tree var) final override |
| { |
| const svalue *var_old_sval |
| = m_old_state->m_region_model->get_rvalue (var, nullptr); |
| const sm_state_map *old_smap = m_old_state->m_checker_states[m_sm_idx]; |
| |
| state_machine::state_t current |
| = old_smap->get_state (var_old_sval, m_ext_state); |
| |
| return current; |
| } |
| |
| state_machine::state_t get_state (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 (tree var, |
| state_machine::state_t to, |
| tree origin ATTRIBUTE_UNUSED) final override |
| { |
| state_machine::state_t from = get_state (var); |
| if (from != m_sm.get_start_state ()) |
| return; |
| if (!is_transition_to_null (to)) |
| return; |
| |
| const svalue *var_new_sval |
| = m_new_state->m_region_model->get_rvalue (var, nullptr); |
| |
| m_emission_path->add_event |
| (std::make_unique<state_change_event> (event_loc_info (*m_point), |
| m_stmt, |
| m_sm, |
| var_new_sval, |
| from, to, |
| nullptr, |
| *m_new_state, |
| nullptr)); |
| } |
| |
| void set_next_state (const svalue *sval, |
| state_machine::state_t to, |
| tree origin ATTRIBUTE_UNUSED) final override |
| { |
| state_machine::state_t from = get_state (sval); |
| if (from != m_sm.get_start_state ()) |
| return; |
| if (!is_transition_to_null (to)) |
| return; |
| |
| m_emission_path->add_event |
| (std::make_unique<state_change_event> (event_loc_info (*m_point), |
| m_stmt, |
| m_sm, |
| sval, |
| from, to, |
| nullptr, |
| *m_new_state, |
| nullptr)); |
| } |
| |
| void warn (tree, std::unique_ptr<pending_diagnostic>) final override |
| { |
| } |
| void warn (const svalue *, std::unique_ptr<pending_diagnostic>) final override |
| { |
| } |
| |
| tree get_diagnostic_tree (tree expr) final override |
| { |
| 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; |
| } |
| |
| void set_global_state (state_machine::state_t) final override |
| { |
| /* No-op. */ |
| } |
| |
| void clear_all_per_svalue_state () final override |
| { |
| /* No-op. */ |
| } |
| |
| void on_custom_transition (custom_transition *) final override |
| { |
| } |
| |
| tree is_zero_assignment (const gimple *stmt) final override |
| { |
| const gassign *assign_stmt = dyn_cast <const gassign *> (stmt); |
| if (!assign_stmt) |
| return NULL_TREE; |
| if (const svalue *sval |
| = m_new_state->m_region_model->get_gassign_result (assign_stmt, nullptr)) |
| if (tree cst = sval->maybe_get_constant ()) |
| if (::zerop(cst)) |
| return gimple_assign_lhs (assign_stmt); |
| return NULL_TREE; |
| } |
| |
| const program_state *get_old_program_state () const final override |
| { |
| return m_old_state; |
| } |
| const program_state *get_new_program_state () const final override |
| { |
| return m_new_state; |
| } |
| |
| location_t get_emission_location () const final override |
| { |
| return UNKNOWN_LOCATION; |
| } |
| |
| /* We only care about transitions to the "null" state |
| within sm-malloc. Special-case this. */ |
| static bool is_transition_to_null (state_machine::state_t s) |
| { |
| return !strcmp (s->get_name (), "null"); |
| } |
| |
| const program_state *m_old_state; |
| const program_state *m_new_state; |
| const gimple *m_stmt; |
| const program_point *m_point; |
| checker_path *m_emission_path; |
| const extrinsic_state &m_ext_state; |
| }; |
| |
| void |
| gimple_stmt_op::add_any_events_for_eedge (const exploded_edge &eedge, |
| checker_path &out_path) const |
| { |
| out_path.add_event |
| (std::make_unique<statement_event> (&get_stmt (), |
| eedge.m_dest->get_function ()->decl, |
| eedge.m_dest->get_stack_depth (), |
| eedge.m_dest->get_state ())); |
| |
| /* Create state change events for assignment to NULL. |
| Iterate through the stmts in dst_enode, adding state change |
| events for them. */ |
| if (const gassign *assign = dyn_cast<const gassign *> (&m_stmt)) |
| { |
| const program_point &src_point = eedge.m_src->get_point (); |
| const extrinsic_state &ext_state = out_path.get_ext_state (); |
| for (unsigned i = 0; i < ext_state.get_num_checkers (); i++) |
| { |
| const state_machine &sm = ext_state.get_sm (i); |
| null_assignment_sm_context sm_ctxt (i, sm, |
| &eedge.m_src->get_state (), |
| &eedge.m_dest->get_state (), |
| assign, |
| &src_point, |
| &out_path, |
| ext_state); |
| sm.on_stmt (sm_ctxt, assign); |
| // TODO: what about phi nodes? |
| } |
| } |
| } |
| |
| // class gassign_op : public gimple_stmt_op |
| |
| // class greturn_op : public gimple_stmt_op |
| |
| void |
| greturn_op::execute (operation_context &op_ctxt) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| |
| auto dst_point (op_ctxt.get_next_intraprocedural_point ()); |
| const program_state &old_state = op_ctxt.get_initial_state (); |
| program_state dst_state (old_state); |
| |
| impl_path_context path_ctxt (&dst_state, logger); |
| uncertainty_t uncertainty; |
| impl_region_model_context ctxt (op_ctxt.m_eg, |
| &op_ctxt.m_src_enode, |
| |
| /* TODO: should we be getting the ECs from the |
| old state, rather than the new? */ |
| &op_ctxt.get_initial_state (), |
| &dst_state, |
| &uncertainty, |
| &path_ctxt, |
| nullptr, |
| nullptr); |
| |
| tree callee = op_ctxt.get_initial_point ().get_function ()->decl; |
| tree lhs = DECL_RESULT (callee); |
| |
| if (lhs && get_retval ()) |
| { |
| region_model *dst_region_model = dst_state.m_region_model; |
| const svalue *sval |
| = dst_region_model->get_rvalue (get_retval (), &ctxt); |
| const region *ret_reg = dst_region_model->get_lvalue (lhs, &ctxt); |
| dst_region_model->set_value (ret_reg, sval, &ctxt); |
| } |
| |
| if (!path_ctxt.terminate_path_p ()) |
| op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty); |
| } |
| |
| bool |
| greturn_op:: |
| execute_for_feasibility (const exploded_edge &eedge, |
| feasibility_state &fstate, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> *) const |
| { |
| tree callee = eedge.m_src->get_function ()->decl; |
| tree lhs = DECL_RESULT (callee); |
| |
| if (lhs && get_retval ()) |
| { |
| region_model &model = fstate.get_model (); |
| const svalue *sval = model.get_rvalue (get_retval (), ctxt); |
| const region *ret_reg = model.get_lvalue (lhs, ctxt); |
| model.set_value (ret_reg, sval, ctxt); |
| } |
| |
| return true; |
| } |
| |
| void |
| greturn_op::add_any_events_for_eedge (const exploded_edge &, |
| checker_path &) const |
| { |
| // No-op. |
| } |
| |
| // class call_and_return_op : public gimple_stmt_op |
| |
| std::unique_ptr<operation> |
| call_and_return_op::make (const gcall &call_stmt) |
| { |
| if (is_special_named_call_p (call_stmt, "__analyzer_dump", 0)) |
| return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state); |
| else if (is_special_named_call_p (call_stmt, "__analyzer_dump_sarif", 0)) |
| return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::sarif); |
| else if (is_special_named_call_p (call_stmt, "__analyzer_dump_dot", 0)) |
| return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::dot); |
| else if (is_special_named_call_p (call_stmt, "__analyzer_dump_state", 2)) |
| return std::make_unique<dump_op> (call_stmt, dump_op::dump_kind::state_2); |
| else if (is_setjmp_call_p (call_stmt)) |
| return std::make_unique<setjmp_op> (call_stmt); |
| else if (is_longjmp_call_p (call_stmt)) |
| return std::make_unique<longjmp_op> (call_stmt); |
| else if (is_cxa_throw_p (call_stmt)) |
| return std::make_unique<cxa_throw_op> (call_stmt, false); |
| else if (is_cxa_rethrow_p (call_stmt)) |
| return std::make_unique<cxa_throw_op> (call_stmt, true); |
| |
| return std::make_unique<call_and_return_op> (call_stmt); |
| } |
| |
| /* Resolve a function call by one of: |
| |
| (a) using a call summary to add eedges to new enodes capturing |
| the states after summarized outcomes of the call |
| |
| (b) adding an interprocedural_call edge, effectively "stepping into" |
| the called function, for detailed analysis of that path |
| |
| (c) simulating the effect of the call, adding an eedge to a new |
| enode for the outcome of the call. */ |
| |
| void |
| call_and_return_op::execute (operation_context &op_ctxt) const |
| { |
| /* Can we turn this into an interprocedural call, and execute within |
| the called fuction? */ |
| const program_state &old_state = op_ctxt.get_initial_state (); |
| program_state dst_state (old_state); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| ctxt.m_stmt = &get_gcall (); |
| call_details cd (get_gcall (), old_state.m_region_model, &ctxt); |
| |
| /* Regardless of how we handle the call, check any known |
| preconditions. */ |
| { |
| /* Check for any preconditions if it's a known_function. */ |
| if (auto kf = maybe_get_known_function (cd)) |
| kf->check_any_preconditions (cd); |
| |
| /* Check for any preconditions using sm-state. */ |
| { |
| int sm_idx; |
| sm_state_map *smap; |
| FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap) |
| { |
| const state_machine &sm |
| = op_ctxt.m_eg.get_ext_state ().get_sm (sm_idx); |
| const sm_state_map *old_smap |
| = old_state.m_checker_states[sm_idx]; |
| sm_state_map *new_smap = dst_state.m_checker_states[sm_idx]; |
| impl_sm_context sm_ctxt (op_ctxt.m_eg, sm_idx, sm, |
| &op_ctxt.m_src_enode, |
| &old_state, &dst_state, |
| old_smap, new_smap, nullptr); |
| sm.check_call_preconditions (sm_ctxt, cd); |
| } |
| } |
| } |
| |
| if (tree callee_fndecl = cd.get_fndecl_for_call ()) |
| { |
| // Consider using a call summary |
| if (function *called_fn = DECL_STRUCT_FUNCTION (callee_fndecl)) |
| if (cgraph_edge *edge = get_any_cgraph_edge (op_ctxt)) |
| if (op_ctxt.m_eg.get_analysis_plan ().use_summary_p (edge)) |
| { |
| per_function_data *called_fn_data |
| = op_ctxt.m_eg.get_per_function_data (called_fn); |
| if (called_fn_data) |
| { |
| replay_call_summaries (op_ctxt, *called_fn, |
| *called_fn_data, &ctxt); |
| return; |
| } |
| } |
| |
| // Do we have an entry snode for this fndecl? |
| if (auto callee_fun = DECL_STRUCT_FUNCTION (callee_fndecl)) |
| if (supernode *callee_entry_snode |
| = (op_ctxt.get_supergraph () |
| .get_node_for_function_entry (*callee_fun))) |
| { |
| const call_string *dst_call_string |
| (op_ctxt.m_src_enode |
| .get_point () |
| .get_call_string () |
| .push_call (op_ctxt.m_sedge, *this, *callee_fun)); |
| const program_point dst_point |
| (callee_entry_snode, *dst_call_string); |
| auto edge_info |
| = std::make_unique<interprocedural_call> (get_gcall (), |
| *callee_fun); |
| edge_info->update_state (&dst_state, nullptr, &ctxt); |
| op_ctxt.add_outcome (dst_point, dst_state, false, nullptr, |
| std::move (edge_info)); |
| return; |
| } |
| } |
| |
| /* Resolve intraprocedurally: execute the gcall, but using the |
| dst_state from above so that any preconditions have been applied. */ |
| gimple_stmt_op::execute_on_state (op_ctxt, std::move (dst_state)); |
| } |
| |
| cgraph_edge * |
| call_and_return_op::get_any_cgraph_edge (operation_context &op_ctxt) const |
| { |
| tree caller_fndecl = op_ctxt.get_initial_point ().get_fndecl (); |
| gcc_assert (caller_fndecl); |
| |
| auto caller_cgnode = cgraph_node::get (caller_fndecl); |
| gcc_assert (caller_cgnode); |
| return caller_cgnode->get_edge (const_cast<gcall *> (&get_gcall ())); |
| } |
| |
| void |
| call_and_return_op:: |
| add_any_events_for_eedge (const exploded_edge &, |
| checker_path &) const |
| { |
| } |
| |
| /* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it |
| to *OUT if OUT is non-NULL), and return the corresponding argument |
| at the callsite. */ |
| |
| tree |
| call_and_return_op::get_arg_for_parm (tree callee_fndecl, |
| tree parm_to_find, |
| callsite_expr *out) const |
| { |
| gcc_assert (TREE_CODE (parm_to_find) == PARM_DECL); |
| |
| const gcall &call_stmt = get_gcall (); |
| |
| unsigned i = 0; |
| for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm; |
| iter_parm = DECL_CHAIN (iter_parm), ++i) |
| { |
| if (i >= gimple_call_num_args (&call_stmt)) |
| return NULL_TREE; |
| if (iter_parm == parm_to_find) |
| { |
| if (out) |
| *out = callsite_expr::from_zero_based_param (i); |
| return gimple_call_arg (&call_stmt, i); |
| } |
| } |
| |
| /* Not found. */ |
| return NULL_TREE; |
| } |
| |
| /* Look for a use of ARG_TO_FIND as an argument at this callsite. |
| If found, return the default SSA def of the corresponding parm within |
| the callee, and if OUT is non-NULL, write the index to *OUT. |
| Only the first match is handled. */ |
| |
| tree |
| call_and_return_op::get_parm_for_arg (tree callee_fndecl, |
| tree arg_to_find, |
| callsite_expr *out) const |
| { |
| const gcall &call_stmt = get_gcall (); |
| |
| unsigned i = 0; |
| for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm; |
| iter_parm = DECL_CHAIN (iter_parm), ++i) |
| { |
| if (i >= gimple_call_num_args (&call_stmt)) |
| return NULL_TREE; |
| tree param = gimple_call_arg (&call_stmt, i); |
| if (arg_to_find == param) |
| { |
| if (out) |
| *out = callsite_expr::from_zero_based_param (i); |
| return ssa_default_def (DECL_STRUCT_FUNCTION (callee_fndecl), |
| iter_parm); |
| } |
| } |
| |
| /* Not found. */ |
| return NULL_TREE; |
| } |
| |
| /* Map caller_expr back to an expr within the callee, or return NULL_TREE. |
| If non-NULL is returned, populate OUT. */ |
| |
| tree |
| call_and_return_op::map_expr_from_caller_to_callee (tree callee_fndecl, |
| tree caller_expr, |
| callsite_expr *out) const |
| { |
| /* Is it an argument (actual param)? If so, convert to |
| parameter (formal param). */ |
| tree parm = get_parm_for_arg (callee_fndecl, caller_expr, out); |
| if (parm) |
| return parm; |
| /* Otherwise try return value. */ |
| if (caller_expr == gimple_call_lhs (&get_gcall ())) |
| { |
| if (out) |
| *out = callsite_expr::from_return_value (); |
| return DECL_RESULT (callee_fndecl); |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Map callee_expr back to an expr within the caller, or return NULL_TREE. |
| If non-NULL is returned, populate OUT. */ |
| |
| tree |
| call_and_return_op::map_expr_from_callee_to_caller (tree callee_fndecl, |
| tree callee_expr, |
| callsite_expr *out) const |
| { |
| if (callee_expr == NULL_TREE) |
| return NULL_TREE; |
| |
| /* If it's a parameter (formal param), get the argument (actual param). */ |
| if (TREE_CODE (callee_expr) == PARM_DECL) |
| return get_arg_for_parm (callee_fndecl, callee_expr, out); |
| |
| /* Similar for the default SSA name of the PARM_DECL. */ |
| if (TREE_CODE (callee_expr) == SSA_NAME |
| && SSA_NAME_IS_DEFAULT_DEF (callee_expr) |
| && TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL) |
| return get_arg_for_parm (callee_fndecl, SSA_NAME_VAR (callee_expr), out); |
| |
| /* Otherwise try return value. */ |
| if (callee_expr == DECL_RESULT (callee_fndecl)) |
| { |
| if (out) |
| *out = callsite_expr::from_return_value (); |
| return gimple_call_lhs (&get_gcall ()); |
| } |
| |
| return NULL_TREE; |
| } |
| |
| const known_function * |
| call_and_return_op::maybe_get_known_function (const call_details &cd) const |
| { |
| region_model_manager *mgr = cd.get_manager (); |
| known_function_manager *known_fn_mgr = mgr->get_known_function_manager (); |
| |
| if (gimple_call_internal_p (&get_gcall ())) |
| return known_fn_mgr->get_internal_fn |
| (gimple_call_internal_fn (&get_gcall ())); |
| |
| if (tree callee_fndecl = cd.get_fndecl_for_call ()) |
| return known_fn_mgr->get_match (callee_fndecl, cd); |
| |
| return nullptr; |
| } |
| |
| void |
| call_and_return_op:: |
| replay_call_summaries (operation_context &op_ctxt, |
| function &called_fn, |
| per_function_data &called_fn_data, |
| region_model_context *ctxt) const |
| { |
| logger *logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| |
| for (auto summary : called_fn_data.m_summaries) |
| { |
| gcc_assert (summary); |
| replay_call_summary (op_ctxt, called_fn, *summary, ctxt); |
| } |
| } |
| |
| /* A concrete call_info subclass representing a replay of a call summary. */ |
| |
| class call_summary_edge_info : public call_info |
| { |
| public: |
| call_summary_edge_info (const call_details &cd, |
| const function &called_fn, |
| call_summary &summary, |
| const extrinsic_state &ext_state) |
| : call_info (cd, called_fn), |
| m_called_fn (called_fn), |
| m_summary (summary), |
| m_ext_state (ext_state) |
| {} |
| |
| bool update_state (program_state *state, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| /* Update STATE based on summary_end_state. */ |
| call_details cd (get_call_details (state->m_region_model, ctxt)); |
| call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state); |
| const program_state &summary_end_state = m_summary.get_state (); |
| return state->replay_call_summary (r, summary_end_state); |
| } |
| |
| bool update_model (region_model *model, |
| const exploded_edge *, |
| region_model_context *ctxt) const final override |
| { |
| /* Update STATE based on summary_end_state. */ |
| call_details cd (get_call_details (model, ctxt)); |
| call_summary_replay r (cd, m_called_fn, m_summary, m_ext_state); |
| const program_state &summary_end_state = m_summary.get_state (); |
| model->replay_call_summary (r, *summary_end_state.m_region_model); |
| return true; |
| } |
| |
| void print_desc (pretty_printer &pp) const final override |
| { |
| pp_string (&pp, m_summary.get_desc ().get ()); |
| } |
| |
| private: |
| const function &m_called_fn; |
| call_summary &m_summary; |
| const extrinsic_state &m_ext_state; |
| }; |
| |
| void |
| call_and_return_op:: |
| replay_call_summary (operation_context &op_ctxt, |
| function &called_fn, |
| call_summary &summary, |
| region_model_context *ctxt) const |
| { |
| logger *logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| if (logger) |
| logger->log ("using %s as summary for call to %qE from %qE", |
| summary.get_desc ().get (), |
| called_fn.decl, |
| op_ctxt.get_initial_point ().get_function ()->decl); |
| const extrinsic_state &ext_state = op_ctxt.get_ext_state (); |
| const program_state &old_state = op_ctxt.get_initial_state (); |
| const program_state &summary_end_state = summary.get_state (); |
| if (logger) |
| { |
| pretty_printer *pp = logger->get_printer (); |
| |
| logger->start_log_line (); |
| pp_string (pp, "callsite state: "); |
| old_state.dump_to_pp (ext_state, true, false, pp); |
| logger->end_log_line (); |
| |
| logger->start_log_line (); |
| pp_string (pp, "summary end state: "); |
| summary_end_state.dump_to_pp (ext_state, true, false, pp); |
| logger->end_log_line (); |
| } |
| |
| program_state new_state (old_state); |
| |
| call_details cd (get_gcall (), new_state.m_region_model, ctxt); |
| call_summary_replay r (cd, called_fn, summary, ext_state); |
| |
| if (new_state.replay_call_summary (r, summary_end_state)) |
| op_ctxt.add_outcome |
| (op_ctxt.get_next_intraprocedural_point (), |
| new_state, |
| true, |
| nullptr, |
| std::make_unique<call_summary_edge_info> (cd, |
| called_fn, |
| summary, |
| ext_state)); |
| } |
| |
| // class dump_op : public call_and_return_op |
| |
| void |
| dump_op::execute (operation_context &op_ctxt) const |
| { |
| const program_state &state = op_ctxt.get_initial_state (); |
| switch (m_dump_kind) |
| { |
| default: |
| gcc_unreachable (); |
| case dump_kind::state: |
| /* Handle the builtin "__analyzer_dump" by dumping state |
| to stderr. */ |
| state.dump (op_ctxt.get_ext_state (), true); |
| break; |
| case dump_kind::sarif: |
| state.dump_sarif (op_ctxt.get_ext_state ()); |
| break; |
| case dump_kind::dot: |
| state.dump_dot (op_ctxt.get_ext_state ()); |
| break; |
| case dump_kind::state_2: |
| { |
| program_state dst_state (state); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| dst_state.impl_call_analyzer_dump_state (get_gcall (), |
| op_ctxt.get_ext_state (), |
| &ctxt); |
| } |
| break; |
| } |
| |
| op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (), |
| state, false, nullptr); |
| } |
| |
| // class setjmp_op : public call_and_return_op |
| |
| void |
| setjmp_op::execute (operation_context &op_ctxt) const |
| { |
| program_state dst_state (op_ctxt.get_initial_state ()); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| dst_state.m_region_model->on_setjmp (get_gcall (), |
| op_ctxt.m_src_enode, |
| op_ctxt.m_sedge, |
| &ctxt); |
| op_ctxt.add_outcome (op_ctxt.get_next_intraprocedural_point (), |
| dst_state, true, nullptr); |
| } |
| |
| void |
| setjmp_op::add_any_events_for_eedge (const exploded_edge &eedge, |
| checker_path &out_path) const |
| { |
| out_path.add_event |
| (std::make_unique<setjmp_event> |
| (event_loc_info (eedge.m_src), |
| eedge.m_src, |
| get_gcall ())); |
| } |
| |
| // class longjmp_op : public call_and_return_op |
| |
| void |
| longjmp_op::execute (operation_context &op_ctxt) const |
| { |
| program_state dst_state (op_ctxt.get_initial_state ()); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| op_ctxt.m_src_enode.on_longjmp (op_ctxt.m_eg, get_gcall (), &dst_state, |
| &ctxt); |
| } |
| |
| // class cxa_throw_op : public call_and_return_op |
| |
| void |
| cxa_throw_op::execute (operation_context &op_ctxt) const |
| { |
| program_state dst_state (op_ctxt.get_initial_state ()); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| program_point after_throw_point (op_ctxt.get_next_intraprocedural_point ()); |
| op_ctxt.m_src_enode.on_throw (op_ctxt.m_eg, |
| get_gcall (), |
| after_throw_point, |
| &dst_state, |
| m_is_rethrow, |
| &ctxt); |
| // We don't continue along op_ctxt's superedge |
| } |
| |
| // class control_flow_op : public operation |
| |
| void |
| control_flow_op:: |
| walk_load_store_addr_ops (void *data, |
| walk_stmt_load_store_addr_fn load_cb, |
| walk_stmt_load_store_addr_fn store_cb, |
| walk_stmt_load_store_addr_fn addr_cb) const |
| { |
| walk_stmt_load_store_addr_ops (const_cast <gimple *> (&m_ctrlflow_stmt), |
| data, |
| load_cb, store_cb, addr_cb); |
| } |
| |
| void |
| control_flow_op::add_any_events_for_eedge (const exploded_edge &eedge, |
| checker_path &out_path) const |
| { |
| out_path.add_event |
| (std::make_unique<start_cfg_edge_event> (eedge, |
| event_loc_info (eedge.m_src), |
| this)); |
| out_path.add_event |
| (std::make_unique<end_cfg_edge_event> (eedge, |
| event_loc_info (eedge.m_dest), |
| this)); |
| } |
| |
| /* Attempt to generate a description of any condition that holds at this edge. |
| |
| The intent is to make the user-facing messages more clear, especially for |
| cases where there's a single or double-negative, such as |
| when describing the false branch of an inverted condition. |
| |
| For example, rather than printing just: |
| |
| | if (!ptr) |
| | ~ |
| | | |
| | (1) following 'false' branch... |
| |
| it's clearer to spell out the condition that holds: |
| |
| | if (!ptr) |
| | ~ |
| | | |
| | (1) following 'false' branch (when 'ptr' is non-NULL)... |
| ^^^^^^^^^^^^^^^^^^^^^^ |
| |
| In the above example, this function would generate the highlighted |
| string: "when 'ptr' is non-NULL". |
| |
| If the edge is not a condition, or it's not clear that a description of |
| the condition would be helpful to the user, return NULL. */ |
| |
| label_text |
| control_flow_op::maybe_describe_condition (bool ) const |
| { |
| return label_text::borrow (nullptr); |
| } |
| |
| void |
| control_flow_op::execute (operation_context &op_ctxt) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| |
| program_state dst_state (op_ctxt.get_initial_state ()); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| if (apply_constraints (&op_ctxt.m_sedge, |
| *dst_state.m_region_model, |
| &ctxt, |
| nullptr)) |
| { |
| bool unknown_side_effects; |
| handle_on_stmt_for_state_machines (op_ctxt, |
| dst_state, |
| nullptr, |
| unknown_side_effects, |
| m_ctrlflow_stmt); |
| |
| if (!ctxt.terminate_path_p ()) |
| { |
| auto dst_point (op_ctxt.get_next_intraprocedural_point ()); |
| op_ctxt.add_outcome (dst_point, dst_state, false, nullptr); |
| } |
| } |
| } |
| |
| bool |
| control_flow_op:: |
| execute_for_feasibility (const exploded_edge &eedge, |
| feasibility_state &fstate, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> *out_rc) const |
| { |
| gcc_assert (eedge.m_sedge); |
| return apply_constraints (eedge.m_sedge, |
| fstate.get_model (), |
| ctxt, |
| out_rc); |
| } |
| |
| // class gcond_edge_op : public control_flow_op |
| |
| gcond_edge_op::gcond_edge_op (::edge cfg_edge, |
| const gcond &cond_stmt) |
| : control_flow_op (kind::cond_edge, cfg_edge, cond_stmt), |
| m_true_value (get_flags () & EDGE_TRUE_VALUE) |
| { |
| /* Exactly one of EDGE_TRUE_VALUE and EDGE_FALSE_VALUE must |
| be set on CFG_EDGE. */ |
| gcc_assert (static_cast<bool> (get_flags () & EDGE_TRUE_VALUE) |
| ^ static_cast<bool> (get_flags () & EDGE_FALSE_VALUE)); |
| } |
| |
| void |
| gcond_edge_op::print_as_edge_label (pretty_printer *pp, |
| bool user_facing) const |
| { |
| if (!user_facing) |
| pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0); |
| |
| if (m_true_value) |
| pp_printf (pp, "true"); |
| else |
| pp_printf (pp, "false"); |
| } |
| |
| label_text |
| gcond_edge_op::maybe_describe_condition (bool can_colorize) const |
| { |
| const gcond &cond_stmt = get_gcond (); |
| enum tree_code op = gimple_cond_code (&cond_stmt); |
| tree lhs = gimple_cond_lhs (&cond_stmt); |
| tree rhs = gimple_cond_rhs (&cond_stmt); |
| if (!m_true_value) |
| op = invert_tree_comparison (op, false /* honor_nans */); |
| return maybe_describe_condition (can_colorize, |
| lhs, op, rhs); |
| } |
| |
| /* Subroutine of gcond_edge_op::maybe_describe_condition above. |
| |
| Attempt to generate a user-facing description of the condition |
| LHS OP RHS, but only if it is likely to make it easier for the |
| user to understand a condition. */ |
| |
| label_text |
| gcond_edge_op::maybe_describe_condition (bool can_colorize, |
| tree lhs, |
| enum tree_code op, |
| tree rhs) |
| { |
| /* In theory we could just build a tree via |
| fold_build2 (op, boolean_type_node, lhs, rhs) |
| and print it with %qE on it, but this leads to warts such as |
| parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'. */ |
| |
| /* Special-case: describe testing the result of strcmp, as figuring |
| out what the "true" or "false" path is can be confusing to the user. */ |
| if (TREE_CODE (lhs) == SSA_NAME |
| && zerop (rhs)) |
| { |
| if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs))) |
| if (is_special_named_call_p (*call, "strcmp", 2)) |
| { |
| if (op == EQ_EXPR) |
| return label_text::borrow ("when the strings are equal"); |
| if (op == NE_EXPR) |
| return label_text::borrow ("when the strings are non-equal"); |
| } |
| } |
| |
| /* Only attempt to generate text for sufficiently simple expressions. */ |
| if (!should_print_expr_p (lhs)) |
| return label_text::borrow (nullptr); |
| if (!should_print_expr_p (rhs)) |
| return label_text::borrow (nullptr); |
| |
| /* Special cases for pointer comparisons against NULL. */ |
| if (POINTER_TYPE_P (TREE_TYPE (lhs)) |
| && POINTER_TYPE_P (TREE_TYPE (rhs)) |
| && zerop (rhs)) |
| { |
| if (op == EQ_EXPR) |
| return make_label_text (can_colorize, "when %qE is NULL", |
| lhs); |
| if (op == NE_EXPR) |
| return make_label_text (can_colorize, "when %qE is non-NULL", |
| lhs); |
| } |
| |
| return make_label_text (can_colorize, "when %<%E %s %E%>", |
| lhs, op_symbol_code (op), rhs); |
| } |
| |
| /* Subroutine of maybe_describe_condition. |
| |
| Return true if EXPR is we will get suitable user-facing output |
| from %E on it. */ |
| |
| bool |
| gcond_edge_op::should_print_expr_p (tree expr) |
| { |
| if (TREE_CODE (expr) == SSA_NAME) |
| { |
| if (SSA_NAME_VAR (expr)) |
| return should_print_expr_p (SSA_NAME_VAR (expr)); |
| else |
| return false; |
| } |
| |
| if (DECL_P (expr)) |
| return true; |
| |
| if (CONSTANT_CLASS_P (expr)) |
| return true; |
| |
| return false; |
| } |
| |
| bool |
| gcond_edge_op:: |
| apply_constraints (const superedge *, |
| region_model &model, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> *out) const |
| { |
| const gcond &cond_stmt = get_gcond (); |
| enum tree_code op = gimple_cond_code (&cond_stmt); |
| tree lhs = gimple_cond_lhs (&cond_stmt); |
| tree rhs = gimple_cond_rhs (&cond_stmt); |
| if (!m_true_value) |
| op = invert_tree_comparison (op, false /* honor_nans */); |
| return model.add_constraint (lhs, op, rhs, ctxt, out); |
| } |
| |
| // class ggoto_edge_op : public control_flow_op |
| |
| ggoto_edge_op::ggoto_edge_op (::edge cfg_edge, |
| const ggoto &goto_stmt, |
| tree dst_label) |
| : control_flow_op (kind::goto_edge, cfg_edge, goto_stmt), |
| m_dst_label (dst_label) |
| { |
| } |
| |
| void |
| ggoto_edge_op::print_as_edge_label (pretty_printer *pp, |
| bool user_facing) const |
| { |
| if (!user_facing) |
| pp_gimple_stmt_1 (pp, &get_ctrlflow_stmt (), 0, (dump_flags_t)0); |
| |
| if (m_dst_label) |
| pp_printf (pp, "%qD", m_dst_label); |
| } |
| |
| label_text |
| ggoto_edge_op::maybe_describe_condition (bool) const |
| { |
| return label_text::borrow (""); |
| } |
| |
| bool |
| ggoto_edge_op:: |
| apply_constraints (const superedge *, |
| region_model &model, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> */*out_rc*/) const |
| { |
| const ggoto &goto_stmt = get_ggoto (); |
| tree dest = gimple_goto_dest (&goto_stmt); |
| const svalue *dest_sval = model.get_rvalue (dest, ctxt); |
| |
| /* If we know we were jumping to a specific label. */ |
| if (m_dst_label) |
| { |
| auto mgr = model.get_manager (); |
| const label_region *dst_label_reg |
| = mgr->get_region_for_label (m_dst_label); |
| const svalue *dst_label_ptr |
| = mgr->get_ptr_svalue (ptr_type_node, dst_label_reg); |
| |
| if (!model.add_constraint (dest_sval, EQ_EXPR, dst_label_ptr, ctxt)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // class switch_case_op : public control_flow_op |
| |
| switch_case_op::switch_case_op (function &fun, |
| ::edge cfg_edge, |
| const gswitch &switch_stmt, |
| bounded_ranges_manager &mgr) |
| : control_flow_op (kind::switch_edge, cfg_edge, switch_stmt) |
| { |
| /* Populate m_case_labels with all cases which go to DST. */ |
| for (unsigned i = 0; i < gimple_switch_num_labels (&switch_stmt); i++) |
| { |
| tree case_ = gimple_switch_label (&switch_stmt, i); |
| basic_block bb = label_to_block (&fun, |
| CASE_LABEL (case_)); |
| if (bb == cfg_edge->dest) |
| m_case_labels.push_back (case_); |
| } |
| |
| auto_vec <const bounded_ranges *> case_ranges_vec |
| (gimple_switch_num_labels (&switch_stmt)); |
| for (auto case_label : m_case_labels) |
| { |
| /* Get the ranges for this case label. */ |
| const bounded_ranges *case_ranges |
| = mgr.make_case_label_ranges (&switch_stmt, case_label); |
| case_ranges_vec.quick_push (case_ranges); |
| } |
| |
| m_all_cases_ranges = mgr.get_or_create_union (case_ranges_vec); |
| } |
| |
| /* Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP. */ |
| |
| void |
| switch_case_op::print_as_edge_label (pretty_printer *pp, |
| bool user_facing) const |
| { |
| if (user_facing) |
| { |
| for (unsigned i = 0; i < m_case_labels.size (); ++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) |
| { |
| 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:"); |
| } |
| } |
| else |
| { |
| pp_character (pp, '{'); |
| for (unsigned i = 0; i < m_case_labels.size (); ++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, '}'); |
| if (implicitly_created_default_p ()) |
| { |
| pp_string (pp, " IMPLICITLY CREATED"); |
| } |
| } |
| } |
| |
| /* Return true iff SWITCH_STMT has a non-default label that contains |
| INT_CST. */ |
| |
| static bool |
| has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst) |
| { |
| /* We expect the initial label to be the default; skip it. */ |
| gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL_TREE); |
| unsigned min_idx = 1; |
| unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1; |
| |
| /* Binary search: try to find the label containing INT_CST. |
| This requires the cases to be sorted by CASE_LOW (done by the |
| gimplifier). */ |
| while (max_idx >= min_idx) |
| { |
| unsigned case_idx = (min_idx + max_idx) / 2; |
| tree label = gimple_switch_label (switch_stmt, case_idx); |
| tree low = CASE_LOW (label); |
| gcc_assert (low); |
| tree high = CASE_HIGH (label); |
| if (!high) |
| high = low; |
| if (tree_int_cst_compare (int_cst, low) < 0) |
| { |
| /* INT_CST is below the range of this label. */ |
| gcc_assert (case_idx > 0); |
| max_idx = case_idx - 1; |
| } |
| else if (tree_int_cst_compare (int_cst, high) > 0) |
| { |
| /* INT_CST is above the range of this case. */ |
| min_idx = case_idx + 1; |
| } |
| else |
| /* This case contains INT_CST. */ |
| return true; |
| } |
| /* Not found. */ |
| return false; |
| } |
| |
| /* Return true iff SWITCH_STMT (which must be on an enum value) |
| has nondefault cases handling all values in the enum. */ |
| |
| static bool |
| has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt, |
| tree type) |
| { |
| gcc_assert (switch_stmt); |
| gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE); |
| |
| for (tree enum_val_iter = TYPE_VALUES (type); |
| enum_val_iter; |
| enum_val_iter = TREE_CHAIN (enum_val_iter)) |
| { |
| tree enum_val = TREE_VALUE (enum_val_iter); |
| gcc_assert (TREE_CODE (enum_val) == CONST_DECL); |
| gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST); |
| if (!has_nondefault_case_for_value_p (switch_stmt, |
| DECL_INITIAL (enum_val))) |
| return false; |
| } |
| return true; |
| } |
| |
| /* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints |
| for the edge to be taken. |
| |
| If they are feasible, add the constraints and return true. |
| |
| Return false if the constraints contradict existing knowledge |
| (and so the edge should not be taken). |
| When returning false, if OUT is non-NULL, write a new rejected_constraint |
| to it. */ |
| |
| bool |
| switch_case_op:: |
| apply_constraints (const superedge *, |
| region_model &model, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> *out) const |
| { |
| const gswitch *switch_stmt = &get_gswitch (); |
| tree index = gimple_switch_index (switch_stmt); |
| const svalue *index_sval = model.get_rvalue (index, ctxt); |
| bool check_index_type = true; |
| |
| /* With -fshort-enum, there may be a type cast. */ |
| if (ctxt && index_sval->get_kind () == SK_UNARYOP |
| && TREE_CODE (index_sval->get_type ()) == INTEGER_TYPE) |
| { |
| const unaryop_svalue *unaryop = as_a <const unaryop_svalue *> (index_sval); |
| if (unaryop->get_op () == NOP_EXPR |
| && is_a <const initial_svalue *> (unaryop->get_arg ())) |
| if (const initial_svalue *initvalop = (as_a <const initial_svalue *> |
| (unaryop->get_arg ()))) |
| if (initvalop->get_type () |
| && TREE_CODE (initvalop->get_type ()) == ENUMERAL_TYPE) |
| { |
| index_sval = initvalop; |
| check_index_type = false; |
| } |
| } |
| |
| /* If we're switching based on an enum type, assume that the user is only |
| working with values from the enum. Hence if this is an |
| implicitly-created "default", assume it doesn't get followed. |
| This fixes numerous "uninitialized" false positives where we otherwise |
| consider jumping past the initialization cases. */ |
| |
| if (/* Don't check during feasibility-checking (when ctxt is NULL). */ |
| ctxt |
| /* Must be an enum value. */ |
| && index_sval->get_type () |
| && (!check_index_type |
| || TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE) |
| && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE |
| /* If we have a constant, then we can check it directly. */ |
| && index_sval->get_kind () != SK_CONSTANT |
| && implicitly_created_default_p () |
| && has_nondefault_cases_for_all_enum_values_p (switch_stmt, |
| index_sval->get_type ()) |
| /* Don't do this if there's a chance that the index is |
| attacker-controlled. */ |
| && !ctxt->possibly_tainted_p (index_sval)) |
| { |
| if (out) |
| *out = std::make_unique <rejected_default_case> (model); |
| return false; |
| } |
| |
| bool sat |
| = model.get_constraints ()->add_bounded_ranges (index_sval, |
| m_all_cases_ranges); |
| if (!sat && out) |
| *out = std::make_unique <rejected_ranges_constraint> |
| (model, index, m_all_cases_ranges); |
| if (sat && ctxt && !m_all_cases_ranges->empty_p ()) |
| ctxt->on_bounded_ranges (*index_sval, *m_all_cases_ranges); |
| return sat; |
| } |
| |
| /* Return true iff this op's edge is purely for an |
| implicitly-created "default". */ |
| |
| bool |
| switch_case_op::implicitly_created_default_p () const |
| { |
| if (m_case_labels.size () != 1) |
| return false; |
| |
| tree case_label = m_case_labels[0]; |
| gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR); |
| if (CASE_LOW (case_label)) |
| return false; |
| |
| /* We have a single "default" case. |
| Assume that it was implicitly created if it has UNKNOWN_LOCATION. */ |
| return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION; |
| } |
| |
| /* Given an ERT_TRY region, get the eh_catch corresponding to |
| the label of DST_SNODE, if any. */ |
| |
| static eh_catch |
| get_catch (eh_region eh_reg, supernode *dst_snode) |
| { |
| gcc_assert (eh_reg->type == ERT_TRY); |
| |
| tree dst_snode_label = dst_snode->get_label (); |
| if (!dst_snode_label) |
| return nullptr; |
| |
| for (eh_catch iter = eh_reg->u.eh_try.first_catch; |
| iter; |
| iter = iter->next_catch) |
| if (iter->label == dst_snode_label) |
| return iter; |
| |
| return nullptr; |
| } |
| |
| class rejected_eh_dispatch : public rejected_constraint |
| { |
| public: |
| rejected_eh_dispatch (const region_model &model) |
| : rejected_constraint (model) |
| {} |
| |
| void dump_to_pp (pretty_printer *pp) const final override |
| { |
| pp_printf (pp, "rejected_eh_dispatch"); |
| } |
| }; |
| |
| static bool |
| exception_matches_type_p (tree exception_type, |
| tree catch_type) |
| { |
| if (catch_type == exception_type) |
| return true; |
| |
| /* TODO (PR analyzer/119697): we should also handle subclasses etc; |
| see the rules in https://en.cppreference.com/w/cpp/language/catch |
| |
| It looks like we should be calling (or emulating) |
| can_convert_eh from the C++ FE, but that's specific to the C++ FE. */ |
| |
| return false; |
| } |
| |
| static bool |
| matches_any_exception_type_p (eh_catch ehc, tree exception_type) |
| { |
| if (ehc->type_list == NULL_TREE) |
| /* All exceptions are caught here. */ |
| return true; |
| |
| for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter)) |
| if (exception_matches_type_p (TREE_VALUE (iter), |
| exception_type)) |
| return true; |
| return false; |
| } |
| |
| // class eh_dispatch_edge_op : public control_flow_op |
| |
| std::unique_ptr<eh_dispatch_edge_op> |
| eh_dispatch_edge_op::make (supernode *src_snode, |
| supernode *dst_snode, |
| ::edge cfg_edge, |
| const geh_dispatch &eh_dispatch_stmt) |
| { |
| const eh_status *eh = src_snode->get_function ()->eh; |
| gcc_assert (eh); |
| int region_idx = gimple_eh_dispatch_region (&eh_dispatch_stmt); |
| gcc_assert (region_idx > 0); |
| gcc_assert ((*eh->region_array)[region_idx]); |
| eh_region eh_reg = (*eh->region_array)[region_idx]; |
| gcc_assert (eh_reg); |
| switch (eh_reg->type) |
| { |
| default: |
| gcc_unreachable (); |
| case ERT_CLEANUP: |
| // TODO |
| gcc_unreachable (); |
| break; |
| case ERT_TRY: |
| { |
| eh_catch ehc = get_catch (eh_reg, dst_snode); |
| return std::make_unique<eh_dispatch_try_edge_op> |
| (src_snode, dst_snode, |
| cfg_edge, eh_dispatch_stmt, |
| eh_reg, ehc); |
| } |
| break; |
| case ERT_ALLOWED_EXCEPTIONS: |
| return std::make_unique<eh_dispatch_allowed_edge_op> |
| (src_snode, dst_snode, |
| cfg_edge, eh_dispatch_stmt, |
| eh_reg); |
| break; |
| case ERT_MUST_NOT_THROW: |
| // TODO |
| gcc_unreachable (); |
| break; |
| } |
| } |
| |
| eh_dispatch_edge_op:: |
| eh_dispatch_edge_op (supernode *src_snode, |
| supernode *dst_snode, |
| enum kind kind_, |
| ::edge cfg_edge, |
| const geh_dispatch &geh_dispatch_stmt, |
| eh_region eh_reg) |
| : control_flow_op (kind_, cfg_edge, geh_dispatch_stmt), |
| m_src_snode (src_snode), |
| m_dst_snode (dst_snode), |
| m_eh_region (eh_reg) |
| { |
| } |
| |
| bool |
| eh_dispatch_edge_op:: |
| apply_constraints (const superedge *sedge, |
| region_model &model, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> *out) const |
| { |
| const exception_node *current_node = model.get_current_thrown_exception (); |
| |
| if (!current_node) |
| return false; |
| |
| gcc_assert (current_node); |
| tree curr_exception_type = current_node->maybe_get_type (); |
| if (!curr_exception_type) |
| /* We don't know the specific type. */ |
| return true; |
| |
| return apply_eh_constraints (sedge, model, ctxt, curr_exception_type, out); |
| } |
| |
| // class eh_dispatch_try_edge_op : public eh_dispatch_edge_op |
| |
| eh_dispatch_try_edge_op:: |
| eh_dispatch_try_edge_op (supernode *src_snode, |
| supernode *dst_snode, |
| ::edge cfg_edge, |
| const geh_dispatch &geh_dispatch_stmt, |
| eh_region eh_reg, |
| eh_catch ehc) |
| : eh_dispatch_edge_op (src_snode, dst_snode, |
| kind::eh_dispatch_try_edge, |
| cfg_edge, geh_dispatch_stmt, eh_reg), |
| m_eh_catch (ehc) |
| { |
| gcc_assert (eh_reg->type == ERT_TRY); |
| } |
| |
| void |
| eh_dispatch_try_edge_op::print_as_edge_label (pretty_printer *pp, |
| bool user_facing) const |
| { |
| if (!user_facing) |
| pp_string (pp, "ERT_TRY: "); |
| if (m_eh_catch) |
| { |
| bool first = true; |
| for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter)) |
| { |
| if (!first) |
| pp_string (pp, ", "); |
| pp_printf (pp, "on catch %qT", TREE_VALUE (iter)); |
| first = false; |
| } |
| } |
| else |
| pp_string (pp, "on uncaught exception"); |
| } |
| |
| void |
| eh_dispatch_try_edge_op::add_any_events_for_eedge (const exploded_edge &eedge, |
| checker_path &out_path) const |
| { |
| if (m_eh_catch) |
| { |
| const region_model *model = eedge.m_src->get_state ().m_region_model; |
| auto curr_thrown_exception_node |
| = model->get_current_thrown_exception (); |
| gcc_assert (curr_thrown_exception_node); |
| tree type = curr_thrown_exception_node->maybe_get_type (); |
| out_path.add_event |
| (std::make_unique<catch_cfg_edge_event> |
| (eedge, |
| event_loc_info (eedge.m_dest), |
| *this, |
| type)); |
| } |
| else |
| { |
| /* We have the "uncaught exception" sedge, from eh_dispatch |
| to a block containing resx. |
| Don't add any events for this, so that we can consolidate |
| adjacent stack unwinding events. */ |
| } |
| } |
| |
| bool |
| eh_dispatch_try_edge_op:: |
| apply_eh_constraints (const superedge *sedge, |
| region_model &model, |
| region_model_context */*ctxt*/, |
| tree exception_type, |
| std::unique_ptr<rejected_constraint> *out) const |
| { |
| /* TODO: can we rely on this ordering? |
| or do we need to iterate through prev_catch ? */ |
| /* The exception must not match any of the previous edges. */ |
| for (auto sibling_sedge : get_src_snode ()->m_succs) |
| { |
| if (sibling_sedge == sedge) |
| break; |
| |
| const eh_dispatch_try_edge_op *sibling_edge_op |
| = (const eh_dispatch_try_edge_op *)sibling_sedge->get_op (); |
| if (eh_catch ehc = sibling_edge_op->m_eh_catch) |
| if (matches_any_exception_type_p (ehc, exception_type)) |
| { |
| /* The earlier sibling matches, so the "unhandled" edge is |
| not taken. */ |
| if (out) |
| *out = std::make_unique<rejected_eh_dispatch> (model); |
| return false; |
| } |
| } |
| |
| if (eh_catch ehc = m_eh_catch) |
| { |
| /* We have an edge that tried to match one or more types. */ |
| |
| /* The exception must not match any of the previous edges. */ |
| |
| /* It must match this type. */ |
| if (matches_any_exception_type_p (ehc, exception_type)) |
| return true; |
| else |
| { |
| /* Exception type doesn't match. */ |
| if (out) |
| *out = std::make_unique<rejected_eh_dispatch> (model); |
| return false; |
| } |
| } |
| else |
| { |
| /* This is the "unhandled exception" edge. |
| If we get here then no sibling edges matched; |
| we will follow this edge. */ |
| return true; |
| } |
| } |
| |
| // class eh_dispatch_allowed_edge_op : public eh_dispatch_edge_op |
| |
| eh_dispatch_allowed_edge_op:: |
| eh_dispatch_allowed_edge_op (supernode *src_snode, |
| supernode *dst_snode, |
| ::edge cfg_edge, |
| const geh_dispatch &geh_dispatch_stmt, |
| eh_region eh_reg) |
| : eh_dispatch_edge_op (src_snode, dst_snode, |
| kind::eh_dispatch_try_edge, |
| cfg_edge, geh_dispatch_stmt, eh_reg) |
| { |
| gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS); |
| |
| /* We expect two sibling out-edges at an eh_dispatch from such a region: |
| |
| - one to a bb without a gimple label, with a resx, |
| for exceptions of expected types |
| |
| - one to a bb with a gimple label, with a call to __cxa_unexpected, |
| for exceptions of unexpected types. |
| |
| Set m_kind for this edge accordingly. */ |
| gcc_assert (cfg_edge->src->succs->length () == 2); |
| tree label_for_unexpected_exceptions = eh_reg->u.allowed.label; |
| tree label_for_dest_enode = dst_snode->get_label (); |
| if (label_for_dest_enode == label_for_unexpected_exceptions) |
| m_kind = eh_kind::unexpected; |
| else |
| { |
| gcc_assert (label_for_dest_enode == nullptr); |
| m_kind = eh_kind::expected; |
| } |
| } |
| |
| void |
| eh_dispatch_allowed_edge_op::print_as_edge_label (pretty_printer *pp, |
| bool user_facing) const |
| { |
| if (!user_facing) |
| { |
| switch (m_kind) |
| { |
| default: |
| gcc_unreachable (); |
| case eh_kind::expected: |
| pp_string (pp, "expected: "); |
| break; |
| case eh_kind::unexpected: |
| pp_string (pp, "unexpected: "); |
| break; |
| } |
| pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: "); |
| eh_region eh_reg = get_eh_region (); |
| bool first = true; |
| for (tree iter = eh_reg->u.allowed.type_list; iter; |
| iter = TREE_CHAIN (iter)) |
| { |
| if (!first) |
| pp_string (pp, ", "); |
| pp_printf (pp, "%qT", TREE_VALUE (iter)); |
| first = false; |
| } |
| } |
| } |
| |
| bool |
| eh_dispatch_allowed_edge_op:: |
| apply_eh_constraints (const superedge *, |
| region_model &model, |
| region_model_context */*ctxt*/, |
| tree exception_type, |
| std::unique_ptr<rejected_constraint> *out) const |
| { |
| auto curr_thrown_exception_node = model.get_current_thrown_exception (); |
| gcc_assert (curr_thrown_exception_node); |
| tree curr_exception_type = curr_thrown_exception_node->maybe_get_type (); |
| eh_region eh_reg = get_eh_region (); |
| tree type_list = eh_reg->u.allowed.type_list; |
| |
| switch (get_eh_kind ()) |
| { |
| default: |
| gcc_unreachable (); |
| case eh_kind::expected: |
| if (!curr_exception_type) |
| { |
| /* We don't know the specific type; |
| assume we have one of an expected type. */ |
| return true; |
| } |
| for (tree iter = type_list; iter; iter = TREE_CHAIN (iter)) |
| if (exception_matches_type_p (TREE_VALUE (iter), |
| exception_type)) |
| return true; |
| if (out) |
| *out = std::make_unique<rejected_eh_dispatch> (model); |
| return false; |
| |
| case eh_kind::unexpected: |
| if (!curr_exception_type) |
| { |
| /* We don't know the specific type; |
| assume we don't have one of an expected type. */ |
| if (out) |
| *out = std::make_unique<rejected_eh_dispatch> (model); |
| return false; |
| } |
| for (tree iter = type_list; iter; iter = TREE_CHAIN (iter)) |
| if (exception_matches_type_p (TREE_VALUE (iter), |
| exception_type)) |
| { |
| if (out) |
| *out = std::make_unique<rejected_eh_dispatch> (model); |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| // class phis_for_edge_op : public operation |
| |
| std::unique_ptr<operation> |
| phis_for_edge_op::maybe_make (::edge cfg_in_edge) |
| { |
| std::vector<pair> pairs = get_pairs_for_phi_along_in_edge (cfg_in_edge); |
| if (pairs.empty ()) |
| return nullptr; |
| |
| return std::make_unique <phis_for_edge_op> (std::move (pairs)); |
| } |
| |
| phis_for_edge_op::phis_for_edge_op (std::vector<pair> &&pairs) |
| : operation (kind::phis), |
| m_pairs (std::move (pairs)) |
| { |
| } |
| |
| std::vector<phis_for_edge_op::pair> |
| phis_for_edge_op::get_pairs_for_phi_along_in_edge (::edge cfg_in_edge) |
| { |
| std::vector<pair> result; |
| |
| const size_t phi_arg_idx = cfg_in_edge->dest_idx; |
| for (gphi_iterator gpi = gsi_start_phis (cfg_in_edge->dest); |
| !gsi_end_p (gpi); gsi_next (&gpi)) |
| { |
| gphi * const phi = gpi.phi (); |
| tree dst = gimple_phi_result (phi); |
| |
| /* We don't bother tracking the .MEM SSA names. */ |
| if (tree var = SSA_NAME_VAR (dst)) |
| if (TREE_CODE (var) == VAR_DECL) |
| if (VAR_DECL_IS_VIRTUAL_OPERAND (var)) |
| continue; |
| |
| tree src = gimple_phi_arg_def (phi, phi_arg_idx); |
| |
| result.push_back ({dst, src}); |
| } |
| |
| return result; |
| } |
| |
| void |
| phis_for_edge_op::print_as_edge_label (pretty_printer *pp, |
| bool ) const |
| { |
| pp_printf (pp, "PHI("); |
| bool first = true; |
| for (auto &p : m_pairs) |
| { |
| if (first) |
| first = false; |
| else |
| pp_string (pp, ", "); |
| |
| pp_printf (pp, "%E = %E", p.m_dst, p.m_src); |
| } |
| pp_printf (pp, ");"); |
| } |
| |
| void |
| phis_for_edge_op:: |
| walk_load_store_addr_ops (void */*data*/ , |
| walk_stmt_load_store_addr_fn /*load_cb*/, |
| walk_stmt_load_store_addr_fn /*store_cb*/, |
| walk_stmt_load_store_addr_fn /*addr_cb*/) const |
| { |
| } |
| |
| bool |
| phis_for_edge_op::defines_ssa_name_p (const_tree ssa_name) const |
| { |
| for (auto &p : m_pairs) |
| if (p.m_dst == ssa_name) |
| return true; |
| return false; |
| } |
| |
| void |
| phis_for_edge_op::execute (operation_context &op_ctxt) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| |
| auto dst_point (op_ctxt.get_next_intraprocedural_point ()); |
| |
| const program_state &src_state (op_ctxt.get_initial_state ()); |
| program_state dst_state (src_state); |
| |
| impl_path_context path_ctxt (&dst_state, logger); |
| uncertainty_t uncertainty; |
| impl_region_model_context ctxt (op_ctxt.m_eg, |
| &op_ctxt.m_src_enode, |
| |
| /* TODO: should we be getting the ECs from the |
| old state, rather than the new? */ |
| &op_ctxt.get_initial_state (), |
| &dst_state, |
| &uncertainty, |
| &path_ctxt, |
| nullptr, |
| nullptr); |
| |
| update_state (src_state, dst_state, &ctxt); |
| |
| op_ctxt.add_outcome (dst_point, dst_state, false, &uncertainty); |
| } |
| |
| void |
| phis_for_edge_op::update_state (const program_state &src_state, |
| program_state &dst_state, |
| region_model_context *ctxt) const |
| { |
| const region_model &src_model = *src_state.m_region_model; |
| region_model &dst_model = *dst_state.m_region_model; |
| |
| hash_set<const svalue *> svals_changing_meaning; |
| |
| /* Get state from src_state so that all of the phi stmts for an edge |
| are effectively handled simultaneously. */ |
| for (auto &p : m_pairs) |
| { |
| const svalue *src_sval = src_model.get_rvalue (p.m_src, nullptr); |
| const region *dst_reg = src_model.get_lvalue (p.m_dst, nullptr); |
| |
| const svalue *old_sval = src_model.get_rvalue (p.m_dst, nullptr); |
| if (old_sval->get_kind () == SK_WIDENING) |
| svals_changing_meaning.add (old_sval); |
| |
| dst_model.set_value (dst_reg, src_sval, ctxt); |
| } |
| |
| for (auto iter : svals_changing_meaning) |
| dst_model.get_constraints ()->purge_state_involving (iter); |
| } |
| |
| bool |
| phis_for_edge_op:: |
| execute_for_feasibility (const exploded_edge &eedge, |
| feasibility_state &fstate, |
| region_model_context *ctxt, |
| std::unique_ptr<rejected_constraint> */*out_rc*/) const |
| { |
| hash_set<const svalue *> svals_changing_meaning; |
| /* Get state from src_state so that all of the phi stmts for an edge |
| are effectively handled simultaneously. */ |
| region_model &model = fstate.get_model (); |
| region_model src_model (model); |
| for (auto &p : m_pairs) |
| { |
| const svalue *src_sval = src_model.get_rvalue (p.m_src, ctxt); |
| const region *dst_reg = model.get_lvalue (p.m_dst, ctxt); |
| |
| const svalue *sval = model.get_rvalue (p.m_dst, ctxt); |
| if (sval->get_kind () == SK_WIDENING) |
| svals_changing_meaning.add (sval); |
| |
| model.set_value (dst_reg, src_sval, ctxt); |
| } |
| |
| for (auto iter : svals_changing_meaning) |
| model.get_constraints ()->purge_state_involving (iter); |
| |
| { |
| /* If we've entering an snode that we've already visited on this |
| epath, then we need do fix things up for loops; see the |
| comment for store::loop_replay_fixup. |
| Perhaps we should probably also verify the callstring, |
| and track program_points, but hopefully doing it by supernode |
| is good enough. */ |
| const exploded_node &dst_enode = *eedge.m_dest; |
| const unsigned dst_snode_idx = dst_enode.get_supernode ()->m_id; |
| if (bitmap_bit_p (fstate.get_snodes_visited (), dst_snode_idx)) |
| model.loop_replay_fixup (dst_enode.get_state ().m_region_model); |
| } |
| |
| return true; |
| } |
| |
| void |
| phis_for_edge_op:: |
| update_state_for_bulk_merger (const program_state &src_state, |
| program_state &dst_state) const |
| { |
| update_state (src_state, dst_state, nullptr); |
| } |
| |
| void |
| phis_for_edge_op::add_any_events_for_eedge (const exploded_edge &, |
| checker_path &) const |
| { |
| // No-op |
| } |
| |
| // class resx_op : public gimple_stmt_op |
| |
| void |
| resx_op::execute (operation_context &op_ctxt) const |
| { |
| auto logger = op_ctxt.get_logger (); |
| LOG_SCOPE (logger); |
| |
| program_point dst_point (op_ctxt.get_next_intraprocedural_point ()); |
| program_state dst_state (op_ctxt.get_initial_state ()); |
| op_region_model_context ctxt (op_ctxt, dst_state); |
| |
| if (exploded_node *dst_enode |
| = op_ctxt.m_eg.get_or_create_node (dst_point, dst_state, |
| &op_ctxt.m_src_enode, |
| // Don't add to worklist: |
| false)) |
| { |
| op_ctxt.m_eg.add_edge (&op_ctxt.m_src_enode, |
| dst_enode, |
| &op_ctxt.m_sedge, |
| false, |
| nullptr); |
| /* Try to adding eedges and enodes that unwind to the next |
| eh_dispatch statement, if any. |
| Only the final enode is added to the worklist. */ |
| op_ctxt.m_eg.unwind_from_exception (*dst_enode, |
| nullptr, |
| &ctxt); |
| } |
| } |
| |
| void |
| resx_op::add_any_events_for_eedge (const exploded_edge &, |
| checker_path &) const |
| { |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |