Skip to content

Commit 60b2851

Browse files
author
Daniel Kroening
authored
Merge pull request #5003 from smowton/smowton/feature/revert-guards-after-calls
[TG-9002] Symex: revert guards after call return
2 parents 20a2055 + 66133a6 commit 60b2851

20 files changed

+232
-16
lines changed
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
void g()
2+
{
3+
g();
4+
}
5+
6+
void f(int y)
7+
{
8+
if(y == 5)
9+
{
10+
g();
11+
y = 10;
12+
}
13+
}
14+
15+
int main(int argc, char **argv)
16+
{
17+
f(argc);
18+
assert(argc == 1);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE paths-lifo-expected-failure
2+
test.c
3+
--show-vcc --unwind 10
4+
^\{1\} main::argc!0@1#1 = 1$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^\{-[0-9]+\} f::y!0@1#[0-9]+ = 10$
9+
--
10+
This checks that the assertion, argc == 1, is unguarded (i.e. is not of the form
11+
'guard => condition'). Such a guard is redundant, but could be added before goto-symex
12+
made sure to restore guards to their state at function entry.
13+
We also check that no VCC is created for the unreachable code 'y = 10;'
14+
--paths mode is excluded as it currently always accrues large guards as it proceeds through execution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.c
3+
--unwind 10
4+
^\[main\.assertion\.[0-9]+\] line [0-9]+ assertion argc == 1: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
The main test is in test.desc; this just checks that the test is executable and
11+
the assertion fails as expected.
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
void f(int y)
2+
{
3+
if(y == 5)
4+
{
5+
for(int i = 0; i < 20; ++i)
6+
{
7+
}
8+
y = 10;
9+
}
10+
}
11+
12+
int main(int argc, char **argv)
13+
{
14+
f(argc);
15+
assert(argc == 1);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE paths-lifo-expected-failure
2+
test.c
3+
--show-vcc --unwind 10
4+
^\{1\} main::argc!0@1#1 = 1$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
This checks that the assertion, argc == 1, is unguarded (i.e. is not of the form
10+
'guard => condition'). Such a guard is redundant, but could be added before goto-symex
11+
made sure to restore guards to their state at function entry.
12+
--paths mode is excluded as it currently always accrues large guards as it proceeds through execution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.c
3+
--unwind 10
4+
^\[main\.assertion\.[0-9]+\] line [0-9]+ assertion argc == 1: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
The main test is in test.desc; this just checks that the test is executable and
11+
the assertion fails as expected.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FUTURE
2+
test.c
3+
--show-vcc --unwind 10
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
^\{-[0-9]+\} f::y!0@1#[0-9]+ = 10$
8+
--
9+
We also check that no VCC is created for the unreachable code 'y = 10;'
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
void g(int x)
2+
{
3+
return g(x + 1);
4+
}
5+
6+
void f(int y)
7+
{
8+
if(y == 5)
9+
{
10+
g(1);
11+
y = 10;
12+
}
13+
}
14+
15+
int main(int argc, char **argv)
16+
{
17+
f(argc);
18+
assert(argc == 1);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE paths-lifo-expected-failure
2+
test.c
3+
--show-vcc --depth 1000
4+
^\{1\} main::argc!0@1#1 = 1$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^\{-[0-9]+\} f::y!0@1#[0-9]+ = 10$
9+
--
10+
This checks that the assertion, argc == 1, is unguarded (i.e. is not of the form
11+
'guard => condition'). Such a guard is redundant, but could be added before goto-symex
12+
made sure to restore guards to their state at function entry.
13+
We also check that no VCC is created for the unreachable code 'y = 10;'
14+
--paths mode is excluded as it currently always accrues large guards as it proceeds through execution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.c
3+
--depth 1000
4+
^\[main\.assertion\.[0-9]+\] line [0-9]+ assertion argc == 1: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
The main test is in test.desc; this just checks that the test is executable and
11+
the assertion fails as expected.
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
void f(int y)
2+
{
3+
if(y == 5)
4+
{
5+
__CPROVER_assume(0);
6+
y = 10;
7+
}
8+
}
9+
10+
int main(int argc, char **argv)
11+
{
12+
f(argc);
13+
assert(argc == 1);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE paths-lifo-expected-failure
2+
test.c
3+
--show-vcc
4+
^\{1\} main::argc!0@1#1 = 1$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
This checks that the assertion, argc == 1, is unguarded (i.e. is not of the form
10+
'guard => condition'). Such a guard is redundant, but could be added before goto-symex
11+
made sure to restore guards to their state at function entry.
12+
--paths mode is excluded as it currently always accrues large guards as it proceeds through execution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
test.c
3+
4+
^\[main\.assertion\.[0-9]+\] line [0-9]+ assertion argc == 1: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
The main test is in test.desc; this just checks that the test is executable and
11+
the assertion fails as expected.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FUTURE
2+
test.c
3+
--show-vcc
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
^\{-[0-9]+\} f::y!0@1#[0-9]+ = 10$
8+
--
9+
We also check that no VCC is created for the unreachable code 'y = 10;'

src/goto-symex/call_stack.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ class call_stackt : public std::vector<framet>
2626
return back();
2727
}
2828

29-
framet &new_frame(symex_targett::sourcet calling_location)
29+
framet &
30+
new_frame(symex_targett::sourcet calling_location, const guardt &guard)
3031
{
31-
emplace_back(calling_location);
32+
emplace_back(calling_location, guard);
3233
return back();
3334
}
3435

src/goto-symex/frame.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct framet
2727
std::map<goto_programt::const_targett, goto_state_listt> goto_state_map;
2828
symex_targett::sourcet calling_location;
2929
std::vector<irep_idt> parameter_names;
30-
30+
guardt guard_at_function_start;
3131
goto_programt::const_targett end_of_function;
3232
exprt return_value = nil_exprt();
3333
bool hidden_function = false;
@@ -48,8 +48,9 @@ struct framet
4848

4949
std::unordered_map<irep_idt, loop_infot> loop_iterations;
5050

51-
explicit framet(symex_targett::sourcet _calling_location)
52-
: calling_location(std::move(_calling_location))
51+
framet(symex_targett::sourcet _calling_location, const guardt &state_guard)
52+
: calling_location(std::move(_calling_location)),
53+
guard_at_function_start(state_guard)
5354
{
5455
}
5556
};

src/goto-symex/goto_symex_state.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ goto_symex_statet::goto_symex_statet(
4444
fresh_l2_name_provider(fresh_l2_name_provider)
4545
{
4646
threads.emplace_back(guard_manager);
47-
call_stack().new_frame(source);
47+
call_stack().new_frame(source, guardt(true_exprt(), manager));
4848
}
4949

5050
goto_symex_statet::~goto_symex_statet()=default;

src/goto-symex/symex_function_call.cpp

+30-5
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,10 @@ 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
261+
// Rule out this path:
262+
symex_assume_l2(state, false_exprt());
263+
// Disable processing instructions until we next encounter one reachable
264+
// without passing this instruction:
262265
state.guard.add(false_exprt());
263266
}
264267

@@ -303,7 +306,7 @@ void goto_symext::symex_function_call_code(
303306

304307
// produce a new frame
305308
PRECONDITION(!state.call_stack().empty());
306-
framet &frame = state.call_stack().new_frame(state.source);
309+
framet &frame = state.call_stack().new_frame(state.source, state.guard);
307310

308311
// preserve locality of local variables
309312
locality(identifier, state, path_storage, goto_function, ns);
@@ -332,8 +335,10 @@ void goto_symext::symex_function_call_code(
332335
}
333336

334337
/// pop one call frame
335-
static void
336-
pop_frame(goto_symext::statet &state, const path_storaget &path_storage)
338+
static void pop_frame(
339+
goto_symext::statet &state,
340+
const path_storaget &path_storage,
341+
bool doing_path_exploration)
337342
{
338343
PRECONDITION(!state.call_stack().empty());
339344

@@ -347,6 +352,26 @@ pop_frame(goto_symext::statet &state, const path_storaget &path_storage)
347352
// restore L1 renaming
348353
state.level1.restore_from(frame.old_level1);
349354

355+
// If the program is multi-threaded then the state guard is used to
356+
// accumulate assumptions (in symex_assume_l2) and must be left alone.
357+
// If however it is single-threaded then we should restore the guard, as the
358+
// guard coming out of the function may be more complex (e.g. if the callee
359+
// was { if(x) __CPROVER_assume(false); } then the guard may still be `!x`),
360+
// but at this point all control-flow paths have either converged or been
361+
// proven unviable, so we can stop specifying the callee's constraints when
362+
// we generate an assumption or VCC.
363+
364+
// If the guard is false, *this* path is unviable and we shouldn't discard
365+
// that knowledge. If we're doing path exploration then we do
366+
// tail-duplication, and we actually *are* in a more-restricted context
367+
// than we were when the function began.
368+
if(
369+
state.threads.size() == 1 && !state.guard.is_false() &&
370+
!doing_path_exploration)
371+
{
372+
state.guard = frame.guard_at_function_start;
373+
}
374+
350375
symex_renaming_levelt::viewt view;
351376
state.get_level2().current_names.get_view(view);
352377

@@ -387,7 +412,7 @@ void goto_symext::symex_end_of_function(statet &state)
387412
state.guard.as_expr(), state.source.function_id, state.source, hidden);
388413

389414
// then get rid of the frame
390-
pop_frame(state, path_storage);
415+
pop_frame(state, path_storage, symex_config.doing_path_exploration);
391416
}
392417

393418
/// Preserves locality of parameters of a given function by applying L1

src/goto-symex/symex_goto.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -795,15 +795,16 @@ void goto_symext::loop_bound_exceeded(
795795
negated_cond,
796796
"unwinding assertion loop " + std::to_string(loop_number),
797797
state);
798+
}
799+
800+
// generate unwinding assumption, unless we permit partial loops
801+
symex_assume_l2(state, negated_cond);
798802

803+
if(symex_config.unwinding_assertions)
804+
{
799805
// add to state guard to prevent further assignments
800806
state.guard.add(negated_cond);
801807
}
802-
else
803-
{
804-
// generate unwinding assumption, unless we permit partial loops
805-
symex_assume_l2(state, negated_cond);
806-
}
807808
}
808809
}
809810

src/goto-symex/symex_main.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,13 @@ void goto_symext::execute_next_instruction(
528528

529529
// depth exceeded?
530530
if(symex_config.max_depth != 0 && state.depth > symex_config.max_depth)
531+
{
532+
// Rule out this path:
533+
symex_assume_l2(state, false_exprt());
534+
// Disable processing instructions until we next encounter one reachable
535+
// without passing this instruction:
531536
state.guard.add(false_exprt());
537+
}
532538
state.depth++;
533539

534540
// actually do instruction

0 commit comments

Comments
 (0)