Skip to content

Commit 9622910

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 9fe432b commit 9622910

File tree

1 file changed

+106
-8
lines changed

1 file changed

+106
-8
lines changed

src/goto-instrument/reachability_slicer.cpp

+106-8
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,49 @@ 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+
// Stack entries are pairs of node indices and a boolean flag that indicates
58+
// whether the function's callsite is known, in which case it has already
59+
// been placed on the stack and function heads are a no-op.
5860

59-
std::vector<cfgt::node_indext> reachable = cfg.get_reachable(src, false);
60-
for(const auto index : reachable)
61-
cfg[index].reaches_assertion = true;
61+
std::vector<std::pair<cfgt::node_indext, bool>> stack;
62+
std::vector<cfgt::node_indext> sources = get_sources(is_threaded, criterion);
63+
for(const auto source : sources)
64+
stack.emplace_back(source, false);
65+
66+
while(!stack.empty())
67+
{
68+
auto index = stack.back().first;
69+
auto callsite_is_known = stack.back().second;
70+
stack.pop_back();
71+
72+
auto &node = cfg[index];
73+
if(node.reaches_assertion)
74+
continue;
75+
node.reaches_assertion = true;
76+
77+
for(const auto &edge : node.in)
78+
{
79+
const auto &pred_node = cfg[edge.first];
80+
81+
if(pred_node.PC->is_end_function())
82+
{
83+
stack.emplace_back(edge.first, true);
84+
stack.emplace_back(
85+
cfg.entry_map[std::prev(node.PC)], callsite_is_known);
86+
}
87+
else if(pred_node.PC->is_function_call())
88+
{
89+
// Skip this predecessor, unless this is a bodyless function, or we
90+
// don't know who our callee was:
91+
if(!callsite_is_known || pred_node.PC == std::prev(node.PC))
92+
stack.emplace_back(edge.first, callsite_is_known);
93+
}
94+
else
95+
{
96+
stack.emplace_back(edge.first, callsite_is_known);
97+
}
98+
}
99+
}
62100
}
63101

64102
/// Perform forwards depth-first search of the control-flow graph of the
@@ -71,11 +109,71 @@ void reachability_slicert::fixedpoint_from_assertions(
71109
const is_threadedt &is_threaded,
72110
slicing_criteriont &criterion)
73111
{
74-
std::vector<cfgt::node_indext> src = get_sources(is_threaded, criterion);
112+
// Stack entries are pairs of node indices and a boolean flag that indicates
113+
// whether the function's callsite is known, in which case it has already
114+
// been placed on the stack and return sites are a no-op.
115+
116+
std::vector<std::pair<cfgt::node_indext, bool>> stack;
117+
std::vector<cfgt::node_indext> sources = get_sources(is_threaded, criterion);
118+
for(const auto source : sources)
119+
stack.emplace_back(source, false);
120+
121+
while(!stack.empty())
122+
{
123+
auto index = stack.back().first;
124+
auto callsite_is_known = stack.back().second;
125+
stack.pop_back();
75126

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

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

0 commit comments

Comments
 (0)