Skip to content

Commit 685937a

Browse files
committed
Path exploration pushes both successors onto queue
When doing path exploration and encountering a conditional goto instruction, both successors of the goto (the next instruction, and the target) are pushed onto the path exploration workqueue. This allows the symbolic execution caller to have complete control over which path to execute next. Prior to this commit, symex would push one successor onto the workqueue and continue executing the path of the other successor until the path was terminated. This commit introduces the possibility that symbolic execution pauses without reaching the end of a path, and needs to be resumed by the caller. Thus, new safety checker results are introduced (SUSPENDED and UNKNOWN); the former signals to the caller of symex that symex has not reached the end of a branch, and therefore no safety check has been performed. The (currently only) path exploration strategy still pops paths in the order that they were pushed. This behaviour means that the paths that it pops will always alternate between the next instruction of a goto, and the target of that same goto. The test suite is updated to reflect this strategy.
1 parent 2a86fb4 commit 685937a

File tree

8 files changed

+165
-44
lines changed

8 files changed

+165
-44
lines changed

src/cbmc/bmc.cpp

+23-10
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ safety_checkert::resultt bmct::execute(
342342
const goto_functionst &goto_functions =
343343
goto_model.get_goto_functions();
344344

345+
if(symex.should_pause_symex)
346+
return safety_checkert::resultt::PAUSED;
347+
345348
// This provides the driver program the opportunity to do things like a
346349
// symbol-table or goto-functions dump instead of actually running the
347350
// checker, like show-vcc except driver-program specific.
@@ -602,9 +605,10 @@ int bmct::do_language_agnostic_bmc(
602605
std::function<void(bmct &, const symbol_tablet &)> driver_configure_bmc,
603606
std::function<bool(void)> callback_after_symex)
604607
{
608+
safety_checkert::resultt final_result = safety_checkert::resultt::UNKNOWN;
609+
safety_checkert::resultt tmp_result = safety_checkert::resultt::UNKNOWN;
605610
const symbol_tablet &symbol_table = model.get_symbol_table();
606611
message_handlert &mh = message.get_message_handler();
607-
safety_checkert::resultt result;
608612
std::unique_ptr<path_storaget> worklist =
609613
path_storaget::make(path_storaget::disciplinet::FIFO);
610614
try
@@ -619,7 +623,9 @@ int bmct::do_language_agnostic_bmc(
619623
bmc.set_ui(ui);
620624
if(driver_configure_bmc)
621625
driver_configure_bmc(bmc, symbol_table);
622-
result = bmc.run(model);
626+
tmp_result = bmc.run(model);
627+
if(tmp_result != safety_checkert::resultt::PAUSED)
628+
final_result = tmp_result;
623629
}
624630
INVARIANT(
625631
opts.get_bool_option("paths") || worklist->empty(),
@@ -628,8 +634,8 @@ int bmct::do_language_agnostic_bmc(
628634
std::to_string(worklist->size()) + " unexplored branches.");
629635

630636
// When model checking, the bmc.run() above will already have explored
631-
// the entire program, and result contains the verification result. The
632-
// worklist (containing paths that have not yet been explored) is thus
637+
// the entire program, and final_result contains the verification result.
638+
// The worklist (containing paths that have not yet been explored) is thus
633639
// empty, and we don't enter this loop.
634640
//
635641
// When doing path exploration, there will be some saved paths left to
@@ -644,10 +650,11 @@ int bmct::do_language_agnostic_bmc(
644650

645651
while(!worklist->empty())
646652
{
647-
message.status() << "___________________________\n"
648-
<< "Starting new path (" << worklist->size()
649-
<< " to go)\n"
650-
<< message.eom;
653+
if(tmp_result != safety_checkert::resultt::PAUSED)
654+
message.status() << "___________________________\n"
655+
<< "Starting new path (" << worklist->size()
656+
<< " to go)\n"
657+
<< message.eom;
651658
cbmc_solverst solvers(opts, symbol_table, message.get_message_handler());
652659
solvers.set_ui(ui);
653660
std::unique_ptr<cbmc_solverst::solvert> cbmc_solver;
@@ -665,7 +672,9 @@ int bmct::do_language_agnostic_bmc(
665672
callback_after_symex);
666673
if(driver_configure_bmc)
667674
driver_configure_bmc(pe, symbol_table);
668-
result &= pe.run(model);
675+
tmp_result = pe.run(model);
676+
if(tmp_result != safety_checkert::resultt::PAUSED)
677+
final_result &= tmp_result;
669678
worklist->pop();
670679
}
671680
}
@@ -685,14 +694,18 @@ int bmct::do_language_agnostic_bmc(
685694
throw std::current_exception();
686695
}
687696

688-
switch(result)
697+
switch(final_result)
689698
{
690699
case safety_checkert::resultt::SAFE:
691700
return CPROVER_EXIT_VERIFICATION_SAFE;
692701
case safety_checkert::resultt::UNSAFE:
693702
return CPROVER_EXIT_VERIFICATION_UNSAFE;
694703
case safety_checkert::resultt::ERROR:
695704
return CPROVER_EXIT_INTERNAL_ERROR;
705+
case safety_checkert::resultt::UNKNOWN:
706+
return CPROVER_EXIT_INTERNAL_ERROR;
707+
case safety_checkert::resultt::PAUSED:
708+
UNREACHABLE;
696709
}
697710
UNREACHABLE;
698711
}

src/goto-programs/safety_checker.h

+43-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,23 @@ class safety_checkert:public messaget
3030
const namespacet &_ns,
3131
message_handlert &_message_handler);
3232

33-
enum class resultt { SAFE, UNSAFE, ERROR };
33+
enum class resultt
34+
{
35+
/// No safety properties were violated
36+
SAFE,
37+
/// Some safety properties were violated
38+
UNSAFE,
39+
/// Safety is unknown due to an error during safety checking
40+
ERROR,
41+
/// Symbolic execution has been suspended due to encountering a GOTO while
42+
/// doing path exploration; the symex state has been saved, and symex should
43+
/// be resumed by the caller.
44+
PAUSED,
45+
/// We haven't yet assigned a safety check result to this object. A value of
46+
/// UNKNOWN can be used to initialize a resultt object, and that object may
47+
/// then safely be used with the |= and &= operators.
48+
UNKNOWN
49+
};
3450

3551
// check whether all assertions in goto_functions are safe
3652
// if UNSAFE, then a trace is returned
@@ -47,11 +63,22 @@ class safety_checkert:public messaget
4763
};
4864

4965
/// \brief The worst of two results
66+
///
67+
/// A result of PAUSED means that the safety check has not yet completed,
68+
/// thus it makes no sense to compare it to the result of a completed safety
69+
/// check. Therefore do not pass safety_checkert::resultt:PAUSED as an
70+
/// argument to this function.
5071
inline safety_checkert::resultt &
5172
operator&=(safety_checkert::resultt &a, safety_checkert::resultt const &b)
5273
{
74+
PRECONDITION(
75+
a != safety_checkert::resultt::PAUSED &&
76+
b != safety_checkert::resultt::PAUSED);
5377
switch(a)
5478
{
79+
case safety_checkert::resultt::UNKNOWN:
80+
a = b;
81+
return a;
5582
case safety_checkert::resultt::ERROR:
5683
return a;
5784
case safety_checkert::resultt::SAFE:
@@ -60,16 +87,29 @@ operator&=(safety_checkert::resultt &a, safety_checkert::resultt const &b)
6087
case safety_checkert::resultt::UNSAFE:
6188
a = b == safety_checkert::resultt::ERROR ? b : a;
6289
return a;
90+
case safety_checkert::resultt::PAUSED:
91+
UNREACHABLE;
6392
}
6493
UNREACHABLE;
6594
}
6695

6796
/// \brief The best of two results
97+
///
98+
/// A result of PAUSED means that the safety check has not yet completed,
99+
/// thus it makes no sense to compare it to the result of a completed safety
100+
/// check. Therefore do not pass safety_checkert::resultt:PAUSED as an
101+
/// argument to this function.
68102
inline safety_checkert::resultt &
69103
operator|=(safety_checkert::resultt &a, safety_checkert::resultt const &b)
70104
{
105+
PRECONDITION(
106+
a != safety_checkert::resultt::PAUSED &&
107+
b != safety_checkert::resultt::PAUSED);
71108
switch(a)
72109
{
110+
case safety_checkert::resultt::UNKNOWN:
111+
a = b;
112+
return a;
73113
case safety_checkert::resultt::SAFE:
74114
return a;
75115
case safety_checkert::resultt::ERROR:
@@ -78,6 +118,8 @@ operator|=(safety_checkert::resultt &a, safety_checkert::resultt const &b)
78118
case safety_checkert::resultt::UNSAFE:
79119
a = b == safety_checkert::resultt::SAFE ? b : a;
80120
return a;
121+
case safety_checkert::resultt::PAUSED:
122+
UNREACHABLE;
81123
}
82124
UNREACHABLE;
83125
}

src/goto-symex/goto_symex.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ class goto_symext
5353
const symbol_tablet &outer_symbol_table,
5454
symex_target_equationt &_target,
5555
path_storaget &path_storage)
56-
: total_vccs(0),
56+
: should_pause_symex(false),
57+
total_vccs(0),
5758
remaining_vccs(0),
5859
constant_propagation(true),
5960
language_mode(),
@@ -159,6 +160,15 @@ class goto_symext
159160
goto_programt::const_targett first,
160161
goto_programt::const_targett limit);
161162

163+
/// \brief Have states been pushed onto the workqueue?
164+
///
165+
/// If this flag is set at the end of a symbolic execution run, it means that
166+
/// symex has been paused because we encountered a GOTO instruction while
167+
/// doing path exploration, and thus pushed the successor states of the GOTO
168+
/// onto path_storage. The symbolic execution caller should now choose which
169+
/// successor state to continue executing, and resume symex from that state.
170+
bool should_pause_symex;
171+
162172
protected:
163173
/// Initialise the symbolic execution and the given state with <code>pc</code>
164174
/// as entry point.

src/goto-symex/goto_symex_state.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,13 @@ class goto_symex_statet final
372372
incremental_dirtyt dirty;
373373

374374
goto_programt::const_targett saved_target;
375-
bool has_saved_target;
375+
376+
/// \brief This state is saved, with the PC pointing to the target of a GOTO
377+
bool has_saved_jump_target;
378+
379+
/// \brief This state is saved, with the PC pointing to the next instruction
380+
/// of a GOTO
381+
bool has_saved_next_instruction;
376382
bool saved_target_is_backwards;
377383

378384
private:

src/goto-symex/path_storage.cpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ path_storaget::make(path_storaget::disciplinet discipline)
1919
UNREACHABLE;
2020
}
2121

22-
path_storaget::patht &path_fifot::peek()
22+
path_storaget::patht &path_fifot::private_peek()
2323
{
2424
return paths.front();
2525
}
2626

27-
void path_fifot::push(const path_storaget::patht &bp)
27+
void path_fifot::push(
28+
const path_storaget::patht &next_instruction,
29+
const path_storaget::patht &jump_target)
2830
{
29-
paths.push_back(bp);
31+
paths.push_back(next_instruction);
32+
paths.push_back(jump_target);
3033
}
3134

32-
void path_fifot::pop()
35+
void path_fifot::private_pop()
3336
{
3437
paths.pop_front();
3538
}

src/goto-symex/path_storage.h

+32-8
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,29 @@ class path_storaget
4848
virtual ~path_storaget() = default;
4949

5050
/// \brief Reference to the next path to resume
51-
virtual patht &peek() = 0;
52-
53-
/// \brief Add a path to resume to the storage
54-
virtual void push(const patht &bp) = 0;
51+
patht &peek()
52+
{
53+
PRECONDITION(!empty());
54+
return private_peek();
55+
}
56+
57+
/// \brief Add paths to resume to the storage
58+
///
59+
/// Symbolic execution is suspended when we reach a conditional goto
60+
/// instruction with two successors. Thus, we always add a pair of paths to
61+
/// the path storage.
62+
///
63+
/// \param next_instruction the instruction following the goto instruction
64+
/// \param jump_target the target instruction of the goto instruction
65+
virtual void
66+
push(const patht &next_instruction, const patht &jump_target) = 0;
5567

5668
/// \brief Remove the next path to resume from the storage
57-
virtual void pop() = 0;
69+
void pop()
70+
{
71+
PRECONDITION(!empty());
72+
private_pop();
73+
}
5874

5975
/// \brief How many paths does this storage contain?
6076
virtual std::size_t size() const = 0;
@@ -64,19 +80,27 @@ class path_storaget
6480
{
6581
return size() == 0;
6682
};
83+
84+
private:
85+
// Derived classes should override these methods, allowing the base class to
86+
// enforce preconditions.
87+
virtual patht &private_peek() = 0;
88+
virtual void private_pop() = 0;
6789
};
6890

6991
/// \brief FIFO save queue: paths are resumed in the order that they were saved
7092
class path_fifot : public path_storaget
7193
{
7294
public:
73-
patht &peek() override;
74-
void push(const patht &bp) override;
75-
void pop() override;
95+
void push(const patht &, const patht &) override;
7696
std::size_t size() const override;
7797

7898
protected:
7999
std::list<patht> paths;
100+
101+
private:
102+
patht &private_peek() override;
103+
void private_pop() override;
80104
};
81105

82106
#endif /* CPROVER_GOTO_SYMEX_PATH_STORAGE_H */

src/goto-symex/symex_goto.cpp

+34-17
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void goto_symext::symex_goto(statet &state)
152152
// new_state_pc for later. But if we're executing from a saved state, then
153153
// new_state_pc should be the state that we saved from earlier, so let's
154154
// execute that instead.
155-
if(state.has_saved_target)
155+
if(state.has_saved_jump_target)
156156
{
157157
INVARIANT(
158158
new_state_pc == state.saved_target,
@@ -170,28 +170,45 @@ void goto_symext::symex_goto(statet &state)
170170
new_state_pc = state_pc;
171171
state_pc = tmp;
172172

173-
log.debug() << "Resuming from '" << state_pc->source_location << "'"
174-
<< log.eom;
173+
log.debug() << "Resuming from jump target '" << state_pc->source_location
174+
<< "'" << log.eom;
175+
}
176+
else if(state.has_saved_next_instruction)
177+
{
178+
log.debug() << "Resuming from next instruction '"
179+
<< state_pc->source_location << "'" << log.eom;
175180
}
176181
else if(options.get_bool_option("paths"))
177182
{
178-
// At this point, `state_pc` is the instruction we should execute
179-
// immediately, and `new_state_pc` is the instruction that we should execute
180-
// later (after we've finished exploring this branch). For path-based
181-
// exploration, save `new_state_pc` to the saved state that we will resume
182-
// executing from, so that goto_symex::symex_goto() knows that we've already
183-
// explored the branch starting from `state_pc` when it is later called at
184-
// this branch.
185-
path_storaget::patht branch_point(target, state);
186-
branch_point.state.saved_target = new_state_pc;
187-
branch_point.state.has_saved_target = true;
183+
// We should save both the instruction after this goto, and the target of
184+
// the goto.
185+
186+
path_storaget::patht next_instruction(target, state);
187+
next_instruction.state.saved_target = state_pc;
188+
next_instruction.state.has_saved_next_instruction = true;
189+
next_instruction.state.saved_target_is_backwards = !forward;
190+
191+
path_storaget::patht jump_target(target, state);
192+
jump_target.state.saved_target = new_state_pc;
193+
jump_target.state.has_saved_jump_target = true;
188194
// `forward` tells us where the branch we're _currently_ executing is
189195
// pointing to; this needs to be inverted for the branch that we're saving,
190196
// so let its truth value for `backwards` be the same as ours for `forward`.
191-
branch_point.state.saved_target_is_backwards = forward;
192-
path_storage.push(branch_point);
193-
log.debug() << "Saving '" << new_state_pc->source_location << "'"
197+
jump_target.state.saved_target_is_backwards = forward;
198+
199+
log.debug() << "Saving next instruction '"
200+
<< next_instruction.state.saved_target->source_location << "'"
201+
<< log.eom;
202+
log.debug() << "Saving jump target '"
203+
<< jump_target.state.saved_target->source_location << "'"
194204
<< log.eom;
205+
path_storage.push(next_instruction, jump_target);
206+
207+
// It is now up to the caller of symex to decide which path to continue
208+
// executing. Signal to the caller that states have been pushed (therefore
209+
// symex has not yet completed and must be resumed), and bail out.
210+
should_pause_symex = true;
211+
return;
195212
}
196213

197214
// put into state-queue
@@ -242,7 +259,7 @@ void goto_symext::symex_goto(statet &state)
242259
state.rename(guard_expr, ns);
243260
}
244261

245-
if(state.has_saved_target)
262+
if(state.has_saved_jump_target)
246263
{
247264
if(forward)
248265
state.guard.add(guard_expr);

0 commit comments

Comments
 (0)