Skip to content

Commit 7512c70

Browse files
committed
Reachability slicer: mark reachable code more precisely
The reachability slicer currently uses a very simple graph walk, and in particular walks out of a function to all possible callers, regardless of whether we know the actual caller. This commit fixes that shortcoming by adding the callsite successor *at the callsite*, and tracking the fact that the callee's successor has already been taken care of in the graph search stack, thus allowing it to ignore the END_FUNCTION -> callsite edges which are less precise. Functions whose caller is genuinely unknown, such as the root function containing a reachability target (e.g. assert instruction) are treated as before, considering all possible callees. The backwards search is improved similarly to the forwards.
1 parent a06503b commit 7512c70

File tree

2 files changed

+123
-8
lines changed

2 files changed

+123
-8
lines changed

src/goto-instrument/reachability_slicer.cpp

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,47 @@ void reachability_slicert::fixedpoint_to_assertions(
5454
const is_threadedt &is_threaded,
5555
slicing_criteriont &criterion)
5656
{
57-
std::vector<cfgt::node_indext> src = get_sources(is_threaded, criterion);
57+
std::vector<search_stack_entryt> stack;
58+
std::vector<cfgt::node_indext> sources = get_sources(is_threaded, criterion);
59+
for(const auto source : sources)
60+
stack.emplace_back(source, false);
5861

59-
std::vector<cfgt::node_indext> reachable = cfg.get_reachable(src, false);
60-
for(const auto index : reachable)
61-
cfg[index].reaches_assertion = true;
62+
while(!stack.empty())
63+
{
64+
auto &node = cfg[stack.back().node_index];
65+
auto caller_is_known = stack.back().caller_is_known;
66+
stack.pop_back();
67+
68+
if(node.reaches_assertion)
69+
continue;
70+
node.reaches_assertion = true;
71+
72+
for(const auto &edge : node.in)
73+
{
74+
const auto &pred_node = cfg[edge.first];
75+
76+
if(pred_node.PC->is_end_function())
77+
{
78+
// This is an end-of-function -> successor-of-callsite edge.
79+
// Thus node.PC must have a predecessor instruction which is the calling
80+
// instruction. Queue both the caller and the end of the callee.
81+
stack.emplace_back(edge.first, true);
82+
stack.emplace_back(
83+
cfg.entry_map[std::prev(node.PC)], caller_is_known);
84+
}
85+
else if(pred_node.PC->is_function_call())
86+
{
87+
// Skip this predecessor, unless this is a bodyless function, or we
88+
// don't know who our callee was:
89+
if(!caller_is_known || std::next(pred_node.PC) == node.PC)
90+
stack.emplace_back(edge.first, caller_is_known);
91+
}
92+
else
93+
{
94+
stack.emplace_back(edge.first, caller_is_known);
95+
}
96+
}
97+
}
6298
}
6399

64100
/// Perform forwards depth-first search of the control-flow graph of the
@@ -71,11 +107,67 @@ void reachability_slicert::fixedpoint_from_assertions(
71107
const is_threadedt &is_threaded,
72108
slicing_criteriont &criterion)
73109
{
74-
std::vector<cfgt::node_indext> src = get_sources(is_threaded, criterion);
110+
std::vector<search_stack_entryt> stack;
111+
std::vector<cfgt::node_indext> sources = get_sources(is_threaded, criterion);
112+
for(const auto source : sources)
113+
stack.emplace_back(source, false);
114+
115+
while(!stack.empty())
116+
{
117+
auto &node = cfg[stack.back().node_index];
118+
auto caller_is_known = stack.back().caller_is_known;
119+
stack.pop_back();
120+
75121

76-
const std::vector<cfgt::node_indext> reachable = cfg.get_reachable(src, true);
77-
for(const auto index : reachable)
78-
cfg[index].reachable_from_assertion = true;
122+
if(node.reachable_from_assertion)
123+
continue;
124+
node.reachable_from_assertion = true;
125+
126+
if(node.PC->is_function_call())
127+
{
128+
// Queue the instruction's natural successor (function head, or next
129+
// instruction if the function is bodyless)
130+
INVARIANT(node.out.size() == 1, "Call sites should have one successor");
131+
auto successor_index = node.out.begin()->first;
132+
133+
// If the function has a body, mark the function head, but note that we
134+
// have already taken care of the return site.
135+
const auto &callee_head_node = cfg[successor_index];
136+
auto callee_it = callee_head_node.PC;
137+
if(callee_it != std::next(node.PC))
138+
{
139+
stack.emplace_back(successor_index, true);
140+
141+
// Check if it can return, and if so mark the callsite's successor:
142+
while(!callee_it->is_end_function())
143+
++callee_it;
144+
145+
if(cfg[cfg.entry_map[callee_it]].out.size() != 0)
146+
{
147+
stack.emplace_back(
148+
cfg.entry_map[std::next(node.PC)], caller_is_known);
149+
}
150+
}
151+
else
152+
{
153+
// Bodyless function -- mark the successor instruction only.
154+
stack.emplace_back(successor_index, caller_is_known);
155+
}
156+
}
157+
else if(node.PC->is_end_function())
158+
{
159+
if(!caller_is_known)
160+
{
161+
for(const auto &edge : node.out)
162+
stack.emplace_back(edge.first, false);
163+
}
164+
}
165+
else
166+
{
167+
for(const auto &edge : node.out)
168+
stack.emplace_back(edge.first, caller_is_known);
169+
}
170+
}
79171
}
80172

81173
/// This function removes all instructions that have the flag

src/goto-instrument/reachability_slicer_class.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ class reachability_slicert
5151

5252
typedef std::stack<cfgt::entryt> queuet;
5353

54+
/// A search stack entry, used in tracking nodes to mark reachable when
55+
/// walking over the CFG in `fixedpoint_to_assertions` and
56+
/// `fixedpoint_from_assertions`.
57+
struct search_stack_entryt
58+
{
59+
/// CFG node to mark reachable
60+
cfgt::node_indext node_index;
61+
62+
/// If true, this function's caller is known and has already been queued to
63+
/// mark reachable, so there is no need to queue anything when walking out
64+
/// of the function, whether forwards (via END_FUNCTION) or backwards (via a
65+
/// callsite).
66+
/// If false, this function's caller is not known, so when walking forwards
67+
/// from the end or backwards from the beginning we should queue all
68+
/// possible callers.
69+
bool caller_is_known;
70+
71+
search_stack_entryt(cfgt::node_indext node_index, bool caller_is_known) :
72+
node_index(node_index), caller_is_known(caller_is_known)
73+
{
74+
}
75+
};
76+
5477
void fixedpoint_to_assertions(
5578
const is_threadedt &is_threaded,
5679
slicing_criteriont &criterion);

0 commit comments

Comments
 (0)