diff --git a/src/analyses/call_graph_helpers.cpp b/src/analyses/call_graph_helpers.cpp index 116ef12680e..449de47c6cc 100644 --- a/src/analyses/call_graph_helpers.cpp +++ b/src/analyses/call_graph_helpers.cpp @@ -70,3 +70,29 @@ std::set get_reaching_functions( { return get_connected_functions(graph, function, false); } + +std::set get_functions_reachable_within_n_steps( + const call_grapht::directed_grapht &graph, + const std::set &start_functions, + std::size_t &n) +{ + std::vector start_indices; + std::set result; + + for(const auto &func : start_functions) + start_indices.push_back(*(graph.get_node_index(func))); + + for(const auto &index : graph.depth_limited_search(start_indices, n)) + result.insert(graph[index].function); + + return result; +} + +std::set get_functions_reachable_within_n_steps( + const call_grapht::directed_grapht &graph, + const irep_idt &start_function, + std::size_t &n) +{ + std::set start_functions({ start_function }); + return get_functions_reachable_within_n_steps(graph, start_functions, n); +} diff --git a/src/analyses/call_graph_helpers.h b/src/analyses/call_graph_helpers.h index 170785a9f89..faedd44f15c 100644 --- a/src/analyses/call_graph_helpers.h +++ b/src/analyses/call_graph_helpers.h @@ -49,4 +49,28 @@ std::set get_reachable_functions( std::set get_reaching_functions( const call_grapht::directed_grapht &graph, const irep_idt &function); +/// Get either callers or callees reachable from a given +/// list of functions within N steps +/// \param graph: call graph +/// \param start_functions: set of start functions +/// \param n: number of steps to consider +/// \return set of functions that can be reached from the start function +/// including the start function +std::set get_functions_reachable_within_n_steps( + const call_grapht::directed_grapht &graph, + const std::set &start_functions, + std::size_t &n); + +/// Get either callers or callees reachable from a given +/// list of functions within N steps +/// \param graph: call graph +/// \param start_function: single start function +/// \param n: number of steps to consider +/// \return set of functions that can be reached from the start function +/// including the start function +std::set get_functions_reachable_within_n_steps( + const call_grapht::directed_grapht &graph, + const irep_idt &start_function, + std::size_t &n); + #endif diff --git a/src/util/graph.h b/src/util/graph.h index 8a83a0905a1..a79cf2f4316 100644 --- a/src/util/graph.h +++ b/src/util/graph.h @@ -255,6 +255,18 @@ class grapht void disconnect_unreachable(node_indext src); void disconnect_unreachable(const std::vector &src); + std::vector depth_limited_search( + const node_indext &src, + std::size_t &limit, + bool forwards) const; + + std::vector + depth_limited_search(typename N::node_indext src, std::size_t limit) const; + + std::vector depth_limited_search( + std::vector &src, + std::size_t limit) const; + void make_chordal(); // return value: number of connected subgraphs @@ -278,6 +290,11 @@ class grapht std::function f) const; protected: + std::vector depth_limited_search( + std::vector &src, + std::size_t limit, + std::vector &visited) const; + class tarjant { public: @@ -584,6 +601,83 @@ std::vector grapht::get_reachable( return result; } +/// Run recursive depth-limited search on the graph, starting +/// from multiple source nodes, to find the nodes reachable within n steps. +/// This function initialises the search. +/// \param src The node to start the search from. +/// \param limit limit on steps +/// \return a vector of reachable node indices +template +std::vector grapht::depth_limited_search( + const typename N::node_indext src, + std::size_t limit) const +{ + std::vector start_vector(1, src); + return depth_limited_search(start_vector, limit); +} + +/// Run recursive depth-limited search on the graph, starting +/// from multiple source nodes, to find the nodes reachable within n steps. +/// This function initialises the search. +/// \param src The nodes to start the search from. +/// \param limit limit on steps +/// \return a vector of reachable node indices +template +std::vector grapht::depth_limited_search( + std::vector &src, + std::size_t limit) const +{ + std::vector visited(nodes.size(), false); + + for(const auto &node : src) + { + PRECONDITION(node < nodes.size()); + visited[node] = true; + } + + return depth_limited_search(src, limit, visited); +} + +/// Run recursive depth-limited search on the graph, starting +// from multiple source nodes, to find the nodes reachable within n steps +/// \param src The nodes to start the search from. +/// \param limit limit on steps +/// \param visited vector of booleans indicating whether a node has been visited +/// \return a vector of reachable node indices +template +std::vector grapht::depth_limited_search( + std::vector &src, + std::size_t limit, + std::vector &visited) const +{ + if(limit == 0) + return src; + + std::vector next_ring; + + for(const auto &n : src) + { + for(const auto &o : nodes[n].out) + { + if(!visited[o.first]) + { + next_ring.push_back(o.first); + visited[o.first] = true; + } + } + } + + if(next_ring.empty()) + return src; + + limit--; + + for(const auto &succ : depth_limited_search(next_ring, limit, visited)) + src.push_back(succ); + + return src; +} + template std::size_t grapht::connected_subgraphs( std::vector &subgraph_nr) diff --git a/unit/analyses/call_graph.cpp b/unit/analyses/call_graph.cpp index c6e626af8b5..d14127e7af3 100644 --- a/unit/analyses/call_graph.cpp +++ b/unit/analyses/call_graph.cpp @@ -223,6 +223,48 @@ SCENARIO("call_graph", REQUIRE(exported.has_edge(nodes_by_name["B"], nodes_by_name["D"])); } + THEN("We expect {A,B} to be reachable from {A} in 1 step") + { + irep_idt function_name = "A"; + std::size_t depth = 1; + std::set reachable = get_functions_reachable_within_n_steps( + exported, function_name, depth); + REQUIRE(reachable.size() == 2); + REQUIRE(reachable.count("A")); + REQUIRE(reachable.count("B")); + } + THEN("We expect {A,B,C,D} to be reachable from {A} in 2 and 3 steps") + { + irep_idt function_name = "A"; + std::size_t depth = 2; + std::set reachable = get_functions_reachable_within_n_steps( + exported, function_name, depth); + REQUIRE(reachable.size() == 4); + REQUIRE(reachable.count("A")); + REQUIRE(reachable.count("B")); + REQUIRE(reachable.count("C")); + REQUIRE(reachable.count("D")); + + depth = 3; + reachable = get_functions_reachable_within_n_steps( + exported, function_name, depth); + REQUIRE(reachable.size() == 4); + REQUIRE(reachable.count("A")); + REQUIRE(reachable.count("B")); + REQUIRE(reachable.count("C")); + REQUIRE(reachable.count("D")); + } + + THEN("We expect only {A} to be reachable from {A} in 0 steps") + { + irep_idt function_name = "A"; + std::size_t depth = 0; + std::set reachable = get_functions_reachable_within_n_steps( + exported, function_name, depth); + REQUIRE(reachable.size() == 1); + REQUIRE(reachable.count("A")); + } + THEN("We expect A to have successors {A, B}") { std::set successors = get_callees(exported, "A");