From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 7853) id 6D6D539B3C30; Fri, 20 Aug 2021 04:27:41 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6D6D539B3C30 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: Ankur saini To: gcc-cvs@gcc.gnu.org Subject: [gcc(refs/users/arsenic/heads/analyzer_extension)] analyzer: detect and analyze calls via function pointer X-Act-Checkin: gcc X-Git-Author: Ankur Saini X-Git-Refname: refs/users/arsenic/heads/analyzer_extension X-Git-Oldrev: e660441f94f2ca2ca1bd63c922c6a43737c2259e X-Git-Newrev: aed369d76ee714bc57642943bffea2d8e5610ddb Message-Id: <20210820042741.6D6D539B3C30@sourceware.org> Date: Fri, 20 Aug 2021 04:27:41 +0000 (GMT) X-BeenThere: gcc-cvs@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-cvs mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 20 Aug 2021 04:27:41 -0000 https://gcc.gnu.org/g:aed369d76ee714bc57642943bffea2d8e5610ddb commit aed369d76ee714bc57642943bffea2d8e5610ddb Author: Ankur Saini Date: Thu Jul 29 15:48:07 2021 +0530 analyzer: detect and analyze calls via function pointer 2021-07-29 Ankur Saini gcc/analyzer/ChangeLog: PR analyzer/100546 * analysis-plan.cc (analysis_plan::use_summary_p): Don't use call summaries if there is no callgraph edge * checker-path.cc (call_event::call_event): Handle calls events that are not represented by a supergraph call edge (return_event::return_event): Likewise. (call_event::get_desc): Work with new call_event structure. (return_event::get_desc): Likeise. * checker-path.h (call_event::m_src_snode): New field. (call_event::m_dest_snode): New field. (return_event::m_src_snode): New field. (return_event::m_dest_snode): New field. * diagnostic-manager.cc (diagnostic_manager::prune_for_sm_diagnostic): Refactor to work with edges without callgraph edge. (diagnostic_manager::prune_for_sm_diagnostic): Likewise. * engine.cc (dynamic_call_info_t::update_model): New function. (dynamic_call_info_t::add_events_to_path): New function. (exploded_graph::create_dynamic_call): New function. (exploded_graph::process_node): Work with dynamically discovered calls. * exploded-graph.h (class dynamic_call_info_t): New class. (exploded_graph::create_dynamic_call): New decl. * program-point.cc (program_point::push_to_call_stack): New function. (program_point::pop_from_call_stack): New function. * program-point.h (program_point::push_to_call_stack): New decl. (program_point::pop_from_call_stack): New decl. * program-state.cc (program_state::push_call): New function. (program_state::returning_call): New function. * program-state.h (program_state::push_call): New decl. (program_state::returning_call): New decl. * region-model.cc (region_model::update_for_gcall) New function. (region_model::update_for_return_gcall): New function. (egion_model::update_for_call_superedge): Get the underlying gcall and update for gcall. (region_model::update_for_return_superedge): Likewise. * region-model.h (region_model::update_for_gcall): New decl. (region_model::update_for_return_gcall): New decl. * state-purge.cc (state_purge_per_ssa_name::process_point): Update to work with calls without underlying cgraph edge. * supergraph.cc (supergraph::supergraph) Split snodes at every callsite. * supergraph.h (supernode::get_returning_call) New accessor. gcc/testsuite/ChangeLog: PR analyzer/100546 * gcc.dg/analyzer/function-ptr-4.c: New test. * gcc.dg/analyzer/pr100546.c: New test. Diff: --- gcc/analyzer/analysis-plan.cc | 4 + gcc/analyzer/checker-path.cc | 28 ++-- gcc/analyzer/checker-path.h | 6 + gcc/analyzer/diagnostic-manager.cc | 23 +--- gcc/analyzer/engine.cc | 183 +++++++++++++++++++++++++ gcc/analyzer/exploded-graph.h | 39 ++++++ gcc/analyzer/program-point.cc | 18 +++ gcc/analyzer/program-point.h | 3 +- gcc/analyzer/program-state.cc | 44 ++++++ gcc/analyzer/program-state.h | 11 ++ gcc/analyzer/region-model.cc | 44 ++++-- gcc/analyzer/region-model.h | 6 + gcc/analyzer/state-purge.cc | 35 +++-- gcc/analyzer/supergraph.cc | 43 +++++- gcc/analyzer/supergraph.h | 7 +- gcc/testsuite/gcc.dg/analyzer/function-ptr-4.c | 24 ++++ gcc/testsuite/gcc.dg/analyzer/pr100546.c | 17 +++ 17 files changed, 481 insertions(+), 54 deletions(-) diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc index 7dfc48e9c3e..57a6dcb1f6e 100644 --- a/gcc/analyzer/analysis-plan.cc +++ b/gcc/analyzer/analysis-plan.cc @@ -109,6 +109,10 @@ analysis_plan::use_summary_p (const cgraph_edge *edge) const if (!flag_analyzer_call_summaries) return false; + /* Don't use call summaries if there is no callgraph edge */ + if (!edge || !edge->callee) + return false; + /* TODO: don't count callsites each time. */ int num_call_sites = 0; const cgraph_node *callee = edge->callee; diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index e10c8e2bb7c..e132f003470 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -614,7 +614,11 @@ call_event::call_event (const exploded_edge &eedge, location_t loc, tree fndecl, int depth) : superedge_event (EK_CALL_EDGE, eedge, loc, fndecl, depth) { - gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL); + if (eedge.m_sedge) + gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL); + + m_src_snode = eedge.m_src->get_supernode (); + m_dest_snode = eedge.m_dest->get_supernode (); } /* Implementation of diagnostic_event::get_desc vfunc for @@ -638,8 +642,8 @@ call_event::get_desc (bool can_colorize) const label_text custom_desc = m_pending_diagnostic->describe_call_with_state (evdesc::call_with_state (can_colorize, - m_sedge->m_src->m_fun->decl, - m_sedge->m_dest->m_fun->decl, + m_src_snode->m_fun->decl, + m_dest_snode->m_fun->decl, var, m_critical_state)); if (custom_desc.m_buffer) @@ -648,8 +652,8 @@ call_event::get_desc (bool can_colorize) const return make_label_text (can_colorize, "calling %qE from %qE", - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl); + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl); } /* Override of checker_event::is_call_p for calls. */ @@ -668,7 +672,11 @@ return_event::return_event (const exploded_edge &eedge, location_t loc, tree fndecl, int depth) : superedge_event (EK_RETURN_EDGE, eedge, loc, fndecl, depth) { - gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN); + if (eedge.m_sedge) + gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN); + + m_src_snode = eedge.m_src->get_supernode (); + m_dest_snode = eedge.m_dest->get_supernode (); } /* Implementation of diagnostic_event::get_desc vfunc for @@ -694,16 +702,16 @@ return_event::get_desc (bool can_colorize) const label_text custom_desc = m_pending_diagnostic->describe_return_of_state (evdesc::return_of_state (can_colorize, - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl, + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl, m_critical_state)); if (custom_desc.m_buffer) return custom_desc; } return make_label_text (can_colorize, "returning to %qE from %qE", - m_sedge->m_dest->m_fun->decl, - m_sedge->m_src->m_fun->decl); + m_dest_snode->m_fun->decl, + m_src_snode->m_fun->decl); } /* Override of checker_event::is_return_p for returns. */ diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 1843c4bc7b4..27634c20864 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -338,6 +338,9 @@ public: label_text get_desc (bool can_colorize) const FINAL OVERRIDE; bool is_call_p () const FINAL OVERRIDE; + + const supernode *m_src_snode; + const supernode *m_dest_snode; }; /* A concrete event subclass for an interprocedural return. */ @@ -351,6 +354,9 @@ public: label_text get_desc (bool can_colorize) const FINAL OVERRIDE; bool is_return_p () const FINAL OVERRIDE; + + const supernode *m_src_snode; + const supernode *m_dest_snode; }; /* A concrete event subclass for the start of a consolidated run of CFG diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index ef3df324365..06e751033ac 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2093,18 +2093,13 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, case EK_CALL_EDGE: { call_event *event = (call_event *)base_event; - const callgraph_superedge& cg_superedge - = event->get_callgraph_superedge (); const region_model *callee_model = event->m_eedge.m_dest->get_state ().m_region_model; + const region_model *caller_model + = event->m_eedge.m_src->get_state ().m_region_model; tree callee_var = callee_model->get_representative_tree (sval); - /* We could just use caller_model->get_representative_tree (sval); - to get the caller_var, but for now use - map_expr_from_callee_to_caller so as to only record critical - state for parms and the like. */ callsite_expr expr; - tree caller_var - = cg_superedge.map_expr_from_callee_to_caller (callee_var, &expr); + tree caller_var = caller_model->get_representative_tree (sval); if (caller_var) { if (get_logger ()) @@ -2126,15 +2121,11 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, if (sval) { return_event *event = (return_event *)base_event; - const callgraph_superedge& cg_superedge - = event->get_callgraph_superedge (); - const region_model *caller_model - = event->m_eedge.m_dest->get_state ().m_region_model; - tree caller_var = caller_model->get_representative_tree (sval); callsite_expr expr; - tree callee_var - = cg_superedge.map_expr_from_caller_to_callee (caller_var, - &expr); + + const region_model *callee_model + = event->m_eedge.m_src->get_state ().m_region_model; + tree callee_var = callee_model->get_representative_tree (sval); if (callee_var) { if (get_logger ()) diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index ecd4265733b..461de9cf77c 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1627,6 +1627,50 @@ exploded_node::dump_succs_and_preds (FILE *outf) const } } +/* class dynamic_call_info_t : public exploded_edge::custom_info_t. */ + +/* Implementation of exploded_edge::custom_info_t::update_model vfunc + for dynamic_call_info_t. + + Update state for the dynamically discorverd calls */ + +void +dynamic_call_info_t::update_model (region_model *model, + const exploded_edge &eedge) +{ + const program_state &dest_state = eedge.m_dest->get_state (); + *model = *dest_state.m_region_model; +} + +/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc + for dynamic_call_info_t. */ + +void +dynamic_call_info_t::add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) +{ + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + const int src_stack_depth = src_point.get_stack_depth (); + const exploded_node *dest_node = eedge.m_dest; + const program_point &dest_point = dest_node->get_point (); + const int dest_stack_depth = dest_point.get_stack_depth (); + + if (m_is_returning_call) + emission_path->add_event (new return_event (eedge, (m_dynamic_call + ? m_dynamic_call->location + : UNKNOWN_LOCATION), + dest_point.get_fndecl (), + dest_stack_depth)); + else + emission_path->add_event (new call_event (eedge, (m_dynamic_call + ? m_dynamic_call->location + : UNKNOWN_LOCATION), + src_point.get_fndecl (), + src_stack_depth)); + +} + /* class rewind_info_t : public exploded_edge::custom_info_t. */ /* Implementation of exploded_edge::custom_info_t::update_model vfunc @@ -2980,6 +3024,61 @@ state_change_requires_new_enode_p (const program_state &old_state, return false; } +/* Create enodes and eedges for the function calls that doesn't have an + underlying call superedge. + + Such case occurs when GCC's middle end didn't know which function to + call but the analyzer does (with the help of current state). + + Some example such calls are dynamically dispatched calls to virtual + functions or calls that happen via function pointer. */ + +void +exploded_graph::create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger) +{ + LOG_FUNC (logger); + + const program_point *this_point = &node->get_point (); + function *fun = DECL_STRUCT_FUNCTION (fn_decl); + if (fun) + { + const supergraph &sg = this->get_supergraph (); + supernode * sn_entry = sg.get_node_for_function_entry (fun); + supernode * sn_exit = sg.get_node_for_function_exit (fun); + + program_point new_point + = program_point::before_supernode (sn_entry, + NULL, + this_point->get_call_string ()); + + new_point.push_to_call_stack (sn_exit, + next_point.get_supernode()); + next_state.push_call (*this, node, call, uncertainty); + + if (next_state.m_valid) + { + if (logger) + logger->log ("Discovered call to %s [SN: %i -> SN: %i]", + function_name(fun), + this_point->get_supernode ()->m_index, + sn_entry->m_index); + + exploded_node *enode = get_or_create_node (new_point, + next_state, + node); + if (enode) + add_edge (node,enode, NULL, + new dynamic_call_info_t (call)); + } + } +} + /* The core of exploded_graph::process_worklist (the main analysis loop), handling one node in the worklist. @@ -3174,10 +3273,13 @@ exploded_graph::process_node (exploded_node *node) break; case PK_AFTER_SUPERNODE: { + bool found_a_superedge = false; + bool is_an_exit_block = false; /* If this is an EXIT BB, detect leaks, and potentially create a function summary. */ if (point.get_supernode ()->return_p ()) { + is_an_exit_block = true; node->detect_leaks (*this); if (flag_analyzer_call_summaries && point.get_call_string ().empty_p ()) @@ -3205,6 +3307,7 @@ exploded_graph::process_node (exploded_node *node) superedge *succ; FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ) { + found_a_superedge = true; if (logger) logger->log ("considering SN: %i -> SN: %i", succ->m_src->m_index, succ->m_dest->m_index); @@ -3214,6 +3317,54 @@ exploded_graph::process_node (exploded_node *node) point.get_call_string ()); program_state next_state (state); uncertainty_t uncertainty; + + /* Make use the current state and try to discover and analyse + indirect function calls (a call that doesn't have an underlying + cgraph edge representing call). + + Some examples of such calls are virtual function calls + and calls that happen via a function pointer. */ + if (succ->m_kind == SUPEREDGE_INTRAPROCEDURAL_CALL + && !(succ->get_any_callgraph_edge ())) + { + const gcall *call + = point.get_supernode ()->get_final_call (); + + impl_region_model_context ctxt (*this, + node, + &state, + &next_state, + &uncertainty, + point.get_stmt()); + + region_model *model = state.m_region_model; + + if (tree fn_decl = model->get_fndecl_for_call(call,&ctxt)) + create_dynamic_call (call, + fn_decl, + node, + next_state, + next_point, + &uncertainty, + logger); + else + { + /* An unknown function was called at this point, in such + case, don't terminate the analysis of the current + function. + + The analyzer handles calls to unknown functions while + analysing the stmt itself, so the the function call + must have been handled by the anlyzer till now. */ + exploded_node *next + = get_or_create_node (next_point, + next_state, + node); + if (next) + add_edge (node, next, succ); + } + } + if (!node->on_edge (*this, succ, &next_point, &next_state, &uncertainty)) { @@ -3227,6 +3378,38 @@ exploded_graph::process_node (exploded_node *node) if (next) add_edge (node, next, succ); } + + /* Return from the calls which doesn't have a return superedge. + Such case occurs when GCC's middle end didn't knew which function to + call but analyzer did. */ + if((is_an_exit_block && !found_a_superedge) + && (!point.get_call_string ().empty_p ())) + { + const call_string cs = point.get_call_string (); + program_point next_point + = program_point::before_supernode (cs.get_caller_node (), + NULL, + cs); + program_state next_state (state); + uncertainty_t uncertainty; + + const gcall *call + = next_point.get_supernode ()->get_returning_call (); + + if(call) + next_state.returning_call (*this, node, call, &uncertainty); + + if (next_state.m_valid) + { + next_point.pop_from_call_stack (); + exploded_node *enode = get_or_create_node (next_point, + next_state, + node); + if (enode) + add_edge (node, enode, NULL, + new dynamic_call_info_t (call, true)); + } + } } break; } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 8f48d8a286c..192a4b3c8f8 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -362,6 +362,37 @@ private: DISABLE_COPY_AND_ASSIGN (exploded_edge); }; +/* Extra data for an exploded_edge that represents dynamic call info ( calls + that doesn't have an underlying superedge representing the call ). */ + +class dynamic_call_info_t : public exploded_edge::custom_info_t +{ +public: + dynamic_call_info_t (const gcall *dynamic_call, + const bool is_returning_call = false) + : m_dynamic_call (dynamic_call), + m_is_returning_call (is_returning_call) + {} + + void print (pretty_printer *pp) FINAL OVERRIDE + { + if (m_is_returning_call) + pp_string (pp, "dynamic_return"); + else + pp_string (pp, "dynamic_call"); + } + + void update_model (region_model *model, + const exploded_edge &eedge) FINAL OVERRIDE; + + void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) FINAL OVERRIDE; +private: + const gcall *m_dynamic_call; + const bool m_is_returning_call; +}; + + /* Extra data for an exploded_edge that represents a rewind from a longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */ @@ -785,6 +816,14 @@ public: bool maybe_process_run_of_before_supernode_enodes (exploded_node *node); void process_node (exploded_node *node); + void create_dynamic_call (const gcall *call, + tree fn_decl, + exploded_node *node, + program_state next_state, + program_point &next_point, + uncertainty_t *uncertainty, + logger *logger); + exploded_node *get_or_create_node (const program_point &point, const program_state &state, exploded_node *enode_for_diag); diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index d9f50daeb3c..25d56af3191 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -330,6 +330,24 @@ program_point::to_json () const return point_obj; } +/* Update the callstack to represent a call from caller to callee. + + Generally used to push a custom call to a perticular program point + where we don't have a superedge representing the call. */ +void +program_point::push_to_call_stack (const supernode *caller, + const supernode *callee) +{ + m_call_string.push_call (callee, caller); +} + +/* Pop the topmost call from the current callstack. */ +void +program_point::pop_from_call_stack () +{ + m_call_string.pop (); +} + /* Generate a hash value for this program_point. */ hashval_t diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h index 5f86745cd1e..6bae29b23e8 100644 --- a/gcc/analyzer/program-point.h +++ b/gcc/analyzer/program-point.h @@ -293,7 +293,8 @@ public: } bool on_edge (exploded_graph &eg, const superedge *succ); - + void push_to_call_stack (const supernode *caller, const supernode *callee); + void pop_from_call_stack (); void validate () const; /* For before_stmt, go to next stmt. */ diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 5bb86767873..ea53c61f497 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1034,6 +1034,50 @@ program_state::on_edge (exploded_graph &eg, return true; } +/* Update this program_state to reflect a call to function + represented by CALL_STMT. + currently used only when the call doesn't have a superedge representing + the call ( like call via a function pointer ) */ +void +program_state::push_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty) +{ + /* Update state. */ + const program_point &point = enode->get_point (); + const gimple *last_stmt = point.get_supernode ()->get_last_stmt (); + + impl_region_model_context ctxt (eg, enode, + &enode->get_state (), + this, + uncertainty, + last_stmt); + m_region_model->update_for_gcall (call_stmt, &ctxt); +} + +/* Update this program_state to reflect a return from function + call to which is represented by CALL_STMT. + currently used only when the call doesn't have a superedge representing + the return */ +void +program_state::returning_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty) +{ + /* Update state. */ + const program_point &point = enode->get_point (); + const gimple *last_stmt = point.get_supernode ()->get_last_stmt (); + + impl_region_model_context ctxt (eg, enode, + &enode->get_state (), + this, + uncertainty, + last_stmt); + m_region_model->update_for_return_gcall (call_stmt, &ctxt); +} + /* Generate a simpler version of THIS, discarding state that's no longer relevant at POINT. The idea is that we're more likely to be able to consolidate diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 8dee930665c..eb49006d777 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -218,6 +218,17 @@ public: void push_frame (const extrinsic_state &ext_state, function *fun); function * get_current_function () const; + void push_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty); + + void returning_call (exploded_graph &eg, + exploded_node *enode, + const gcall *call_stmt, + uncertainty_t *uncertainty); + + bool on_edge (exploded_graph &eg, exploded_node *enode, const superedge *succ, diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 58da7e3c0e4..2316fbe5041 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -3172,12 +3172,11 @@ region_model::maybe_update_for_edge (const superedge &edge, caller's frame. */ void -region_model::update_for_call_superedge (const call_superedge &call_edge, - region_model_context *ctxt) +region_model::update_for_gcall (const gcall *call_stmt, + region_model_context *ctxt) { /* Build a vec of argument svalues, using the current top frame for resolving tree expressions. */ - const gcall *call_stmt = call_edge.get_call_stmt (); auto_vec arg_svals (gimple_call_num_args (call_stmt)); for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++) @@ -3186,33 +3185,58 @@ region_model::update_for_call_superedge (const call_superedge &call_edge, arg_svals.quick_push (get_rvalue (arg, ctxt)); } - push_frame (call_edge.get_callee_function (), &arg_svals, ctxt); + /* Get the function * from the call. */ + tree fn_decl = get_fndecl_for_call (call_stmt,ctxt); + function *fun = DECL_STRUCT_FUNCTION (fn_decl); + push_frame (fun, &arg_svals, ctxt); } /* Pop the top-most frame_region from the stack, and copy the return region's values (if any) into the region for the lvalue of the LHS of the call (if any). */ + void -region_model::update_for_return_superedge (const return_superedge &return_edge, - region_model_context *ctxt) +region_model::update_for_return_gcall (const gcall *call_stmt, + region_model_context *ctxt) { /* Get the region for the result of the call, within the caller frame. */ const region *result_dst_reg = NULL; - const gcall *call_stmt = return_edge.get_call_stmt (); tree lhs = gimple_call_lhs (call_stmt); if (lhs) { /* Normally we access the top-level frame, which is: - path_var (expr, get_stack_depth () - 1) - whereas here we need the caller frame, hence "- 2" here. */ + path_var (expr, get_stack_depth () - 1) + whereas here we need the caller frame, hence "- 2" here. */ gcc_assert (get_stack_depth () >= 2); result_dst_reg = get_lvalue (path_var (lhs, get_stack_depth () - 2), - ctxt); + ctxt); } pop_frame (result_dst_reg, NULL, ctxt); } +/* Extract calling information from the superedge and update the model for the + call */ + +void +region_model::update_for_call_superedge (const call_superedge &call_edge, + region_model_context *ctxt) +{ + const gcall *call_stmt = call_edge.get_call_stmt (); + update_for_gcall (call_stmt,ctxt); +} + +/* Extract calling information from the return superedge and update the model + for the returning call */ + +void +region_model::update_for_return_superedge (const return_superedge &return_edge, + region_model_context *ctxt) +{ + const gcall *call_stmt = return_edge.get_call_stmt (); + update_for_return_gcall (call_stmt, ctxt); +} + /* Update this region_model with a summary of the effect of calling and returning from CG_SEDGE. diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 30f02a06b62..e40264e0eb0 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -608,6 +608,12 @@ class region_model region_model_context *ctxt, rejected_constraint **out); + void update_for_gcall (const gcall *call_stmt, + region_model_context *ctxt); + + void update_for_return_gcall (const gcall *call_stmt, + region_model_context *ctxt); + const region *push_frame (function *fun, const vec *arg_sids, region_model_context *ctxt); const frame_region *get_current_frame () const { return m_current_frame; } diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc index bfa48a9ef3f..880057004e2 100644 --- a/gcc/analyzer/state-purge.cc +++ b/gcc/analyzer/state-purge.cc @@ -383,18 +383,31 @@ state_purge_per_ssa_name::process_point (const function_point &point, { /* Add any intraprocedually edge for a call. */ if (snode->m_returning_call) - { - cgraph_edge *cedge + { + gcall *returning_call = snode->m_returning_call; + cgraph_edge *cedge = supergraph_call_edge (snode->m_fun, - snode->m_returning_call); - gcc_assert (cedge); - superedge *sedge - = map.get_sg ().get_intraprocedural_edge_for_call (cedge); - gcc_assert (sedge); - add_to_worklist - (function_point::after_supernode (sedge->m_src), - worklist, logger); - } + returning_call); + if(cedge) + { + superedge *sedge + = map.get_sg ().get_intraprocedural_edge_for_call (cedge); + gcc_assert (sedge); + add_to_worklist + (function_point::after_supernode (sedge->m_src), + worklist, logger); + } + else + { + supernode *callernode + = map.get_sg ().get_supernode_for_stmt (returning_call); + + gcc_assert (callernode); + add_to_worklist + (function_point::after_supernode (callernode), + worklist, logger); + } + } } } break; diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 1eb25436f94..66ef765f472 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -183,11 +183,33 @@ supergraph::supergraph (logger *logger) m_stmt_to_node_t.put (stmt, node_for_stmts); m_stmt_uids.make_uid_unique (stmt); if (cgraph_edge *edge = supergraph_call_edge (fun, stmt)) - { - m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts); - node_for_stmts = add_node (fun, bb, as_a (stmt), NULL); - m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts); - } + { + m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts); + node_for_stmts = add_node (fun, bb, as_a (stmt), + NULL); + m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts); + } + else + { + // maybe call is via a function pointer + if (gcall *call = dyn_cast (stmt)) + { + cgraph_edge *edge + = cgraph_node::get (fun->decl)->get_edge (stmt); + if (!edge || !edge->callee) + { + supernode *old_node_for_stmts = node_for_stmts; + node_for_stmts = add_node (fun, bb, call, NULL); + + superedge *sedge + = new callgraph_superedge (old_node_for_stmts, + node_for_stmts, + SUPEREDGE_INTRAPROCEDURAL_CALL, + NULL); + add_edge (sedge); + } + } + } } m_bb_to_final_node.put (bb, node_for_stmts); @@ -1139,6 +1161,17 @@ callgraph_superedge::get_callee_decl () const return get_callee_function ()->decl; } +/* Get the gcall * of this interprocedural call/return edge. */ + +gcall * +callgraph_superedge::get_call_stmt () const +{ + if (m_cedge) + return m_cedge->call_stmt; + + return m_src->get_final_call (); +} + /* Get the calling fndecl at this interprocedural call/return edge. */ tree diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index 877958f75fa..335f5133407 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -268,6 +268,11 @@ class supernode : public dnode return i; } + gcall *get_returning_call () const + { + return m_returning_call; + } + gimple *get_last_stmt () const { if (m_stmts.length () == 0) @@ -400,7 +405,7 @@ class callgraph_superedge : public superedge function *get_caller_function () const; tree get_callee_decl () const; tree get_caller_decl () const; - gcall *get_call_stmt () const { return m_cedge->call_stmt; } + gcall *get_call_stmt () const; tree get_arg_for_parm (tree parm, callsite_expr *out) const; tree get_parm_for_arg (tree arg, callsite_expr *out) const; tree map_expr_from_caller_to_callee (tree caller_expr, diff --git a/gcc/testsuite/gcc.dg/analyzer/function-ptr-4.c b/gcc/testsuite/gcc.dg/analyzer/function-ptr-4.c new file mode 100644 index 00000000000..016351a83f6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/function-ptr-4.c @@ -0,0 +1,24 @@ +/* Test to see if the analyzer detect and analyze calls via + function pointers or not. */ + +#include + +void fun(int *int_ptr) +{ + free(int_ptr); /* { dg-warning "double-'free' of 'int_ptr'" } */ +} + +void single_call() +{ + int *int_ptr = (int*)malloc(sizeof(int)); + void (*fun_ptr)(int *) = &fun; + (*fun_ptr)(int_ptr); +} + +void double_call() +{ + int *int_ptr = (int*)malloc(sizeof(int)); + void (*fun_ptr)(int *) = &fun; + (*fun_ptr)(int_ptr); /* { dg-message "calling 'fun' from 'double_call'" } */ + (*fun_ptr)(int_ptr); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr100546.c b/gcc/testsuite/gcc.dg/analyzer/pr100546.c new file mode 100644 index 00000000000..3349d4067af --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/pr100546.c @@ -0,0 +1,17 @@ +#include +#include + +static void noReturn(const char *str) __attribute__((noreturn)); +static void noReturn(const char *str) { + printf("%s\n", str); + exit(1); +} + +void (*noReturnPtr)(const char *str) = &noReturn; + +int main(int argc, char **argv) { + char *str = 0; + if (!str) + noReturnPtr(__FILE__); + return printf("%c\n", *str); +}