Skip to content

Commit 7b06a00

Browse files
authored
Merge pull request diffblue#1498 from smowton/smowton/feature/call_graph_improvements
Call graph enhancements
2 parents d41403f + 7a98e15 commit 7b06a00

10 files changed

+639
-71
lines changed

src/analyses/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
SRC = ai.cpp \
22
call_graph.cpp \
3+
call_graph_helpers.cpp \
34
constant_propagator.cpp \
45
custom_bitvector_analysis.cpp \
56
dependence_graph.cpp \

src/analyses/call_graph.cpp

+198-10
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,30 @@ Author: Daniel Kroening, [email protected]
1414
#include <util/std_expr.h>
1515
#include <util/xml.h>
1616

17-
call_grapht::call_grapht()
17+
/// Create empty call graph
18+
/// \param collect_callsites: if true, then each added graph edge will have
19+
/// the calling instruction recorded in `callsites` map.
20+
call_grapht::call_grapht(bool collect_callsites):
21+
collect_callsites(collect_callsites)
1822
{
1923
}
2024

21-
call_grapht::call_grapht(const goto_modelt &goto_model):
22-
call_grapht(goto_model.goto_functions)
25+
/// Create complete call graph
26+
/// \param goto_model: model to search for callsites
27+
/// \param collect_callsites: if true, then each added graph edge will have
28+
/// the calling instruction recorded in `callsites` map.
29+
call_grapht::call_grapht(const goto_modelt &goto_model, bool collect_callsites):
30+
call_grapht(goto_model.goto_functions, collect_callsites)
2331
{
2432
}
2533

26-
call_grapht::call_grapht(const goto_functionst &goto_functions)
34+
/// Create complete call graph
35+
/// \param goto_functions: functions to search for callsites
36+
/// \param collect_callsites: if true, then each added graph edge will have
37+
/// the calling instruction recorded in `callsites` map.
38+
call_grapht::call_grapht(
39+
const goto_functionst &goto_functions, bool collect_callsites):
40+
collect_callsites(collect_callsites)
2741
{
2842
forall_goto_functions(f_it, goto_functions)
2943
{
@@ -32,28 +46,107 @@ call_grapht::call_grapht(const goto_functionst &goto_functions)
3246
}
3347
}
3448

35-
void call_grapht::add(
36-
const irep_idt &function,
37-
const goto_programt &body)
49+
static void forall_callsites(
50+
const goto_programt &body,
51+
std::function<void(goto_programt::const_targett, const irep_idt &)> call_task)
3852
{
3953
forall_goto_program_instructions(i_it, body)
4054
{
4155
if(i_it->is_function_call())
4256
{
4357
const exprt &function_expr=to_code_function_call(i_it->code).function();
4458
if(function_expr.id()==ID_symbol)
45-
add(function, to_symbol_expr(function_expr).get_identifier());
59+
{
60+
const irep_idt &callee=to_symbol_expr(function_expr).get_identifier();
61+
call_task(i_it, callee);
62+
}
4663
}
4764
}
4865
}
4966

67+
/// Create call graph restricted to functions reachable from `root`
68+
/// \param goto_functions: functions to search for callsites
69+
/// \param root: function to start exploring the graph
70+
/// \param collect_callsites: if true, then each added graph edge will have
71+
/// the calling instruction recorded in `callsites` map.
72+
call_grapht::call_grapht(
73+
const goto_functionst &goto_functions,
74+
const irep_idt &root,
75+
bool collect_callsites)
76+
{
77+
std::stack<irep_idt, std::vector<irep_idt>> pending_stack;
78+
pending_stack.push(root);
79+
80+
while(!pending_stack.empty())
81+
{
82+
irep_idt function=pending_stack.top();
83+
pending_stack.pop();
84+
const goto_programt &goto_program=
85+
goto_functions.function_map.at(function).body;
86+
87+
forall_callsites(
88+
goto_program,
89+
[&](goto_programt::const_targett i_it, const irep_idt &callee)
90+
{
91+
add(function, callee, i_it);
92+
if(graph.find(callee)==graph.end())
93+
pending_stack.push(callee);
94+
}
95+
); // NOLINT
96+
}
97+
}
98+
99+
/// Create call graph restricted to functions reachable from `root`
100+
/// \param goto_model: model to search for callsites
101+
/// \param root: function to start exploring the graph
102+
/// \param collect_callsites: if true, then each added graph edge will have
103+
/// the calling instruction recorded in `callsites` map.
104+
call_grapht::call_grapht(
105+
const goto_modelt &goto_model,
106+
const irep_idt &root,
107+
bool collect_callsites):
108+
call_grapht(goto_model.goto_functions, root, collect_callsites)
109+
{
110+
}
111+
112+
void call_grapht::add(
113+
const irep_idt &function,
114+
const goto_programt &body)
115+
{
116+
forall_callsites(
117+
body,
118+
[&](goto_programt::const_targett i_it, const irep_idt &callee)
119+
{
120+
add(function, callee, i_it);
121+
}
122+
); // NOLINT
123+
}
124+
125+
/// Add edge
126+
/// \param caller: caller function
127+
/// \param callee: callee function
50128
void call_grapht::add(
51129
const irep_idt &caller,
52130
const irep_idt &callee)
53131
{
54132
graph.insert(std::pair<irep_idt, irep_idt>(caller, callee));
55133
}
56134

135+
/// Add edge with optional callsite information
136+
/// \param caller: caller function
137+
/// \param callee: callee function
138+
/// \param callsite: call instruction responsible for this edge. Note this is
139+
/// only stored if `collect_callsites` was specified during construction.
140+
void call_grapht::add(
141+
const irep_idt &caller,
142+
const irep_idt &callee,
143+
locationt callsite)
144+
{
145+
add(caller, callee);
146+
if(collect_callsites)
147+
callsites[{caller, callee}].insert(callsite);
148+
}
149+
57150
/// Returns an inverted copy of this call graph
58151
/// \return Inverted (callee -> caller) call graph
59152
call_grapht call_grapht::get_inverted() const
@@ -64,6 +157,82 @@ call_grapht call_grapht::get_inverted() const
64157
return result;
65158
}
66159

160+
/// Helper class that maintains a map from function name to grapht node index
161+
/// and adds nodes to the graph on demand.
162+
class function_indicest
163+
{
164+
typedef call_grapht::directed_grapht::node_indext node_indext;
165+
call_grapht::directed_grapht &graph;
166+
167+
public:
168+
std::unordered_map<irep_idt, node_indext, irep_id_hash> function_indices;
169+
170+
explicit function_indicest(call_grapht::directed_grapht &graph):
171+
graph(graph)
172+
{
173+
}
174+
175+
node_indext operator[](const irep_idt &function)
176+
{
177+
auto findit=function_indices.insert({function, 0});
178+
if(findit.second)
179+
{
180+
node_indext new_index=graph.add_node();
181+
findit.first->second=new_index;
182+
graph[new_index].function=function;
183+
}
184+
return findit.first->second;
185+
}
186+
};
187+
188+
/// Returns a `grapht` representation of this call graph, suitable for use
189+
/// with generic grapht algorithms. Note that parallel edges in call_grapht
190+
/// (e.g. A { B(); B(); } appearing as two A->B edges) will be condensed in
191+
/// the grapht output, so only one edge will appear. If `collect_callsites`
192+
/// was set when this call-graph was constructed the edge will be annotated
193+
/// with the call-site set.
194+
/// \return grapht representation of this call_grapht
195+
call_grapht::directed_grapht call_grapht::get_directed_graph() const
196+
{
197+
call_grapht::directed_grapht ret;
198+
function_indicest function_indices(ret);
199+
200+
for(const auto &edge : graph)
201+
{
202+
auto a_index=function_indices[edge.first];
203+
auto b_index=function_indices[edge.second];
204+
// Check then create the edge like this to avoid copying the callsites
205+
// set once per parallel edge, which could be costly if there are many.
206+
if(!ret.has_edge(a_index, b_index))
207+
{
208+
ret.add_edge(a_index, b_index);
209+
if(collect_callsites)
210+
ret[a_index].out[b_index].callsites=callsites.at(edge);
211+
}
212+
}
213+
214+
ret.nodes_by_name=std::move(function_indices.function_indices);
215+
return ret;
216+
}
217+
218+
/// Prints callsites responsible for a graph edge as comma-separated
219+
/// location numbers, e.g. "{1, 2, 3}".
220+
/// \param edge: graph edge
221+
/// \return pretty representation of edge callsites
222+
std::string call_grapht::format_callsites(const edget &edge) const
223+
{
224+
PRECONDITION(collect_callsites);
225+
std::string ret="{";
226+
for(const locationt &loc : callsites.at(edge))
227+
{
228+
if(ret.size()>1)
229+
ret+=", ";
230+
ret+=std::to_string(loc->location_number);
231+
}
232+
ret+='}';
233+
return ret;
234+
}
235+
67236
void call_grapht::output_dot(std::ostream &out) const
68237
{
69238
out << "digraph call_graph {\n";
@@ -72,8 +241,10 @@ void call_grapht::output_dot(std::ostream &out) const
72241
{
73242
out << " \"" << edge.first << "\" -> "
74243
<< "\"" << edge.second << "\" "
75-
<< " [arrowhead=\"vee\"];"
76-
<< "\n";
244+
<< " [arrowhead=\"vee\"";
245+
if(collect_callsites)
246+
out << " label=\"" << format_callsites(edge) << "\"";
247+
out << "];\n";
77248
}
78249

79250
out << "}\n";
@@ -84,11 +255,18 @@ void call_grapht::output(std::ostream &out) const
84255
for(const auto &edge : graph)
85256
{
86257
out << edge.first << " -> " << edge.second << "\n";
258+
if(collect_callsites)
259+
out << " (callsites: " << format_callsites(edge) << ")\n";
87260
}
88261
}
89262

90263
void call_grapht::output_xml(std::ostream &out) const
91264
{
265+
// Note I don't implement callsite output here; I'll leave that
266+
// to the first interested XML user.
267+
if(collect_callsites)
268+
out << "<!-- XML call-graph representation does not document callsites yet."
269+
" If you need this, edit call_grapht::output_xml -->\n";
92270
for(const auto &edge : graph)
93271
{
94272
out << "<call_graph_edge caller=\"";
@@ -98,3 +276,13 @@ void call_grapht::output_xml(std::ostream &out) const
98276
out << "\">\n";
99277
}
100278
}
279+
280+
optionalt<std::size_t> call_grapht::directed_grapht::get_node_index(
281+
const irep_idt &function) const
282+
{
283+
auto findit=nodes_by_name.find(function);
284+
if(findit==nodes_by_name.end())
285+
return optionalt<node_indext>();
286+
else
287+
return findit->second;
288+
}

0 commit comments

Comments
 (0)