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;
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
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)
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. */
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
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. */
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. */
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
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 ())
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 ())
}
}
+/* 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
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.
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 ())
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);
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))
{
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;
}
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). */
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);
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
}
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. */
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
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,
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<const svalue *> arg_svals (gimple_call_num_args (call_stmt));
for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++)
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.
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<const svalue *> *arg_sids,
region_model_context *ctxt);
const frame_region *get_current_frame () const { return m_current_frame; }
{
/* 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;
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 <gcall *> (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 <gcall *> (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<gcall *> (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);
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
return i;
}
+ gcall *get_returning_call () const
+ {
+ return m_returning_call;
+ }
+
gimple *get_last_stmt () const
{
if (m_stmts.length () == 0)
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,
--- /dev/null
+/* Test to see if the analyzer detect and analyze calls via
+ function pointers or not. */
+
+#include <stdlib.h>
+
+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);
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+
+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);
+}