Skip to content

Commit 048957c

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 7d2b070 commit 048957c

File tree

6 files changed

+127
-6
lines changed

6 files changed

+127
-6
lines changed

src/goto-symex/goto_state.cpp

Lines changed: 58 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,59 @@ std::size_t goto_statet::increase_generation(
3436

3537
return current_emplace_res.first->second.second;
3638
}
39+
40+
/// Given a condition that must hold on this path, propagate as much knowledge
41+
/// as possible. For example, if the condition is (x == 5), whether that's an
42+
/// assumption or a GOTO condition that we just passed through, we can propagate
43+
/// the constant '5' for future 'x' references. If the condition is (y == &o1)
44+
/// then we can use that to populate the value set.
45+
/// \param condition: condition that must hold on this path
46+
/// \param previous_state: currently active state, not necessarily the same as
47+
/// *this. For a GOTO instruction this is the state immediately preceding the
48+
/// branch while *this is the state that will be used on the taken- or
49+
/// not-taken path. For an ASSUME instruction \p previous_state and *this are
50+
/// equal.
51+
/// \param ns: global namespace
52+
void goto_statet::apply_condition(
53+
const exprt &condition,
54+
const goto_symex_statet &previous_state,
55+
const namespacet &ns)
56+
{
57+
if(condition.id() == ID_and)
58+
{
59+
for(const auto &op : condition.operands())
60+
apply_condition(op, previous_state, ns);
61+
}
62+
else if(condition.id() == ID_equal)
63+
{
64+
exprt lhs = condition.op0();
65+
exprt rhs = condition.op1();
66+
if(is_ssa_expr(rhs))
67+
std::swap(lhs, rhs);
68+
69+
if(is_ssa_expr(lhs) && goto_symex_is_constantt()(rhs))
70+
{
71+
const ssa_exprt &ssa_lhs = to_ssa_expr(lhs);
72+
INVARIANT(
73+
!ssa_lhs.get_level_2().empty(),
74+
"apply_condition operand should be L2 renamed");
75+
76+
if(
77+
previous_state.threads.size() == 1 ||
78+
previous_state.write_is_shared(ssa_lhs, ns) !=
79+
goto_symex_statet::write_is_shared_resultt::SHARED)
80+
{
81+
ssa_exprt l1_lhs = ssa_lhs;
82+
l1_lhs.remove_level_2();
83+
const irep_idt &l1_identifier = l1_lhs.get_identifier();
84+
85+
increase_generation(
86+
l1_identifier, l1_lhs, previous_state.get_l2_name_provider());
87+
88+
propagation[l1_identifier] = rhs;
89+
90+
value_set.assign(l1_lhs, rhs, ns, true, false);
91+
}
92+
}
93+
}
94+
}

src/goto-symex/goto_state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class goto_statet
7575
{
7676
}
7777

78+
void apply_condition(
79+
const exprt &condition, // L2
80+
const goto_symex_statet &previous_state,
81+
const namespacet &ns);
82+
7883
std::size_t increase_generation(
7984
const irep_idt l1_identifier,
8085
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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,36 @@ Author: Daniel Kroening, [email protected]
2222
#include <analyses/dirty.h>
2323
#include <util/simplify_expr.h>
2424

25+
/// Propagate constants and points-to information implied by a GOTO condition.
26+
/// See \ref goto_statet::apply_condition for aspects of this which are common
27+
/// to GOTO and ASSUME instructions.
28+
/// \param current_state: state prior to the GOTO instruction
29+
/// \param jump_taken_state: state following taking the GOTO
30+
/// \param jump_not_taken_state: fall-through state
31+
/// \param new_guard: GOTO condition, L2 renamed and simplified
32+
/// \param ns: global namespace
33+
static void apply_goto_condition(
34+
const goto_symex_statet &current_state,
35+
goto_statet &jump_taken_state,
36+
goto_statet &jump_not_taken_state,
37+
const exprt &new_guard,
38+
const namespacet &ns)
39+
{
40+
jump_taken_state.apply_condition(new_guard, current_state, ns);
41+
42+
// Try to find a negative condition that implies an equality constraint on
43+
// the branch-not-taken path.
44+
// Could use not_exprt + simplify, but let's avoid paying that cost on quite
45+
// a hot path:
46+
if(new_guard.id() == ID_not)
47+
jump_not_taken_state.apply_condition(new_guard.op0(), current_state, ns);
48+
else if(new_guard.id() == ID_notequal)
49+
{
50+
jump_not_taken_state.apply_condition(
51+
equal_exprt(new_guard.op0(), new_guard.op1()), current_state, ns);
52+
}
53+
}
54+
2555
void goto_symext::symex_goto(statet &state)
2656
{
2757
const goto_programt::instructiont &instruction=*state.source.pc;
@@ -230,6 +260,17 @@ void goto_symext::symex_goto(statet &state)
230260

231261
symex_transition(state, state_pc, backward);
232262

263+
if(!symex_config.doing_path_exploration)
264+
{
265+
// This doesn't work for --paths (single-path mode) yet, as in multi-path
266+
// mode we remove the implied constants at a control-flow merge, but in
267+
// single-path mode we don't run merge_gotos.
268+
auto &taken_state = backward ? state : goto_state_list.back().second;
269+
auto &not_taken_state = backward ? goto_state_list.back().second : state;
270+
271+
apply_goto_condition(state, taken_state, not_taken_state, new_guard, ns);
272+
}
273+
233274
// produce new guard symbol
234275
exprt guard_expr;
235276

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)