-
Notifications
You must be signed in to change notification settings - Fork 277
Call graph enhancements #1498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
smowton
merged 10 commits into
diffblue:develop
from
smowton:smowton/feature/call_graph_improvements
Dec 16, 2017
Merged
Call graph enhancements #1498
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
8115248
Call graph: optionally record callsites
smowton aaa8513
Call graph -> grapht transformation
smowton 9b65862
grapht: add get_reachable function
smowton 3b06a16
Call graph: add constructors that only include reachable functions
smowton 8f6f429
Add call graph helpers
smowton 8ed3ccb
Add documentation to call graph
smowton 9c29bee
Improve call graph unit tests
smowton d136bbc
Expose limited call graph via goto-instrument
smowton 6228ed3
Slice global inits: use improved call graph
smowton 7a98e15
Rename call graph constructors
smowton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,16 +14,30 @@ Author: Daniel Kroening, [email protected] | |
#include <util/std_expr.h> | ||
#include <util/xml.h> | ||
|
||
call_grapht::call_grapht() | ||
/// Create empty call graph | ||
/// \param collect_callsites: if true, then each added graph edge will have | ||
/// the calling instruction recorded in `callsites` map. | ||
call_grapht::call_grapht(bool collect_callsites): | ||
collect_callsites(collect_callsites) | ||
{ | ||
} | ||
|
||
call_grapht::call_grapht(const goto_modelt &goto_model): | ||
call_grapht(goto_model.goto_functions) | ||
/// Create complete call graph | ||
/// \param goto_model: model to search for callsites | ||
/// \param collect_callsites: if true, then each added graph edge will have | ||
/// the calling instruction recorded in `callsites` map. | ||
call_grapht::call_grapht(const goto_modelt &goto_model, bool collect_callsites): | ||
call_grapht(goto_model.goto_functions, collect_callsites) | ||
{ | ||
} | ||
|
||
call_grapht::call_grapht(const goto_functionst &goto_functions) | ||
/// Create complete call graph | ||
/// \param goto_functions: functions to search for callsites | ||
/// \param collect_callsites: if true, then each added graph edge will have | ||
/// the calling instruction recorded in `callsites` map. | ||
call_grapht::call_grapht( | ||
const goto_functionst &goto_functions, bool collect_callsites): | ||
collect_callsites(collect_callsites) | ||
{ | ||
forall_goto_functions(f_it, goto_functions) | ||
{ | ||
|
@@ -32,28 +46,107 @@ call_grapht::call_grapht(const goto_functionst &goto_functions) | |
} | ||
} | ||
|
||
void call_grapht::add( | ||
const irep_idt &function, | ||
const goto_programt &body) | ||
static void forall_callsites( | ||
const goto_programt &body, | ||
std::function<void(goto_programt::const_targett, const irep_idt &)> call_task) | ||
{ | ||
forall_goto_program_instructions(i_it, body) | ||
{ | ||
if(i_it->is_function_call()) | ||
{ | ||
const exprt &function_expr=to_code_function_call(i_it->code).function(); | ||
if(function_expr.id()==ID_symbol) | ||
add(function, to_symbol_expr(function_expr).get_identifier()); | ||
{ | ||
const irep_idt &callee=to_symbol_expr(function_expr).get_identifier(); | ||
call_task(i_it, callee); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Create call graph restricted to functions reachable from `root` | ||
/// \param goto_functions: functions to search for callsites | ||
/// \param root: function to start exploring the graph | ||
/// \param collect_callsites: if true, then each added graph edge will have | ||
/// the calling instruction recorded in `callsites` map. | ||
call_grapht::call_grapht( | ||
const goto_functionst &goto_functions, | ||
const irep_idt &root, | ||
bool collect_callsites) | ||
{ | ||
std::stack<irep_idt, std::vector<irep_idt>> pending_stack; | ||
pending_stack.push(root); | ||
|
||
while(!pending_stack.empty()) | ||
{ | ||
irep_idt function=pending_stack.top(); | ||
pending_stack.pop(); | ||
const goto_programt &goto_program= | ||
goto_functions.function_map.at(function).body; | ||
|
||
forall_callsites( | ||
goto_program, | ||
[&](goto_programt::const_targett i_it, const irep_idt &callee) | ||
{ | ||
add(function, callee, i_it); | ||
if(graph.find(callee)==graph.end()) | ||
pending_stack.push(callee); | ||
} | ||
); // NOLINT | ||
} | ||
} | ||
|
||
/// Create call graph restricted to functions reachable from `root` | ||
/// \param goto_model: model to search for callsites | ||
/// \param root: function to start exploring the graph | ||
/// \param collect_callsites: if true, then each added graph edge will have | ||
/// the calling instruction recorded in `callsites` map. | ||
call_grapht::call_grapht( | ||
const goto_modelt &goto_model, | ||
const irep_idt &root, | ||
bool collect_callsites): | ||
call_grapht(goto_model.goto_functions, root, collect_callsites) | ||
{ | ||
} | ||
|
||
void call_grapht::add( | ||
const irep_idt &function, | ||
const goto_programt &body) | ||
{ | ||
forall_callsites( | ||
body, | ||
[&](goto_programt::const_targett i_it, const irep_idt &callee) | ||
{ | ||
add(function, callee, i_it); | ||
} | ||
); // NOLINT | ||
} | ||
|
||
/// Add edge | ||
/// \param caller: caller function | ||
/// \param callee: callee function | ||
void call_grapht::add( | ||
const irep_idt &caller, | ||
const irep_idt &callee) | ||
{ | ||
graph.insert(std::pair<irep_idt, irep_idt>(caller, callee)); | ||
} | ||
|
||
/// Add edge with optional callsite information | ||
/// \param caller: caller function | ||
/// \param callee: callee function | ||
/// \param callsite: call instruction responsible for this edge. Note this is | ||
/// only stored if `collect_callsites` was specified during construction. | ||
void call_grapht::add( | ||
const irep_idt &caller, | ||
const irep_idt &callee, | ||
locationt callsite) | ||
{ | ||
add(caller, callee); | ||
if(collect_callsites) | ||
callsites[{caller, callee}].insert(callsite); | ||
} | ||
|
||
/// Returns an inverted copy of this call graph | ||
/// \return Inverted (callee -> caller) call graph | ||
call_grapht call_grapht::get_inverted() const | ||
|
@@ -64,6 +157,82 @@ call_grapht call_grapht::get_inverted() const | |
return result; | ||
} | ||
|
||
/// Helper class that maintains a map from function name to grapht node index | ||
/// and adds nodes to the graph on demand. | ||
class function_indicest | ||
{ | ||
typedef call_grapht::directed_grapht::node_indext node_indext; | ||
call_grapht::directed_grapht &graph; | ||
|
||
public: | ||
std::unordered_map<irep_idt, node_indext, irep_id_hash> function_indices; | ||
|
||
explicit function_indicest(call_grapht::directed_grapht &graph): | ||
graph(graph) | ||
{ | ||
} | ||
|
||
node_indext operator[](const irep_idt &function) | ||
{ | ||
auto findit=function_indices.insert({function, 0}); | ||
if(findit.second) | ||
{ | ||
node_indext new_index=graph.add_node(); | ||
findit.first->second=new_index; | ||
graph[new_index].function=function; | ||
} | ||
return findit.first->second; | ||
} | ||
}; | ||
|
||
/// Returns a `grapht` representation of this call graph, suitable for use | ||
/// with generic grapht algorithms. Note that parallel edges in call_grapht | ||
/// (e.g. A { B(); B(); } appearing as two A->B edges) will be condensed in | ||
/// the grapht output, so only one edge will appear. If `collect_callsites` | ||
/// was set when this call-graph was constructed the edge will be annotated | ||
/// with the call-site set. | ||
/// \return grapht representation of this call_grapht | ||
call_grapht::directed_grapht call_grapht::get_directed_graph() const | ||
{ | ||
call_grapht::directed_grapht ret; | ||
function_indicest function_indices(ret); | ||
|
||
for(const auto &edge : graph) | ||
{ | ||
auto a_index=function_indices[edge.first]; | ||
auto b_index=function_indices[edge.second]; | ||
// Check then create the edge like this to avoid copying the callsites | ||
// set once per parallel edge, which could be costly if there are many. | ||
if(!ret.has_edge(a_index, b_index)) | ||
{ | ||
ret.add_edge(a_index, b_index); | ||
if(collect_callsites) | ||
ret[a_index].out[b_index].callsites=callsites.at(edge); | ||
} | ||
} | ||
|
||
ret.nodes_by_name=std::move(function_indices.function_indices); | ||
return ret; | ||
} | ||
|
||
/// Prints callsites responsible for a graph edge as comma-separated | ||
/// location numbers, e.g. "{1, 2, 3}". | ||
/// \param edge: graph edge | ||
/// \return pretty representation of edge callsites | ||
std::string call_grapht::format_callsites(const edget &edge) const | ||
{ | ||
PRECONDITION(collect_callsites); | ||
std::string ret="{"; | ||
for(const locationt &loc : callsites.at(edge)) | ||
{ | ||
if(ret.size()>1) | ||
ret+=", "; | ||
ret+=std::to_string(loc->location_number); | ||
} | ||
ret+='}'; | ||
return ret; | ||
} | ||
|
||
void call_grapht::output_dot(std::ostream &out) const | ||
{ | ||
out << "digraph call_graph {\n"; | ||
|
@@ -72,8 +241,10 @@ void call_grapht::output_dot(std::ostream &out) const | |
{ | ||
out << " \"" << edge.first << "\" -> " | ||
<< "\"" << edge.second << "\" " | ||
<< " [arrowhead=\"vee\"];" | ||
<< "\n"; | ||
<< " [arrowhead=\"vee\""; | ||
if(collect_callsites) | ||
out << " label=\"" << format_callsites(edge) << "\""; | ||
out << "];\n"; | ||
} | ||
|
||
out << "}\n"; | ||
|
@@ -84,11 +255,18 @@ void call_grapht::output(std::ostream &out) const | |
for(const auto &edge : graph) | ||
{ | ||
out << edge.first << " -> " << edge.second << "\n"; | ||
if(collect_callsites) | ||
out << " (callsites: " << format_callsites(edge) << ")\n"; | ||
} | ||
} | ||
|
||
void call_grapht::output_xml(std::ostream &out) const | ||
{ | ||
// Note I don't implement callsite output here; I'll leave that | ||
// to the first interested XML user. | ||
if(collect_callsites) | ||
out << "<!-- XML call-graph representation does not document callsites yet." | ||
" If you need this, edit call_grapht::output_xml -->\n"; | ||
for(const auto &edge : graph) | ||
{ | ||
out << "<call_graph_edge caller=\""; | ||
|
@@ -98,3 +276,13 @@ void call_grapht::output_xml(std::ostream &out) const | |
out << "\">\n"; | ||
} | ||
} | ||
|
||
optionalt<std::size_t> call_grapht::directed_grapht::get_node_index( | ||
const irep_idt &function) const | ||
{ | ||
auto findit=nodes_by_name.find(function); | ||
if(findit==nodes_by_name.end()) | ||
return optionalt<node_indext>(); | ||
else | ||
return findit->second; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps rather than a comment a
warning()
if(collect_callsites)