Skip to content

Commit a7e3c19

Browse files
committed
Symex: propagate constants implied by assumptions and conditions
When passing `assume(symbol == constant)` or `if symbol == constant then GOTO`, we can populate the constant propagator accordingly and use that information until the next merge point without that constraint. We implement this by allocating and defining a fresh L2 generation on this path, which will be merged as "real", assignment-derived generations are. Symbols are subject to propagation under the same conditions as they are on assignment (e.g. requiring that they are not subject to concurrent modification by other threads).
1 parent c8e1b38 commit a7e3c19

File tree

6 files changed

+106
-6
lines changed

6 files changed

+106
-6
lines changed

src/goto-symex/goto_state.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Author: Romain Brenguier, [email protected]
77
\*******************************************************************/
88

99
#include "goto_state.h"
10+
#include "goto_symex_is_constant.h"
11+
#include "goto_symex_state.h"
1012

1113
#include <util/format_expr.h>
1214

@@ -34,3 +36,47 @@ std::size_t goto_statet::increase_generation(
3436

3537
return current_emplace_res.first->second.second;
3638
}
39+
40+
void goto_statet::apply_condition(
41+
const exprt &condition,
42+
const goto_symex_statet &previous_state,
43+
const namespacet &ns)
44+
{
45+
if(condition.id() == ID_and)
46+
{
47+
for(const auto &op : condition.operands())
48+
apply_condition(op, previous_state, ns);
49+
}
50+
else if(condition.id() == ID_equal)
51+
{
52+
exprt lhs = condition.op0();
53+
exprt rhs = condition.op1();
54+
if(is_ssa_expr(rhs))
55+
std::swap(lhs, rhs);
56+
57+
if(is_ssa_expr(lhs) && goto_symex_is_constantt()(rhs))
58+
{
59+
const ssa_exprt &ssa_lhs = to_ssa_expr(lhs);
60+
INVARIANT(
61+
!ssa_lhs.get_level_2().empty(),
62+
"apply_condition operand should be L2 renamed");
63+
64+
if(
65+
previous_state.threads.size() == 1 ||
66+
previous_state.write_is_shared(ssa_lhs, ns) !=
67+
goto_symex_statet::write_is_shared_resultt::SHARED)
68+
{
69+
ssa_exprt l1_lhs = ssa_lhs;
70+
l1_lhs.remove_level_2();
71+
const irep_idt &l1_identifier = l1_lhs.get_identifier();
72+
73+
increase_generation(
74+
l1_identifier, l1_lhs, previous_state.get_l2_name_provider());
75+
76+
propagation[l1_identifier] = rhs;
77+
78+
value_set.assign(l1_lhs, rhs, ns, true, false);
79+
}
80+
}
81+
}
82+
}

src/goto-symex/goto_state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ class goto_statet
8282
value_set = std::move(other_state.value_set);
8383
}
8484

85+
void apply_condition(
86+
const exprt &condition, // L2
87+
const goto_symex_statet &previous_state,
88+
const namespacet &ns);
89+
8590
std::size_t increase_generation(
8691
const irep_idt l1_identifier,
8792
const ssa_exprt &lhs,

src/goto-symex/goto_symex_state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ class goto_symex_statet final : public goto_statet
233233
level2.current_names.erase(it);
234234
}
235235

236+
std::function<std::size_t(const irep_idt &)> get_l2_name_provider() const
237+
{
238+
return fresh_l2_name_provider;
239+
}
240+
236241
private:
237242
std::function<std::size_t(const irep_idt &)> fresh_l2_name_provider;
238243

src/goto-symex/symex_goto.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ Author: Daniel Kroening, [email protected]
2222
#include <analyses/dirty.h>
2323
#include <util/simplify_expr.h>
2424

25+
static void apply_goto_condition(
26+
const goto_symex_statet &current_state,
27+
goto_statet &jump_taken_state,
28+
goto_statet &jump_not_taken_state,
29+
const exprt &new_guard,
30+
const namespacet &ns)
31+
{
32+
jump_taken_state.apply_condition(new_guard, current_state, ns);
33+
34+
// Try to find a negative condition that implies an equality constraint on
35+
// the branch-not-taken path.
36+
// Could use not_exprt + simplify, but let's avoid paying that cost on quite
37+
// a hot path:
38+
if(new_guard.id() == ID_not)
39+
jump_not_taken_state.apply_condition(new_guard.op0(), current_state, ns);
40+
else if(new_guard.id() == ID_notequal)
41+
{
42+
jump_not_taken_state.apply_condition(
43+
equal_exprt(new_guard.op0(), new_guard.op1()), current_state, ns);
44+
}
45+
}
46+
2547
void goto_symext::symex_goto(statet &state)
2648
{
2749
const goto_programt::instructiont &instruction=*state.source.pc;
@@ -228,6 +250,16 @@ void goto_symext::symex_goto(statet &state)
228250

229251
symex_transition(state, state_pc, backward);
230252

253+
if(!symex_config.doing_path_exploration)
254+
{
255+
// In --paths mode we would require the GOTO condition as a constraint
256+
// as well, effectively treating the branch like an assumption.
257+
auto &taken_state = backward ? state : goto_state_list.back().second;
258+
auto &not_taken_state = backward ? goto_state_list.back().second : state;
259+
260+
apply_goto_condition(state, taken_state, not_taken_state, new_guard, ns);
261+
}
262+
231263
// produce new guard symbol
232264
exprt guard_expr;
233265

src/goto-symex/symex_main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ void goto_symext::symex_assume_l2(statet &state, const exprt &cond)
160160
if(state.atomic_section_id!=0 &&
161161
state.guard.is_false())
162162
symex_atomic_end(state);
163+
164+
state.apply_condition(cond, state, ns);
163165
}
164166

165167
void goto_symext::rewrite_quantifiers(exprt &expr, statet &state)

unit/path_strategies.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,23 +160,27 @@ SCENARIO("path strategies")
160160
"/* 1 */ int main() \n"
161161
"/* 2 */ { \n"
162162
"/* 3 */ int x; \n"
163-
"/* 4 */ " CPROVER_PREFIX
164-
"assume(x == 1); \n"
163+
"/* 4 */ if(x == 1) { \n"
165164
"/* 5 */ \n"
166-
"/* 6 */ while(x) \n"
167-
"/* 7 */ --x; \n"
165+
"/* 6 */ while(x) \n"
166+
"/* 7 */ --x; \n"
168167
"/* 8 */ \n"
169-
"/* 9 */ assert(x); \n"
170-
"/* 10 */ } \n";
168+
"/* 9 */ assert(x); \n"
169+
"/* 10 */ } \n"
170+
"/* 11 */ } \n";
171171
// clang-format on
172172

173173
check_with_strategy(
174174
"lifo",
175175
opts_callback,
176176
c,
177177
{
178+
// The path where x != 1 and we have nothing to check:
179+
symex_eventt::resume(symex_eventt::enumt::JUMP, 11),
180+
178181
// The path where we skip the loop body. Successful because the path is
179182
// implausible, we cannot have skipped the body if x == 1.
183+
symex_eventt::resume(symex_eventt::enumt::NEXT, 6),
180184
symex_eventt::resume(symex_eventt::enumt::JUMP, 9),
181185
symex_eventt::result(symex_eventt::enumt::SUCCESS),
182186

@@ -200,6 +204,12 @@ SCENARIO("path strategies")
200204
opts_callback,
201205
c,
202206
{
207+
// First the path where we enter the if-block at line 4 then on hitting
208+
// the branch at line 6 consider skipping straight to the end, but find
209+
// nothing there to investigate:
210+
symex_eventt::resume(symex_eventt::enumt::NEXT, 6),
211+
symex_eventt::resume(symex_eventt::enumt::JUMP, 11),
212+
203213
// The path where we skip the loop body. Successful because the path is
204214
// implausible, we cannot have skipped the body if x == 1.
205215
//

0 commit comments

Comments
 (0)