diff --git a/src/util/graph.h b/src/util/graph.h index 6b7fd6e07f7..8a83a0905a1 100644 --- a/src/util/graph.h +++ b/src/util/graph.h @@ -252,6 +252,9 @@ class grapht std::vector get_reachable(const std::vector &src, bool forwards) const; + void disconnect_unreachable(node_indext src); + void disconnect_unreachable(const std::vector &src); + void make_chordal(); // return value: number of connected subgraphs @@ -461,6 +464,43 @@ void grapht::visit_reachable(node_indext src) nodes[index].visited = true; } +/// Removes any edges between nodes in a graph that are unreachable from +/// a given start node. Used for computing cone of influence, +/// by disconnecting unreachable nodes and then performing backwards +/// reachability. +/// Note nodes are not actually removed from the vector nodes, because +/// this requires renumbering node indices. This copies the way nodes +/// are "removed" in make_chordal. +/// \param src: start node +template +void grapht::disconnect_unreachable(node_indext src) +{ + const std::vector source_nodes(1, src); + disconnect_unreachable(source_nodes); +} + +/// Removes any edges between nodes in a graph that are unreachable +/// from a vector of start nodes. +/// \param src: vector of indices of start nodes +template +void grapht::disconnect_unreachable(const std::vector &src) +{ + std::vector reachable = get_reachable(src, true); + std::sort(reachable.begin(), reachable.end()); + std::size_t reachable_idx = 0; + for(std::size_t i = 0; i < nodes.size(); i++) + { + if(reachable_idx >= reachable.size()) + remove_edges(i); + else if(i == reachable[reachable_idx]) + reachable_idx++; + else if(i > reachable[reachable_idx]) + throw "error disconnecting unreachable nodes"; + else + remove_edges(i); + } +} + /// Add to `set`, nodes that are reachable from `set`. /// /// This implements a depth first search using a stack: at each step we pop a diff --git a/unit/Makefile b/unit/Makefile index c095a23b01c..6457ab78637 100644 --- a/unit/Makefile +++ b/unit/Makefile @@ -9,6 +9,7 @@ SRC = unit_tests.cpp \ SRC += unit_tests.cpp \ analyses/ai/ai_simplify_lhs.cpp \ analyses/call_graph.cpp \ + analyses/disconnect_unreachable_nodes_in_graph.cpp \ analyses/does_remove_const/does_expr_lose_const.cpp \ analyses/does_remove_const/does_type_preserve_const_correctness.cpp \ analyses/does_remove_const/is_type_at_least_as_const_as.cpp \ diff --git a/unit/analyses/disconnect_unreachable_nodes_in_graph.cpp b/unit/analyses/disconnect_unreachable_nodes_in_graph.cpp new file mode 100644 index 00000000000..84d26f106c5 --- /dev/null +++ b/unit/analyses/disconnect_unreachable_nodes_in_graph.cpp @@ -0,0 +1,223 @@ +/*******************************************************************\ + +Module: Unit test for graph class functions + +Author: + +\*******************************************************************/ + +#include + +#include + +#include +#include + +#include +#include + +#include + +static symbolt +create_void_function_symbol(const irep_idt &name, const codet &code) +{ + const code_typet void_function_type({}, empty_typet()); + symbolt function; + function.name = name; + function.type = void_function_type; + function.mode = ID_java; + function.value = code; + return function; +} + +static bool multimap_key_matches( + const std::multimap &map, + const irep_idt &key, + const std::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); + return matching_set == values; +} + +SCENARIO("graph", "[core][util][graph]") +{ + GIVEN("Some cyclic function calls") + { + // Create code like: + // void A() + // { + // A(); + // B(); + // B(); + // } + // void B() + // { + // C(); + // D(); + // } + // void C() { } + // void D() { } + // void E() + // { + // F(); + // G(); + // } + // void F() { } + // void G() { } + + goto_modelt goto_model; + code_typet void_function_type({}, empty_typet()); + + { + code_blockt calls; + code_function_callt call1; + call1.function() = symbol_exprt("A", void_function_type); + code_function_callt call2; + call2.function() = symbol_exprt("B", void_function_type); + calls.move_to_operands(call1); + calls.move_to_operands(call2); + + goto_model.symbol_table.add(create_void_function_symbol("A", calls)); + } + + { + code_blockt calls; + code_function_callt call1; + call1.function() = symbol_exprt("C", void_function_type); + code_function_callt call2; + call2.function() = symbol_exprt("D", void_function_type); + calls.move_to_operands(call1); + calls.move_to_operands(call2); + + goto_model.symbol_table.add(create_void_function_symbol("B", calls)); + } + + { + code_blockt calls; + code_function_callt call1; + call1.function() = symbol_exprt("F", void_function_type); + code_function_callt call2; + call2.function() = symbol_exprt("G", void_function_type); + calls.move_to_operands(call1); + calls.move_to_operands(call2); + + goto_model.symbol_table.add(create_void_function_symbol("E", calls)); + } + + goto_model.symbol_table.add(create_void_function_symbol("C", code_skipt())); + goto_model.symbol_table.add(create_void_function_symbol("D", code_skipt())); + goto_model.symbol_table.add(create_void_function_symbol("F", code_skipt())); + goto_model.symbol_table.add(create_void_function_symbol("G", code_skipt())); + + stream_message_handlert msg(std::cout); + goto_convert(goto_model, msg); + + call_grapht call_graph_from_goto_functions(goto_model); + + WHEN("A call graph is constructed from the GOTO functions") + { + THEN("We expect A -> { A, B, B }, B -> { C, D }, E -> { F, G }") + { + const auto &check_graph = call_graph_from_goto_functions.edges; + REQUIRE(check_graph.size() == 6); + REQUIRE(multimap_key_matches(check_graph, "A", {"A", "B", "B"})); + REQUIRE(multimap_key_matches(check_graph, "B", {"C", "D"})); + REQUIRE(multimap_key_matches(check_graph, "E", {"F", "G"})); + } + } + + 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 < exported.size(); ++i) + nodes_by_name[exported[i].function] = i; + + THEN("We expect 7 nodes") + { + REQUIRE(exported.size() == 7); + } + + THEN("We expect edges A -> { A, B }, B -> { C, D }, E -> { F, G }") + { + // 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"])); + REQUIRE(exported.has_edge(nodes_by_name["E"], nodes_by_name["F"])); + REQUIRE(exported.has_edge(nodes_by_name["E"], nodes_by_name["G"])); + } + + THEN("We expect G to have predecessors {E}") + { + std::set predecessors = get_callers(exported, "G"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("E")); + } + + THEN("We expect F to have predecessors {E}") + { + std::set predecessors = get_callers(exported, "F"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("E")); + } + + THEN("We expect {E} to be able to reach E") + { + std::set predecessors = get_reaching_functions(exported, "E"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("E")); + } + } + + WHEN("functions unreachable from A in the grapht are disconnected") + { + 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 < exported.size(); ++i) + nodes_by_name[exported[i].function] = i; + + exported.disconnect_unreachable(nodes_by_name["A"]); + + THEN("We expect edges A -> { A, B }, B -> { C, D }") + { + 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 {E} to be able to reach E") + { + std::set predecessors = get_reaching_functions(exported, "E"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("E")); + } + + THEN("We expect {F} to be able to reach F") + { + std::set predecessors = get_reaching_functions(exported, "F"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("F")); + } + + THEN("We expect {G} to be able to reach G") + { + std::set predecessors = get_reaching_functions(exported, "G"); + REQUIRE(predecessors.size() == 1); + REQUIRE(predecessors.count("G")); + } + } + } +}