Skip to content

Disconnect unreachable nodes in a graph #2369

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
merged 2 commits into from
Jun 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/util/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ class grapht
std::vector<node_indext>
get_reachable(const std::vector<node_indext> &src, bool forwards) const;

void disconnect_unreachable(node_indext src);
void disconnect_unreachable(const std::vector<node_indext> &src);

void make_chordal();

// return value: number of connected subgraphs
Expand Down Expand Up @@ -461,6 +464,43 @@ void grapht<N>::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 <class N>
void grapht<N>::disconnect_unreachable(node_indext src)
{
const std::vector<node_indext> 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 <class N>
void grapht<N>::disconnect_unreachable(const std::vector<node_indext> &src)
{
std::vector<node_indext> 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
Expand Down
1 change: 1 addition & 0 deletions unit/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
223 changes: 223 additions & 0 deletions unit/analyses/disconnect_unreachable_nodes_in_graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*******************************************************************\

Module: Unit test for graph class functions

Author:

\*******************************************************************/

#include <iostream>

#include <testing-utils/catch.hpp>

#include <analyses/call_graph.h>
#include <analyses/call_graph_helpers.h>

#include <util/std_code.h>
#include <util/symbol_table.h>

#include <goto-programs/goto_convert_functions.h>

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<irep_idt, irep_idt> &map,
const irep_idt &key,
const std::set<irep_idt> &values)
{
auto matching_values = map.equal_range(key);
std::set<irep_idt> 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<irep_idt, node_indext> 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<irep_idt> predecessors = get_callers(exported, "G");
REQUIRE(predecessors.size() == 1);
REQUIRE(predecessors.count("E"));
}

THEN("We expect F to have predecessors {E}")
{
std::set<irep_idt> 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<irep_idt> 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<irep_idt, node_indext> 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<irep_idt> 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<irep_idt> 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<irep_idt> predecessors = get_reaching_functions(exported, "G");
REQUIRE(predecessors.size() == 1);
REQUIRE(predecessors.count("G"));
}
}
}
}