Skip to content

Commit ca13458

Browse files
committed
Regularise and optimise symex path truncating
There are various situations where symex wishes to mark the current path non-viable: * On a __CPROVER_ASSUME(false), or an expression which simplifies to false * When --depth is exceeded * When --unwind is exceeded However these situations were handled in slightly varying ways, some using the symex_assume mechanism and others directly hacking the state guard. In the ASSUME(false) case, we also failed to actually falsify the guard, meaning we continued pointlessly generating constraints for unreachable code. This commit directs all such paths through either symex_assume_l2, if they wish to assert a non-false condition, or the new utility function truncate_current_path when unconditionally truncating the current execution path. We also mutate the state guard *in the specific case where it becomes false*, as this enables us to save execution time that would otherwise be spent on unreachable code, similar to how we treat conditional GOTOs with statically decidable conditions. This should mean that (a) all sorts of path truncation result in the guard becoming false and corresponding work-saving, and (b) the different kinds of truncation are handled uniformly, making future maintenance simpler.
1 parent e1e2fe5 commit ca13458

File tree

9 files changed

+62
-18
lines changed

9 files changed

+62
-18
lines changed

regression/cbmc/assume-false/test.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
int main(int argc, char **argv) {
2+
int i = 0;
3+
if(argc % 2)
4+
i = argc;
5+
else {
6+
__CPROVER_assume(0);
7+
i = 2;
8+
}
9+
10+
assert(i == argc);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.c
3+
--show-vcc
4+
main::1::i!0@1#[0-9]+ = 0
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
main::1::i!0@1#[0-9]+ = 2
9+
--
10+
This checks that code rendered unreachable by a __CPROVER_assume(0) does not generate constraints.
11+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
test.c
3+
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
The main test is in test.desc; this just checks that the program
10+
has the expected result

src/goto-symex/goto_symex.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,17 @@ class goto_symext
411411
virtual void symex_assume(statet &state, const exprt &cond);
412412
void symex_assume_l2(statet &, const exprt &cond);
413413

414+
/// Mark the current execution path unviable, due to an ASSUME(FALSE),
415+
/// exceeding depth or unwind limits, or anything else that means we
416+
/// statically know we can't possibly execute this path. Symex will use this
417+
/// to cease executing instructions (on this thread) until we next merge with
418+
/// a viable path
419+
/// \param state: state to alter
420+
void truncate_current_path(statet &state)
421+
{
422+
symex_assume_l2(state, false_exprt());
423+
}
424+
414425
/// Merge all branches joining at the current program point. Applies
415426
/// \ref merge_goto for each goto state (each of which corresponds to previous
416427
/// branch).

src/goto-symex/symex_function_call.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,7 @@ void goto_symext::symex_function_call_code(
258258
if(symex_config.unwinding_assertions)
259259
vcc(false_exprt(), "recursion unwinding assertion", state);
260260

261-
// add to state guard to prevent further assignments
262-
state.guard.add(false_exprt());
261+
truncate_current_path(state);
263262
}
264263

265264
symex_transition(state);

src/goto-symex/symex_goto.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ void goto_symext::symex_goto(statet &state)
268268
// generate assume(false) or a suitable negation if this
269269
// instruction is a conditional goto
270270
if(new_guard.is_true())
271-
symex_assume_l2(state, false_exprt());
271+
truncate_current_path(state);
272272
else
273273
symex_assume_l2(state, not_exprt(new_guard));
274274

@@ -796,14 +796,9 @@ void goto_symext::loop_bound_exceeded(
796796
"unwinding assertion loop " + std::to_string(loop_number),
797797
state);
798798

799-
// add to state guard to prevent further assignments
800-
state.guard.add(negated_cond);
801-
}
802-
else
803-
{
804-
// generate unwinding assumption, unless we permit partial loops
805-
symex_assume_l2(state, negated_cond);
806799
}
800+
// generate unwinding assumption, unless we permit partial loops
801+
symex_assume_l2(state, negated_cond);
807802
}
808803
}
809804

src/goto-symex/symex_main.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,14 @@ void goto_symext::symex_assume_l2(statet &state, const exprt &cond)
160160

161161
if(state.threads.size()==1)
162162
{
163+
exprt unguarded_cond = rewritten_cond;
163164
exprt tmp = state.guard.guard_expr(rewritten_cond);
164165
target.assumption(state.guard.as_expr(), tmp, state.source);
166+
// There's no need to drag assumptions in general around in the guard, but
167+
// when it's obvious that this branch is no longer viable we might as well
168+
// avoid the cost of executing other instructions on this branch:
169+
if(unguarded_cond.is_false())
170+
state.guard.add(unguarded_cond);
165171
}
166172
// symex_target_equationt::convert_assertions would fail to
167173
// consider assumptions of threads that have a thread-id above that
@@ -527,8 +533,11 @@ void goto_symext::execute_next_instruction(
527533
merge_gotos(state);
528534

529535
// depth exceeded?
530-
if(symex_config.max_depth != 0 && state.depth > symex_config.max_depth)
531-
state.guard.add(false_exprt());
536+
if(symex_config.max_depth != 0 && state.depth > symex_config.max_depth &&
537+
!state.guard.is_false())
538+
{
539+
truncate_current_path(state);
540+
}
532541
state.depth++;
533542

534543
// actually do instruction
@@ -619,7 +628,7 @@ void goto_symext::execute_next_instruction(
619628
case END_THREAD:
620629
// behaves like assume(0);
621630
if(!state.guard.is_false())
622-
state.guard.add(false_exprt());
631+
truncate_current_path(state);
623632
symex_transition(state);
624633
break;
625634

src/goto-symex/symex_throw.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ void goto_symext::symex_throw(statet &state)
4949
#endif
5050

5151
// An un-caught exception. Behaves like assume(0);
52-
symex_assume_l2(state, false_exprt());
52+
truncate_current_path(state);
5353
}

unit/path_strategies.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,9 @@ SCENARIO("path strategies")
202202
symex_eventt::resume(symex_eventt::enumt::JUMP, 9),
203203
symex_eventt::result(symex_eventt::enumt::FAILURE),
204204

205-
// The path where we enter the loop body twice. Successful because
205+
// The path where we enter the loop body twice. No result because
206206
// infeasible.
207207
symex_eventt::resume(symex_eventt::enumt::NEXT, 7),
208-
symex_eventt::result(symex_eventt::enumt::SUCCESS),
209208

210209
// Overall we fail.
211210
symex_eventt::result(symex_eventt::enumt::FAILURE),
@@ -235,9 +234,8 @@ SCENARIO("path strategies")
235234
symex_eventt::result(symex_eventt::enumt::SUCCESS),
236235

237236
// Pop line 7 that we saved from above, and execute the loop a second
238-
// time. Successful, since this path is infeasible.
237+
// time. No result, since this path is infeasible.
239238
symex_eventt::resume(symex_eventt::enumt::NEXT, 7),
240-
symex_eventt::result(symex_eventt::enumt::SUCCESS),
241239

242240
// Pop line 7 that we saved from above and bail out. That corresponds to
243241
// executing the loop once, decrementing x to 0; assert(x) should fail.

0 commit comments

Comments
 (0)