Skip to content

Commit 1949497

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 00e01f3 commit 1949497

File tree

8 files changed

+165
-44
lines changed

8 files changed

+165
-44
lines changed

src/cbmc/bmc.cpp

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ safety_checkert::resultt bmct::execute(
377377
const goto_functionst &goto_functions =
378378
goto_model.get_goto_functions();
379379

380+
if(symex.should_pause_symex)
381+
return safety_checkert::resultt::PAUSED;
382+
380383
// This provides the driver program the opportunity to do things like a
381384
// symbol-table or goto-functions dump instead of actually running the
382385
// checker, like show-vcc except driver-program specific.
@@ -637,9 +640,10 @@ int bmct::do_language_agnostic_bmc(
637640
std::function<void(bmct &, const symbol_tablet &)> driver_configure_bmc,
638641
std::function<bool(void)> callback_after_symex)
639642
{
643+
safety_checkert::resultt final_result = safety_checkert::resultt::UNKNOWN;
644+
safety_checkert::resultt tmp_result = safety_checkert::resultt::UNKNOWN;
640645
const symbol_tablet &symbol_table = model.get_symbol_table();
641646
message_handlert &mh = message.get_message_handler();
642-
safety_checkert::resultt result;
643647
std::unique_ptr<path_storaget> worklist =
644648
path_storaget::make(path_storaget::disciplinet::FIFO);
645649
try
@@ -654,7 +658,9 @@ int bmct::do_language_agnostic_bmc(
654658
bmc.set_ui(ui);
655659
if(driver_configure_bmc)
656660
driver_configure_bmc(bmc, symbol_table);
657-
result = bmc.run(model);
661+
tmp_result = bmc.run(model);
662+
if(tmp_result != safety_checkert::resultt::PAUSED)
663+
final_result = tmp_result;
658664
}
659665
INVARIANT(
660666
opts.get_bool_option("paths") || worklist->empty(),
@@ -663,8 +669,8 @@ int bmct::do_language_agnostic_bmc(
663669
std::to_string(worklist->size()) + " unexplored branches.");
664670

665671
// When model checking, the bmc.run() above will already have explored
666-
// the entire program, and result contains the verification result. The
667-
// worklist (containing paths that have not yet been explored) is thus
672+
// the entire program, and final_result contains the verification result.
673+
// The worklist (containing paths that have not yet been explored) is thus
668674
// empty, and we don't enter this loop.
669675
//
670676
// When doing path exploration, there will be some saved paths left to
@@ -679,10 +685,11 @@ int bmct::do_language_agnostic_bmc(
679685

680686
while(!worklist->empty())
681687
{
682-
message.status() << "___________________________\n"
683-
<< "Starting new path (" << worklist->size()
684-
<< " to go)\n"
685-
<< message.eom;
688+
if(tmp_result != safety_checkert::resultt::PAUSED)
689+
message.status() << "___________________________\n"
690+
<< "Starting new path (" << worklist->size()
691+
<< " to go)\n"
692+
<< message.eom;
686693
cbmc_solverst solvers(opts, symbol_table, message.get_message_handler());
687694
solvers.set_ui(ui);
688695
std::unique_ptr<cbmc_solverst::solvert> cbmc_solver;
@@ -700,7 +707,9 @@ int bmct::do_language_agnostic_bmc(
700707
callback_after_symex);
701708
if(driver_configure_bmc)
702709
driver_configure_bmc(pe, symbol_table);
703-
result &= pe.run(model);
710+
tmp_result = pe.run(model);
711+
if(tmp_result != safety_checkert::resultt::PAUSED)
712+
final_result &= tmp_result;
704713
worklist->pop();
705714
}
706715
}
@@ -720,14 +729,18 @@ int bmct::do_language_agnostic_bmc(
720729
throw std::current_exception();
721730
}
722731

723-
switch(result)
732+
switch(final_result)
724733
{
725734
case safety_checkert::resultt::SAFE:
726735
return CPROVER_EXIT_VERIFICATION_SAFE;
727736
case safety_checkert::resultt::UNSAFE:
728737
return CPROVER_EXIT_VERIFICATION_UNSAFE;
729738
case safety_checkert::resultt::ERROR:
730739
return CPROVER_EXIT_INTERNAL_ERROR;
740+
case safety_checkert::resultt::UNKNOWN:
741+
return CPROVER_EXIT_INTERNAL_ERROR;
742+
case safety_checkert::resultt::PAUSED:
743+
UNREACHABLE;
731744
}
732745
UNREACHABLE;
733746
}

src/goto-programs/safety_checker.h

Lines changed: 43 additions & 1 deletion
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

Lines changed: 11 additions & 1 deletion
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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,13 @@ class goto_symex_statet final
373373
incremental_dirtyt dirty;
374374

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

379385
private:

src/goto-symex/path_storage.cpp

Lines changed: 7 additions & 4 deletions
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

Lines changed: 32 additions & 8 deletions
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

Lines changed: 34 additions & 17 deletions
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)