diff --git a/src/analyses/Makefile b/src/analyses/Makefile index a63a4d4e1c6..61d3bb18e16 100644 --- a/src/analyses/Makefile +++ b/src/analyses/Makefile @@ -1,5 +1,6 @@ SRC = ai.cpp \ call_graph.cpp \ + call_graph_helpers.cpp \ constant_propagator.cpp \ custom_bitvector_analysis.cpp \ dependence_graph.cpp \ diff --git a/src/analyses/call_graph.cpp b/src/analyses/call_graph.cpp index 93850f64d1d..e3abe2fdcc2 100644 --- a/src/analyses/call_graph.cpp +++ b/src/analyses/call_graph.cpp @@ -14,16 +14,30 @@ Author: Daniel Kroening, kroening@kroening.com #include #include -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,9 +46,9 @@ 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 call_task) { forall_goto_program_instructions(i_it, body) { @@ -42,11 +56,75 @@ void call_grapht::add( { 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> 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) @@ -54,6 +132,21 @@ void call_grapht::add( graph.insert(std::pair(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 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 << "\n"; for(const auto &edge : graph) { out << "\n"; } } + +optionalt 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(); + else + return findit->second; +} diff --git a/src/analyses/call_graph.h b/src/analyses/call_graph.h index 684f74c3a3a..262a644ced8 100644 --- a/src/analyses/call_graph.h +++ b/src/analyses/call_graph.h @@ -16,27 +16,133 @@ Author: Daniel Kroening, kroening@kroening.com #include #include +#include class call_grapht { public: - call_grapht(); - explicit call_grapht(const goto_modelt &); - explicit call_grapht(const goto_functionst &); + explicit call_grapht(bool collect_callsites=false); + explicit call_grapht(const goto_modelt &, bool collect_callsites=false); + explicit call_grapht(const goto_functionst &, bool collect_callsites=false); + + // These two functions build a call graph restricted to functions + // reachable from the given root. + + static call_grapht create_from_root_function( + const goto_modelt &model, + const irep_idt &root, + bool collect_callsites) + { + return call_grapht(model, root, collect_callsites); + } + + static call_grapht create_from_root_function( + const goto_functionst &functions, + const irep_idt &root, + bool collect_callsites) + { + return call_grapht(functions, root, collect_callsites); + } + + // Constructors used to implement the above: + +private: + call_grapht( + const goto_modelt &model, + const irep_idt &root, + bool collect_callsites); + call_grapht( + const goto_functionst &functions, + const irep_idt &root, + bool collect_callsites); + +public: void output_dot(std::ostream &out) const; void output(std::ostream &out) const; void output_xml(std::ostream &out) const; + /// Type of the call graph. Note parallel edges (e.g. A having two callsites + /// both targeting B) result in multiple graph edges. typedef std::multimap grapht; + + /// Type of a call graph edge in `grapht` + typedef std::pair edget; + + /// Type of a callsite stored in member `callsites` + typedef goto_programt::const_targett locationt; + + /// Type of a set of callsites + typedef std::set locationst; + + /// Type mapping from call-graph edges onto the set of call instructions + /// that make that call. + typedef std::map callsitest; + + /// Call graph, including duplicate key-value pairs when there are parallel + /// edges (see `grapht` documentation). This representation is retained for + /// backward compatibility; use `get_directed_graph()` to get a generic + /// directed graph representation that provides more graph algorithms + /// (shortest path, SCCs and so on). grapht graph; + /// Map from call-graph edges to a set of callsites that make the given call. + callsitest callsites; + void add(const irep_idt &caller, const irep_idt &callee); + void add(const irep_idt &caller, const irep_idt &callee, locationt callsite); + call_grapht get_inverted() const; + /// Edge of the directed graph representation of this call graph + struct edge_with_callsitest + { + /// Callsites responsible for this graph edge. Will be empty if + /// `collect_callsites` was not set at `call_grapht` construction time. + locationst callsites; + }; + + /// Node of the directed graph representation of this call graph + struct function_nodet : public graph_nodet + { + /// Function name + irep_idt function; + }; + + /// Directed graph representation of this call graph + class directed_grapht : public ::grapht + { + friend class call_grapht; + + /// Maps function names onto node indices + std::unordered_map nodes_by_name; + + public: + /// Find the graph node by function name + /// \param function: function to find + /// \return none if function is not in this graph, or some index otherwise. + optionalt get_node_index(const irep_idt &function) const; + + /// Type of the node name -> node index map. + typedef + std::unordered_map nodes_by_namet; + + /// Get the node name -> node index map + /// \return node-by-name map + const nodes_by_namet &get_nodes_by_name() const + { + return nodes_by_name; + } + }; + + directed_grapht get_directed_graph() const; + protected: void add(const irep_idt &function, const goto_programt &body); +private: + bool collect_callsites; + std::string format_callsites(const edget &edge) const; }; #endif // CPROVER_ANALYSES_CALL_GRAPH_H diff --git a/src/analyses/call_graph_helpers.cpp b/src/analyses/call_graph_helpers.cpp new file mode 100644 index 00000000000..116ef12680e --- /dev/null +++ b/src/analyses/call_graph_helpers.cpp @@ -0,0 +1,72 @@ +/*******************************************************************\ + +Module: Function Call Graph Helpers + +Author: Chris Smowton, chris.smowton@diffblue.com + +\*******************************************************************/ + +/// \file +/// Function Call Graph Helpers + +#include "call_graph_helpers.h" + +/// Get either callers or callees of a given function +/// \param graph: call graph +/// \param function: function to query +/// \param forwards: if true, get callees; otherwise get callers. +static std::set get_neighbours( + const call_grapht::directed_grapht &graph, + const irep_idt &function, + bool forwards) +{ + std::set result; + const auto &fnode = graph[*(graph.get_node_index(function))]; + const auto &neighbours = forwards ? fnode.out : fnode.in; + for(const auto &succ_edge : neighbours) + result.insert(graph[succ_edge.first].function); + return result; +} + +std::set get_callees( + const call_grapht::directed_grapht &graph, const irep_idt &function) +{ + return get_neighbours(graph, function, true); +} + +std::set get_callers( + const call_grapht::directed_grapht &graph, const irep_idt &function) +{ + return get_neighbours(graph, function, false); +} + +/// Get either reachable functions or functions that can reach a given function. +/// In both cases the query function itself is included. +/// \param graph: call graph +/// \param function: function to query +/// \param forwards: if true, get reachable functions; otherwise get functions +/// that can reach the given function. +static std::set get_connected_functions( + const call_grapht::directed_grapht &graph, + const irep_idt &function, + bool forwards) +{ + std::vector connected_nodes = + graph.get_reachable(*(graph.get_node_index(function)), forwards); + std::set result; + for(const auto i : connected_nodes) + result.insert(graph[i].function); + return result; +} + +std::set get_reachable_functions( + const call_grapht::directed_grapht &graph, const irep_idt &function) +{ + return get_connected_functions(graph, function, true); +} + +std::set get_reaching_functions( + const call_grapht::directed_grapht &graph, const irep_idt &function) +{ + return get_connected_functions(graph, function, false); +} diff --git a/src/analyses/call_graph_helpers.h b/src/analyses/call_graph_helpers.h new file mode 100644 index 00000000000..170785a9f89 --- /dev/null +++ b/src/analyses/call_graph_helpers.h @@ -0,0 +1,52 @@ +/*******************************************************************\ + +Module: Function Call Graph Helpers + +Author: Chris Smowton, chris.smowton@diffblue.com + +\*******************************************************************/ + +/// \file +/// Function Call Graph Helpers + +#ifndef CPROVER_ANALYSES_CALL_GRAPH_HELPERS_H +#define CPROVER_ANALYSES_CALL_GRAPH_HELPERS_H + +#include "call_graph.h" + +// These are convenience functions for working with the directed graph +// representation of a call graph, obtained via +// `call_grapht::get_directed_graph`. Usually function names must be mapped +// to and from node indices, as in `graph.get_node_index("f")`, or +// `graph[node_index].function`; these helpers include the translation for +// convenience. + +/// Get functions directly callable from a given function +/// \param graph: call graph +/// \param function: function to query +/// \return set of called functions +std::set get_callees( + const call_grapht::directed_grapht &graph, const irep_idt &function); + +/// Get functions that call a given function +/// \param graph: call graph +/// \param function: function to query +/// \return set of caller functions +std::set get_callers( + const call_grapht::directed_grapht &graph, const irep_idt &function); + +/// Get functions reachable from a given function +/// \param graph: call graph +/// \param function: function to query +/// \return set of reachable functions, including `function` +std::set get_reachable_functions( + const call_grapht::directed_grapht &graph, const irep_idt &function); + +/// Get functions that can reach a given function +/// \param graph: call graph +/// \param function: function to query +/// \return set of functions that can reach the target, including `function` +std::set get_reaching_functions( + const call_grapht::directed_grapht &graph, const irep_idt &function); + +#endif diff --git a/src/goto-instrument/goto_instrument_parse_options.cpp b/src/goto-instrument/goto_instrument_parse_options.cpp index a0562e33d73..fd53a05a765 100644 --- a/src/goto-instrument/goto_instrument_parse_options.cpp +++ b/src/goto-instrument/goto_instrument_parse_options.cpp @@ -659,6 +659,22 @@ int goto_instrument_parse_optionst::doit() return CPROVER_EXIT_SUCCESS; } + if(cmdline.isset("reachable-call-graph")) + { + do_indirect_call_and_rtti_removal(); + call_grapht call_graph = + call_grapht::create_from_root_function( + goto_model, goto_functionst::entry_point(), false); + if(cmdline.isset("xml")) + call_graph.output_xml(std::cout); + else if(cmdline.isset("dot")) + call_graph.output_dot(std::cout); + else + call_graph.output(std::cout); + + return 0; + } + if(cmdline.isset("dot")) { namespacet ns(goto_model.symbol_table); @@ -1455,6 +1471,9 @@ void goto_instrument_parse_optionst::help() " --list-calls-args list all function calls with their arguments\n" // NOLINTNEXTLINE(whitespace/line_length) " --print-path-lengths print statistics about control-flow graph paths\n" + " --call-graph show graph of function calls\n" + // NOLINTNEXTLINE(whitespace/line_length) + " --reachable-call-graph show graph of function calls potentially reachable from main function\n" "\n" "Safety checks:\n" " --no-assertions ignore user assertions\n" diff --git a/src/goto-instrument/goto_instrument_parse_options.h b/src/goto-instrument/goto_instrument_parse_options.h index 04b3a8f7b2b..5a89e2cf9df 100644 --- a/src/goto-instrument/goto_instrument_parse_options.h +++ b/src/goto-instrument/goto_instrument_parse_options.h @@ -43,7 +43,7 @@ Author: Daniel Kroening, kroening@kroening.com "(log):" \ "(max-var):(max-po-trans):(ignore-arrays)" \ "(cfg-kill)(no-dependencies)(force-loop-duplication)" \ - "(call-graph)" \ + "(call-graph)(reachable-call-graph)" \ "(no-po-rendering)(render-cluster-file)(render-cluster-function)" \ "(nondet-volatile)(isr):" \ "(stack-depth):(nondet-static)" \ diff --git a/src/goto-programs/slice_global_inits.cpp b/src/goto-programs/slice_global_inits.cpp index cc4f7349eb7..33679d8a9d5 100644 --- a/src/goto-programs/slice_global_inits.cpp +++ b/src/goto-programs/slice_global_inits.cpp @@ -25,48 +25,24 @@ Date: December 2016 #include #include +#include + void slice_global_inits(goto_modelt &goto_model) { // gather all functions reachable from the entry point - - call_grapht call_graph(goto_model); - const call_grapht::grapht &graph=call_graph.graph; - goto_functionst &goto_functions=goto_model.goto_functions; - - std::list worklist; - std::unordered_set functions_reached; - const irep_idt entry_point=goto_functionst::entry_point(); + goto_functionst &goto_functions=goto_model.goto_functions; - goto_functionst::function_mapt::const_iterator e_it; - e_it=goto_functions.function_map.find(entry_point); - - if(e_it==goto_functions.function_map.end()) + if(!goto_functions.function_map.count(entry_point)) throw "entry point not found"; - worklist.push_back(entry_point); - - do - { - const irep_idt id=worklist.front(); - worklist.pop_front(); - - functions_reached.insert(id); - - const auto &p=graph.equal_range(id); - - for(auto it=p.first; it!=p.second; it++) - { - const irep_idt callee=it->second; - - if(functions_reached.find(callee)==functions_reached.end()) - worklist.push_back(callee); - } - } - while(!worklist.empty()); - - const irep_idt initialize=CPROVER_PREFIX "initialize"; - functions_reached.erase(initialize); + // Get the call graph restricted to functions reachable from + // the entry point: + call_grapht call_graph = + call_grapht::create_from_root_function(goto_model, entry_point, false); + const auto directed_graph = call_graph.get_directed_graph(); + INVARIANT( + !directed_graph.empty(), "At least __CPROVER_start should be reachable"); // gather all symbols used by reachable functions @@ -88,10 +64,11 @@ void slice_global_inits(goto_modelt &goto_model) symbol_collectort visitor; - assert(!functions_reached.empty()); - - for(const irep_idt &id : functions_reached) + for(std::size_t node_idx = 0; node_idx < directed_graph.size(); ++node_idx) { + const irep_idt &id = directed_graph[node_idx].function; + if(id == INITIALIZE_FUNCTION) + continue; const goto_functionst::goto_functiont &goto_function =goto_functions.function_map.at(id); const goto_programt &goto_program=goto_function.body; @@ -108,7 +85,7 @@ void slice_global_inits(goto_modelt &goto_model) // now remove unnecessary initializations goto_functionst::function_mapt::iterator f_it; - f_it=goto_functions.function_map.find(initialize); + f_it=goto_functions.function_map.find(INITIALIZE_FUNCTION); assert(f_it!=goto_functions.function_map.end()); goto_programt &goto_program=f_it->second.body; diff --git a/src/util/graph.h b/src/util/graph.h index b0b5713b5ee..122bb85dd18 100644 --- a/src/util/graph.h +++ b/src/util/graph.h @@ -246,6 +246,8 @@ class grapht void visit_reachable(node_indext src); + std::vector get_reachable(node_indext src, bool forwards) const; + void make_chordal(); // return value: number of connected subgraphs @@ -447,26 +449,40 @@ void grapht::shortest_path( template void grapht::visit_reachable(node_indext src) { - // DFS + std::vector reachable = get_reachable(src, true); + for(const auto index : reachable) + nodes[index].visited = true; +} + +template +std::vector +grapht::get_reachable(node_indext src, bool forwards) const +{ + std::vector result; + std::vector visited(size(), false); - std::stack s; + std::stack> s; s.push(src); while(!s.empty()) { - node_indext n=s.top(); + node_indext n = s.top(); s.pop(); - nodet &node=nodes[n]; - node.visited=true; + if(visited[n]) + continue; - for(typename edgest::const_iterator - it=node.out.begin(); - it!=node.out.end(); - it++) - if(!nodes[it->first].visited) - s.push(it->first); + result.push_back(n); + visited[n] = true; + + const auto &node = nodes[n]; + const auto &succs = forwards ? node.out : node.in; + for(const auto succ : succs) + if(!visited[succ.first]) + s.push(succ.first); } + + return result; } template diff --git a/unit/analyses/call_graph.cpp b/unit/analyses/call_graph.cpp index 985c804c91c..39c7a61588b 100644 --- a/unit/analyses/call_graph.cpp +++ b/unit/analyses/call_graph.cpp @@ -11,6 +11,7 @@ Module: Unit test for call graph generation #include #include +#include #include #include @@ -54,6 +55,7 @@ SCENARIO("call_graph", // { // A(); // B(); + // B(); // } // void B() // { @@ -72,8 +74,11 @@ SCENARIO("call_graph", call1.function()=symbol_exprt("A", void_function_type); code_function_callt call2; call2.function()=symbol_exprt("B", void_function_type); + code_function_callt call3; + call3.function()=symbol_exprt("B", void_function_type); calls.move_to_operands(call1); calls.move_to_operands(call2); + calls.move_to_operands(call3); goto_model.symbol_table.add( create_void_function_symbol("A", calls)); @@ -104,30 +109,162 @@ SCENARIO("call_graph", WHEN("A call graph is constructed from the GOTO functions") { - THEN("We expect A -> { A, B }, B -> { C, D }") + THEN("We expect A -> { A, B, B }, B -> { C, D }") { const auto &check_graph=call_graph_from_goto_functions.graph; - REQUIRE(check_graph.size()==4); - REQUIRE(multimap_key_matches(check_graph, "A", {"A", "B"})); + REQUIRE(check_graph.size()==5); + REQUIRE(multimap_key_matches(check_graph, "A", {"A", "B", "B"})); REQUIRE(multimap_key_matches(check_graph, "B", {"C", "D"})); } + THEN("No callsite data should be collected") + { + REQUIRE(call_graph_from_goto_functions.callsites.empty()); + } } WHEN("The call graph is inverted") { call_grapht inverse_call_graph_from_goto_functions= call_graph_from_goto_functions.get_inverted(); - THEN("We expect A -> { A }, B -> { A }, C -> { B }, D -> { B }") + THEN("We expect A -> { A }, B -> { A, A }, C -> { B }, D -> { B }") { const auto &check_graph=inverse_call_graph_from_goto_functions.graph; - REQUIRE(check_graph.size()==4); + REQUIRE(check_graph.size()==5); REQUIRE(multimap_key_matches(check_graph, "A", {"A"})); - REQUIRE(multimap_key_matches(check_graph, "B", {"A"})); + REQUIRE(multimap_key_matches(check_graph, "B", {"A", "A"})); REQUIRE(multimap_key_matches(check_graph, "C", {"B"})); REQUIRE(multimap_key_matches(check_graph, "D", {"B"})); } } - } + WHEN("A call graph is constructed with call-site tracking") + { + call_grapht call_graph_from_goto_functions(goto_model, true); + THEN("We expect two callsites for the A -> B edge, one for all others") + { + const auto &check_callsites=call_graph_from_goto_functions.callsites; + for(const auto &edge : call_graph_from_goto_functions.graph) + { + if(edge==call_grapht::grapht::value_type("A", "B")) + REQUIRE(check_callsites.at(edge).size()==2); + else + REQUIRE(check_callsites.at(edge).size()==1); + } + } + WHEN("Such a graph is inverted") + { + call_grapht inverted=call_graph_from_goto_functions.get_inverted(); + THEN("The callsite data should be discarded") + { + REQUIRE(inverted.callsites.empty()); + } + } + } + WHEN("A call-graph is constructed rooted at B") + { + call_grapht call_graph_from_b = + call_grapht::create_from_root_function(goto_model, "B", false); + THEN("We expect only B -> C and B -> D in the resulting graph") + { + const auto &check_graph=call_graph_from_b.graph; + REQUIRE(check_graph.size()==2); + REQUIRE(multimap_key_matches(check_graph, "B", {"C", "D"})); + } + } + + WHEN("The call graph is exported as a grapht") + { + call_grapht::directed_grapht exported= + call_graph_from_goto_functions.get_directed_graph(); + + typedef call_grapht::directed_grapht::node_indext node_indext; + std::map nodes_by_name; + for(node_indext i=0; i { A, B }, B -> { C, D }") + { + // Note that means the extra A -> B edge has gone away (the grapht + // structure can't represent the parallel edge) + REQUIRE(exported.has_edge(nodes_by_name["A"], nodes_by_name["A"])); + REQUIRE(exported.has_edge(nodes_by_name["A"], nodes_by_name["B"])); + REQUIRE(exported.has_edge(nodes_by_name["B"], nodes_by_name["C"])); + REQUIRE(exported.has_edge(nodes_by_name["B"], nodes_by_name["D"])); + } + + THEN("We expect A to have successors {A, B}") + { + std::set successors = get_callees(exported, "A"); + REQUIRE(successors.size() == 2); + REQUIRE(successors.count("A")); + REQUIRE(successors.count("B")); + } + + THEN("We expect C to have predecessors {B}") + { + std::set predecessors = get_callers(exported, "C"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("B")); + } + + THEN("We expect all of {A, B, C, D} to be reachable from A") + { + std::set successors = + get_reachable_functions(exported, "A"); + REQUIRE(successors.size() == 4); + REQUIRE(successors.count("A")); + REQUIRE(successors.count("B")); + REQUIRE(successors.count("C")); + REQUIRE(successors.count("D")); + } + + THEN("We expect {D, B, A} to be able to reach D") + { + std::set predecessors = + get_reaching_functions(exported, "D"); + REQUIRE(predecessors.size() == 3); + REQUIRE(predecessors.count("A")); + REQUIRE(predecessors.count("B")); + REQUIRE(predecessors.count("D")); + } + } + + WHEN("The call graph, with call sites, is exported as a grapht") + { + call_grapht call_graph_from_goto_functions(goto_model, true); + call_grapht::directed_grapht exported= + call_graph_from_goto_functions.get_directed_graph(); + + typedef call_grapht::directed_grapht::node_indext node_indext; + std::map nodes_by_name; + for(node_indext i=0; i { A, B }, B -> { C, D }") + { + // Note that means the extra A -> B edge has gone away (the grapht + // structure can't represent the parallel edge) + REQUIRE(exported.has_edge(nodes_by_name["A"], nodes_by_name["A"])); + REQUIRE(exported.has_edge(nodes_by_name["A"], nodes_by_name["B"])); + REQUIRE(exported.has_edge(nodes_by_name["B"], nodes_by_name["C"])); + REQUIRE(exported.has_edge(nodes_by_name["B"], nodes_by_name["D"])); + } + + THEN("We expect all edges to have one callsite apart from A -> B with 2") + { + for(node_indext i=0; i