diff --git a/regression/ansi-c/arch_flags_mcpu_bad/object.intel b/regression/ansi-c/arch_flags_mcpu_bad/object.intel index e32d04e4c56..11593f7dcf1 100644 Binary files a/regression/ansi-c/arch_flags_mcpu_bad/object.intel and b/regression/ansi-c/arch_flags_mcpu_bad/object.intel differ diff --git a/regression/ansi-c/arch_flags_mcpu_good/object.arm b/regression/ansi-c/arch_flags_mcpu_good/object.arm index f0a1397b8d6..9ac75ab47a9 100644 Binary files a/regression/ansi-c/arch_flags_mcpu_good/object.arm and b/regression/ansi-c/arch_flags_mcpu_good/object.arm differ diff --git a/regression/ansi-c/arch_flags_mcpu_good/test.desc b/regression/ansi-c/arch_flags_mcpu_good/test.desc index c7daf68b878..141e5a37063 100644 --- a/regression/ansi-c/arch_flags_mcpu_good/test.desc +++ b/regression/ansi-c/arch_flags_mcpu_good/test.desc @@ -12,12 +12,17 @@ The object file 'object.arm' was compiled from 'source.c' with goto-cc along with an ARM cross-compiler on a 64-bit platform with the following command line: - goto-cc --native-compiler=arm-none-eabi-gcc -mcpu=cortex-a15 -c source.c + goto-cc -o object.arm --native-compiler=arm-none-eabi-gcc -mcpu=cortex-a15 -c source.c -On Ubuntu, you can get a suitable compiler using: +To regenerate object.arm on an x86_64 machine, you will need to install +an ARM-32 cross compiler and pass the name of that compiler to the +--native-compiler flag. On Ubuntu, you can get a suitable compiler +using: sudo apt install gcc-arm-none-eabi +which will install arm-none-eabi-gcc (amongst other things). + preproc.i is already pre-processed so that it can be linked in without needing to invoke a pre-processor from a cross-compile toolchain on your local machine. Linking it together with the ARM object file, while diff --git a/regression/ansi-c/arch_flags_mthumb_bad/object.intel b/regression/ansi-c/arch_flags_mthumb_bad/object.intel index 72136c8ae3c..fc2d521766a 100644 Binary files a/regression/ansi-c/arch_flags_mthumb_bad/object.intel and b/regression/ansi-c/arch_flags_mthumb_bad/object.intel differ diff --git a/regression/ansi-c/arch_flags_mthumb_good/object.arm b/regression/ansi-c/arch_flags_mthumb_good/object.arm index eb41c683911..86bbebb1240 100644 Binary files a/regression/ansi-c/arch_flags_mthumb_good/object.arm and b/regression/ansi-c/arch_flags_mthumb_good/object.arm differ diff --git a/regression/ansi-c/arch_flags_mthumb_good/test.desc b/regression/ansi-c/arch_flags_mthumb_good/test.desc index 9f56b40997e..6bed1f3ccc9 100644 --- a/regression/ansi-c/arch_flags_mthumb_good/test.desc +++ b/regression/ansi-c/arch_flags_mthumb_good/test.desc @@ -12,12 +12,17 @@ file 'object.arm' was compiled from 'source.c' with goto-cc along with an ARM cross-compiler on a 64-bit platform with the following command line: - goto-cc --native-compiler=arm-none-eabi-gcc -mthumb -c source.c + goto-cc -o object.arm --native-compiler=arm-none-eabi-gcc -mthumb -c source.c -On Ubuntu, you can get a suitable compiler using: +To regenerate object.arm on an x86_64 machine, you will need to install +an ARM-32 cross compiler and pass the name of that compiler to the +--native-compiler flag. On Ubuntu, you can get a suitable compiler +using: sudo apt install gcc-arm-none-eabi +which will install arm-none-eabi-gcc (amongst other things). + preproc.i is already pre-processed so that it can be linked in without needing to invoke a pre-processor from a cross-compile toolchain on your local machine. Linking it together with the ARM object file, while diff --git a/src/analyses/Makefile b/src/analyses/Makefile index a63a4d4e1c6..8094a345251 100644 --- a/src/analyses/Makefile +++ b/src/analyses/Makefile @@ -22,6 +22,7 @@ SRC = ai.cpp \ locals.cpp \ natural_loops.cpp \ reaching_definitions.cpp \ + reachable_call_graph.cpp \ static_analysis.cpp \ uncaught_exceptions_analysis.cpp \ uninitialized_domain.cpp \ diff --git a/src/analyses/call_graph.cpp b/src/analyses/call_graph.cpp index 93850f64d1d..dec7f9205c8 100644 --- a/src/analyses/call_graph.cpp +++ b/src/analyses/call_graph.cpp @@ -7,7 +7,7 @@ Author: Daniel Kroening, kroening@kroening.com \*******************************************************************/ /// \file -/// Function Call Graphs +/// Function Call Graph #include "call_graph.h" @@ -18,14 +18,9 @@ call_grapht::call_grapht() { } -call_grapht::call_grapht(const goto_modelt &goto_model): - call_grapht(goto_model.goto_functions) +call_grapht::call_grapht(const goto_modelt &goto_model) { -} - -call_grapht::call_grapht(const goto_functionst &goto_functions) -{ - forall_goto_functions(f_it, goto_functions) + forall_goto_functions(f_it, goto_model.goto_functions) { const goto_programt &body=f_it->second.body; add(f_it->first, body); @@ -51,50 +46,111 @@ void call_grapht::add( const irep_idt &caller, const irep_idt &callee) { - graph.insert(std::pair(caller, callee)); + std::size_t caller_idx = node_numbering.number(caller); + if(caller_idx >= nodes.size()) + { + node_indext node_index = add_node(); + nodes[node_index].function_name = caller; + } + + std::size_t callee_idx = node_numbering.number(callee); + if(callee_idx >= nodes.size()) + { + node_indext node_index = add_node(); + nodes[node_index].function_name = callee; + } + + add_edge(caller_idx, callee_idx); } -/// Returns an inverted copy of this call graph -/// \return Inverted (callee -> caller) call graph -call_grapht call_grapht::get_inverted() const + +void call_grapht::output_dot_node(std::ostream &out, node_indext n) const { - call_grapht result; - for(const auto &caller_callee : graph) - result.add(caller_callee.second, caller_callee.first); - return result; + const nodet &node = nodes.at(n); + + for(const auto &edge : node.out) + { + out << " \"" << node.function_name << "\" -> " << "\"" + << nodes[edge.first].function_name << "\" " << " [arrowhead=\"vee\"];" + << "\n"; + } } -void call_grapht::output_dot(std::ostream &out) const +void call_grapht::output_xml_node(std::ostream &out, node_indext n) const { - out << "digraph call_graph {\n"; + const nodet &node = nodes.at(n); - for(const auto &edge : graph) + for(const auto &edge : node.out) { - out << " \"" << edge.first << "\" -> " - << "\"" << edge.second << "\" " - << " [arrowhead=\"vee\"];" - << "\n"; + out << "\n"; } +} - out << "}\n"; +void call_grapht::output_xml(std::ostream &out) const +{ + for(node_indext n = 0; n < nodes.size(); n++) + output_xml_node(out, n); } -void call_grapht::output(std::ostream &out) const +call_grapht call_grapht::get_inverted() const { - for(const auto &edge : graph) + call_grapht result; + for(const auto &n : nodes) { - out << edge.first << " -> " << edge.second << "\n"; + for(const auto &i : n.in) + result.add(n.function_name, nodes[i.first].function_name); } + return result; } -void call_grapht::output_xml(std::ostream &out) const +std::list call_grapht::shortest_function_path( + irep_idt src, irep_idt dest) { - for(const auto &edge : graph) + std::list result; + node_indext src_idx, dest_idx; + if(!get_node_index(src, src_idx)) + throw "unable to find src function in call graph"; + if(!get_node_index(dest, dest_idx)) + throw "unable to find destination function in call graph"; + + patht path; + shortest_path(src_idx, dest_idx, path); + for(const auto &n : path) { - out << "\n"; + result.push_back(nodes[n].function_name); + } + return result; +} + + +std::unordered_set +call_grapht::reachable_functions(irep_idt start_function) +{ + std::unordered_set result; + std::list worklist; + node_indext start_index; + + if(get_node_index(start_function, start_index)) + worklist.push_back(start_index); + else + throw "no start function found in call graph"; + + while(!worklist.empty()) + { + const node_indext id = worklist.front(); + worklist.pop_front(); + + result.insert(nodes[id].function_name); + for(const auto &o : nodes[id].out) + { + if(result.find(nodes[o.first].function_name) == result.end()) + worklist.push_back(o.first); + } } + + return result; } diff --git a/src/analyses/call_graph.h b/src/analyses/call_graph.h index 684f74c3a3a..28fcd147173 100644 --- a/src/analyses/call_graph.h +++ b/src/analyses/call_graph.h @@ -7,34 +7,100 @@ Author: Daniel Kroening, kroening@kroening.com \*******************************************************************/ /// \file -/// Function Call Graphs +/// Function Call Graph #ifndef CPROVER_ANALYSES_CALL_GRAPH_H #define CPROVER_ANALYSES_CALL_GRAPH_H #include #include +#include #include -class call_grapht +#include +#include +#include + + +/// \brief Function call graph: each node represents a function +/// in the GOTO model, a directed edge from A to B indicates +/// that function A calls function B. +/// Inherits from grapht to allow forward and +/// backward traversal of the function call graph +struct call_graph_nodet: public graph_nodet +{ + typedef graph_nodet::edget edget; + typedef graph_nodet::edgest edgest; + + irep_idt function_name; + bool visited = false; +}; + +class call_grapht: public grapht { public: call_grapht(); explicit call_grapht(const goto_modelt &); - explicit call_grapht(const goto_functionst &); - void output_dot(std::ostream &out) const; - void output(std::ostream &out) const; + void add(const irep_idt &caller, const irep_idt &callee); void output_xml(std::ostream &out) const; - typedef std::multimap grapht; - grapht graph; - - void add(const irep_idt &caller, const irep_idt &callee); + /// \return the inverted call graph call_grapht get_inverted() const; + /// \brief get the names of all functions reachable from a start function + /// \param start name of initial function + /// \return set of all names of the reachable functions + std::unordered_set + reachable_functions(irep_idt start); + + /// \brief Function returns the shortest path on the function call graph + /// between a source and a destination function + /// \param src name of the starting function + /// \param dest name of the destination function + /// \return list of function names on the shortest path between src and dest + std::listshortest_function_path(irep_idt src, irep_idt dest); + + /// get the index of the node that corresponds to a function name + /// \param[in] function_name function_name passed by reference + /// \param[out] n variable for the node index to be written to + /// \return true if a node with the given function name exists, + /// false if it does not exist + bool get_node_index(const irep_idt& function_name, node_indext &n) const + { + for(node_indext idx = 0; idx < nodes.size(); idx++) + { + if(nodes[idx].function_name == function_name) + { + n = idx; + return true; + } + } + return false; + } + + /// \brief get a list of functions called by a function + /// \param function_name the irep_idt of the function + /// \return an unordered set of all functions called by function_name + std::unordered_set get_successors( + const irep_idt& function_name) const + { + std::unordered_set result; + node_indext function_idx; + if(!get_node_index(function_name, function_idx)) + return result; + + for(const auto &o : nodes[function_idx].out) + result.insert(nodes[o.first].function_name); + return result; + } + + protected: + void output_dot_node(std::ostream &out, node_indext n) const override; + void output_xml_node(std::ostream &out, node_indext n) const; + numbering node_numbering; void add(const irep_idt &function, const goto_programt &body); }; diff --git a/src/analyses/reachable_call_graph.cpp b/src/analyses/reachable_call_graph.cpp new file mode 100644 index 00000000000..2ed17371dd9 --- /dev/null +++ b/src/analyses/reachable_call_graph.cpp @@ -0,0 +1,100 @@ +/*******************************************************************\ + +Module: Reachable Call Graphs + +Author: + +\*******************************************************************/ + +/// \file +/// Reachable Call Graph +#include "reachable_call_graph.h" +#include + +reachable_call_grapht::reachable_call_grapht +(const goto_modelt & _goto_model) +{ + build(_goto_model.goto_functions); +} + +void reachable_call_grapht::build(const goto_functionst & goto_functions) +{ + irep_idt start_function_name = goto_functions.entry_point(); + build(goto_functions, start_function_name); +} + + +void reachable_call_grapht::build( + const goto_functionst & goto_functions, + irep_idt start_function_name) +{ + std::set working_queue; + std::set done; + + // start from entry point + working_queue.insert(start_function_name); + + while(!working_queue.empty()) + { + irep_idt caller=*working_queue.begin(); + working_queue.erase(working_queue.begin()); + + if(!done.insert(caller).second) + continue; + + const goto_functionst::function_mapt::const_iterator f_it= + goto_functions.function_map.find(caller); + + if(f_it==goto_functions.function_map.end()) + continue; + + const goto_programt &program= + f_it->second.body; + + forall_goto_program_instructions(i_it, program) + { + if(i_it->is_function_call()) + { + const exprt &function_expr=to_code_function_call(i_it->code).function(); + if(function_expr.id()==ID_symbol) + { + irep_idt callee = to_symbol_expr(function_expr).get_identifier(); + add(caller, callee); + working_queue.insert(callee); + } + } + } + } +} + +std::unordered_set +reachable_call_grapht::backward_slice(irep_idt destination_function) +{ + std::unordered_set result; + std::list worklist; + for(node_indext idx=0; idx + backward_slice(irep_idt destination_function); +protected: + void build(const goto_functionst &); + void build(const goto_functionst &, irep_idt start_function); +}; + +#endif /* SRC_ANALYSES_REACHABLE_CALL_GRAPH_H_ */ diff --git a/src/goto-instrument/goto_instrument_parse_options.cpp b/src/goto-instrument/goto_instrument_parse_options.cpp index 5fa1567001b..88acb7f061e 100644 --- a/src/goto-instrument/goto_instrument_parse_options.cpp +++ b/src/goto-instrument/goto_instrument_parse_options.cpp @@ -60,6 +60,8 @@ Author: Daniel Kroening, kroening@kroening.com #include #include #include +#include + #include @@ -647,17 +649,25 @@ int goto_instrument_parse_optionst::doit() { do_indirect_call_and_rtti_removal(); call_grapht call_graph(goto_model); - 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); + call_graph.output_dot(std::cout); return 0; } + if(cmdline.isset("reachable-call-graph")) + { + do_indirect_call_and_rtti_removal(); + reachable_call_grapht reach_graph(goto_model); + if(cmdline.isset("xml")) + reach_graph.output_xml(std::cout); + else + reach_graph.output_dot(std::cout); + return 0; + } + if(cmdline.isset("dot")) { namespacet ns(goto_model.symbol_table); @@ -1452,6 +1462,8 @@ 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" + " --reachable-call-graph show graph of function calls potentially reachable from main function\n" // NOLINT(*) "\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..d6f85090923 100644 --- a/src/goto-instrument/goto_instrument_parse_options.h +++ b/src/goto-instrument/goto_instrument_parse_options.h @@ -44,6 +44,7 @@ Author: Daniel Kroening, kroening@kroening.com "(max-var):(max-po-trans):(ignore-arrays)" \ "(cfg-kill)(no-dependencies)(force-loop-duplication)" \ "(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..a08b3331449 100644 --- a/src/goto-programs/slice_global_inits.cpp +++ b/src/goto-programs/slice_global_inits.cpp @@ -30,11 +30,8 @@ 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; + goto_functionst &goto_functions=goto_model.goto_functions; const irep_idt entry_point=goto_functionst::entry_point(); @@ -44,26 +41,8 @@ void slice_global_inits(goto_modelt &goto_model) if(e_it==goto_functions.function_map.end()) 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()); + std::unordered_set functions_reached= + call_graph.reachable_functions(entry_point); const irep_idt initialize=CPROVER_PREFIX "initialize"; functions_reached.erase(initialize); diff --git a/src/util/graph.h b/src/util/graph.h index 607a7e3d8e2..900a5c57ac7 100644 --- a/src/util/graph.h +++ b/src/util/graph.h @@ -263,7 +263,7 @@ class grapht std::list topsort() const; void output_dot(std::ostream &out) const; - void output_dot_node(std::ostream &out, node_indext n) const; + virtual void output_dot_node(std::ostream &out, node_indext n) const; protected: class tarjant @@ -655,20 +655,26 @@ std::list::node_indext> grapht::topsort() const template void grapht::output_dot(std::ostream &out) const { + out << "digraph call_graph {\n"; + for(node_indext n=0; n void grapht::output_dot_node(std::ostream &out, node_indext n) const { - const nodet &node=nodes[n]; + const nodet &node=nodes.at(n); - for(typename edgest::const_iterator - it=node.out.begin(); - it!=node.out.end(); - it++) - out << n << " -> " << it->first << '\n'; + for(const auto &edge : node.out) + { + out << " \"" << n << "\" -> " + << "\"" << edge.first << "\" " + << " [arrowhead=\"vee\"];" + << "\n"; + } } #endif // CPROVER_UTIL_GRAPH_H diff --git a/unit/analyses/call_graph.cpp b/unit/analyses/call_graph.cpp index 985c804c91c..77c328cecac 100644 --- a/unit/analyses/call_graph.cpp +++ b/unit/analyses/call_graph.cpp @@ -30,15 +30,13 @@ static symbolt create_void_function_symbol( return function; } -static bool multimap_key_matches( - const std::multimap &map, +static bool graph_key_matches( + const call_grapht graph, const irep_idt &key, - const std::set &values) + const std::unordered_set &values) { - auto matching_values=map.equal_range(key); - std::set matching_set; - for(auto it=matching_values.first; it!=matching_values.second; ++it) - matching_set.insert(it->second); + std::unordered_set matching_set = + graph.get_successors(key); return matching_set==values; } @@ -106,10 +104,11 @@ SCENARIO("call_graph", { THEN("We expect A -> { A, 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(multimap_key_matches(check_graph, "B", {"C", "D"})); + REQUIRE(call_graph_from_goto_functions.size()==4); + REQUIRE(graph_key_matches + (call_graph_from_goto_functions, "A", {"A", "B"})); + REQUIRE(graph_key_matches + (call_graph_from_goto_functions, "B", {"C", "D"})); } } @@ -119,12 +118,15 @@ SCENARIO("call_graph", call_graph_from_goto_functions.get_inverted(); THEN("We expect A -> { A }, B -> { A }, C -> { B }, D -> { B }") { - const auto &check_graph=inverse_call_graph_from_goto_functions.graph; - REQUIRE(check_graph.size()==4); - REQUIRE(multimap_key_matches(check_graph, "A", {"A"})); - REQUIRE(multimap_key_matches(check_graph, "B", {"A"})); - REQUIRE(multimap_key_matches(check_graph, "C", {"B"})); - REQUIRE(multimap_key_matches(check_graph, "D", {"B"})); + REQUIRE(inverse_call_graph_from_goto_functions.size()==4); + REQUIRE(graph_key_matches + (inverse_call_graph_from_goto_functions, "A", {"A"})); + REQUIRE(graph_key_matches + (inverse_call_graph_from_goto_functions, "B", {"A"})); + REQUIRE(graph_key_matches + (inverse_call_graph_from_goto_functions, "C", {"B"})); + REQUIRE(graph_key_matches + (inverse_call_graph_from_goto_functions, "D", {"B"})); } }