Skip to content

Commit 61240e2

Browse files
lks9yosephinestwn
andcommitted
Feature to --retrace a single control flow path
Adds a feature to retrace and verify a single control flow path. The path can be given using `--retrace` as a string of 0s and 1s. From a very brief evaluation test, it seems to be faster than bounded loop unwinding. Only that it might be challenging to find a path to retrace. The 0s and 1s in the input retrace string follow the goto decisions in cbmc's internal goto program representation. During symbolic execution, files and line numbers for each decision are logged to the console, so the interactive use should be intuitive. Co-authored-by: Yosephine Setiawan <[email protected]>
1 parent 3c915eb commit 61240e2

File tree

9 files changed

+235
-4
lines changed

9 files changed

+235
-4
lines changed

doc/man/cbmc.1

+3
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ explore paths one at a time
366366
\fB\-\-show\-symex\-strategies\fR
367367
list strategies for use with \fB\-\-paths\fR
368368
.TP
369+
\fB\-\-retrace\fR [trace]
370+
follow a single control flow path, given by a trace string of 0s and 1s
371+
.TP
369372
\fB\-\-show\-goto\-symex\-steps\fR
370373
show which steps symex travels, includes
371374
diagnostic information

doc/man/jbmc.1

+3
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ explore paths one at a time
281281
\fB\-\-show\-symex\-strategies\fR
282282
list strategies for use with \fB\-\-paths\fR
283283
.TP
284+
\fB\-\-retrace\fR [trace]
285+
follow a single control flow path, given by a trace string of 0s and 1s
286+
.TP
284287
\fB\-\-show\-goto\-symex\-steps\fR
285288
show which steps symex travels, includes
286289
diagnostic information

src/cbmc/cbmc_parse_options.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,21 @@ void cbmc_parse_optionst::get_command_line_options(optionst &options)
168168
exit(CPROVER_EXIT_USAGE_ERROR);
169169
}
170170

171+
if(cmdline.isset("retrace"))
172+
{
173+
auto retrace_input = cmdline.get_value("retrace");
174+
options.set_option("retrace", retrace_input);
175+
176+
// check retrace input
177+
for(auto &c : retrace_input)
178+
if(c != '0' && c != '1')
179+
{
180+
log.error() << "--retrace input string must only contain 0s and 1s"
181+
<< messaget::eom;
182+
exit(CPROVER_EXIT_USAGE_ERROR);
183+
}
184+
}
185+
171186
// We want to warn the user that if we are using standard checks (that enables
172187
// unwinding-assertions) and we did not disable them manually.
173188
if(

src/goto-checker/bmc_util.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,14 @@ void setup_symex(
186186
if(!ns.lookup(INITIALIZE_FUNCTION, init_symbol))
187187
symex.language_mode = init_symbol->mode;
188188

189-
msg.status() << "Starting Bounded Model Checking" << messaget::eom;
189+
if(options.is_set("retrace"))
190+
{
191+
msg.status() << "Starting Retrace Model Checking" << messaget::eom;
192+
}
193+
else
194+
{
195+
msg.status() << "Starting Bounded Model Checking" << messaget::eom;
196+
}
190197

191198
symex.last_source_location.make_nil();
192199

src/goto-checker/bmc_util.h

+3
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ void run_property_decider(
182182
"(partial-loops)" \
183183
"(paths):" \
184184
"(show-symex-strategies)" \
185+
"(retrace):" \
185186
"(depth):" \
186187
"(max-field-sensitivity-array-size):" \
187188
"(no-array-field-sensitivity)" \
@@ -197,6 +198,8 @@ void run_property_decider(
197198
#define HELP_BMC \
198199
" {y--paths} [strategy] \t explore paths one at a time\n" \
199200
" {y--show-symex-strategies} \t list strategies for use with {y--paths}\n" \
201+
" {y--retrace} [trace] \t follow a single control flow path, given by a " \
202+
"trace string of 0s and 1s\n" \
200203
" {y--show-goto-symex-steps} \t show which steps symex travels, includes " \
201204
"diagnostic information\n" \
202205
" {y--show-points-to-sets} \t show points-to sets for pointer dereference. " \

src/goto-symex/goto_symex.h

+3
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ class goto_symext
318318
/// Symbolically execute a GOTO instruction in the context of unreachable code
319319
/// \param state: Symbolic execution state for current instruction
320320
void symex_unreachable_goto(statet &state);
321+
/// Symbolically execute a GOTO along a control flow retrace input path
322+
/// \param state: Symbolic execution state for current instruction
323+
void symex_goto_retrace(statet &state);
321324
/// Symbolically execute a SET_RETURN_VALUE instruction
322325
/// \param state: Symbolic execution state for current instruction
323326
/// \param return_value: The value to be returned

src/goto-symex/symex_config.h

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ struct symex_configt final
2424

2525
bool doing_path_exploration;
2626

27+
bool retracing;
28+
std::string retrace_input;
29+
2730
bool allow_pointer_unsoundness;
2831

2932
bool constant_propagation;

src/goto-symex/symex_goto.cpp

+192-2
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,7 @@ void goto_symext::symex_goto(statet &state)
260260
DATA_INVARIANT(
261261
instruction.targets.size() == 1, "no support for non-deterministic gotos");
262262

263-
goto_programt::const_targett goto_target=
264-
instruction.get_target();
263+
goto_programt::const_targett goto_target = instruction.get_target();
265264

266265
const bool backward = instruction.is_backwards_goto();
267266

@@ -614,6 +613,197 @@ void goto_symext::symex_unreachable_goto(statet &state)
614613
symex_transition(state);
615614
}
616615

616+
void goto_symext::symex_goto_retrace(statet &state)
617+
{
618+
// get guard, targets as in symex_goto()
619+
const goto_programt::instructiont &instruction = *state.source.pc;
620+
621+
exprt new_guard = clean_expr(instruction.condition(), state, false);
622+
623+
renamedt<exprt, L2> renamed_guard = state.rename(std::move(new_guard), ns);
624+
renamed_guard = try_evaluate_pointer_comparisons(
625+
std::move(renamed_guard), state.value_set, language_mode, ns);
626+
if(symex_config.simplify_opt)
627+
renamed_guard.simplify(ns);
628+
new_guard = renamed_guard.get();
629+
630+
target.goto_instruction(state.guard.as_expr(), renamed_guard, state.source);
631+
632+
DATA_INVARIANT(
633+
!instruction.targets.empty(), "goto should have at least one target");
634+
635+
// we only do deterministic gotos for now
636+
DATA_INVARIANT(
637+
instruction.targets.size() == 1, "no support for non-deterministic gotos");
638+
639+
goto_programt::const_targett goto_target = instruction.get_target();
640+
641+
const bool backward = instruction.is_backwards_goto();
642+
643+
goto_programt::const_targett next_instruction = state.source.pc;
644+
next_instruction++;
645+
646+
// goto or next depends on input trace from user
647+
bool choose_goto;
648+
static size_t retrace_index = 0;
649+
if(retrace_index < symex_config.retrace_input.size())
650+
{
651+
choose_goto = symex_config.retrace_input[retrace_index] == '1';
652+
}
653+
else
654+
{
655+
choose_goto = false;
656+
}
657+
retrace_index++;
658+
659+
// print goto choice
660+
log.conditional_output(
661+
log.status(),
662+
[this, &state, &goto_target, &next_instruction, choose_goto](
663+
messaget::mstreamt &mstream)
664+
{
665+
source_locationt cur = state.source.pc->source_location();
666+
source_locationt goto_dest = goto_target->source_location();
667+
source_locationt next_dest = next_instruction->source_location();
668+
source_locationt t_dest = choose_goto ? goto_dest : next_dest;
669+
source_locationt nt_dest = choose_goto ? next_dest : goto_dest;
670+
671+
auto cur_file = cur.get_file();
672+
if(cur_file.empty())
673+
cur_file = "<unknown>";
674+
auto t_file = t_dest.get_file();
675+
if(t_file.empty())
676+
t_file = "<unknown>";
677+
auto nt_file = nt_dest.get_file();
678+
if(nt_file.empty())
679+
nt_file = "<unknown>";
680+
681+
// print nothing when files are the same
682+
if(cur_file == t_file)
683+
t_file = "";
684+
if(cur_file == nt_file)
685+
nt_file = "";
686+
687+
auto cur_line = cur.get_line();
688+
if(cur_line.empty())
689+
cur_line = "?";
690+
auto t_line = t_dest.get_line();
691+
if(t_line.empty())
692+
t_line = "?";
693+
auto nt_line = nt_dest.get_line();
694+
if(nt_line.empty())
695+
nt_line = "?";
696+
697+
std::string head = symex_config.retrace_input;
698+
// add 0s in case input trace was shorter
699+
head.resize(retrace_index - 1, '0');
700+
std::string decision = choose_goto ? "1" : "0";
701+
702+
std::string step = choose_goto ? " goto " : " next ";
703+
704+
std::string padding_1 =
705+
cur_line.size() < 3 ? std::string(3 - cur_line.size(), ' ') : "";
706+
std::string padding_2 =
707+
t_line.size() < 3 ? std::string(3 - t_line.size(), ' ') : "";
708+
709+
mstream << "Retrace "
710+
<< head << messaget::bold << decision << messaget::reset
711+
<< " at " << cur_file << ":" << cur_line << padding_1
712+
<< step << t_file << ":" << t_line << padding_2
713+
<< " (not " << nt_file << ":" << nt_line << ")"
714+
<< messaget::eom;
715+
});
716+
717+
// warn when not following unconditional goto
718+
if(new_guard.is_true() && !choose_goto)
719+
{
720+
log.result() << "Retrace input "
721+
<< messaget::red << "inconsistent" << messaget::reset
722+
<< ": 0/next although guard is true!"
723+
<< log.eom;
724+
}
725+
else if(new_guard.is_false() && choose_goto)
726+
{
727+
log.result() << "Retrace input "
728+
<< messaget::red << "inconsistent" << messaget::reset
729+
<< ": 1/goto although guard is false!"
730+
<< log.eom;
731+
}
732+
733+
symex_targett::sourcet original_source = state.source;
734+
goto_programt::const_targett new_state_pc;
735+
736+
if(choose_goto)
737+
{
738+
// Jump to the jump target if the input is '1'
739+
new_state_pc = goto_target;
740+
symex_transition(state, new_state_pc, backward);
741+
}
742+
else
743+
{
744+
// Jump to the next instruction
745+
new_state_pc = state.source.pc;
746+
new_state_pc++;
747+
symex_transition(state);
748+
}
749+
750+
// produce new guard symbol
751+
exprt guard_expr;
752+
753+
if(
754+
new_guard.id() == ID_symbol ||
755+
(new_guard.id() == ID_not && to_not_expr(new_guard).op().id() == ID_symbol))
756+
{
757+
guard_expr = new_guard;
758+
}
759+
else
760+
{
761+
symbol_exprt guard_symbol_expr =
762+
symbol_exprt(statet::guard_identifier(), bool_typet());
763+
exprt new_rhs = boolean_negate(new_guard);
764+
765+
ssa_exprt new_lhs =
766+
state.rename_ssa<L1>(ssa_exprt{guard_symbol_expr}, ns).get();
767+
new_lhs =
768+
state.assignment(std::move(new_lhs), new_rhs, ns, true, false).get();
769+
770+
guardt guard{true_exprt{}, guard_manager};
771+
772+
log.conditional_output(
773+
log.debug(),
774+
[this, &new_lhs](messaget::mstreamt &mstream)
775+
{
776+
mstream << "Assignment to " << new_lhs.get_identifier()
777+
<< " [" << pointer_offset_bits(new_lhs.type(), ns).value_or(0)
778+
<< " bits]"
779+
<< messaget::eom;
780+
});
781+
782+
target.assignment(
783+
guard.as_expr(),
784+
new_lhs,
785+
new_lhs,
786+
guard_symbol_expr,
787+
new_rhs,
788+
original_source,
789+
symex_targett::assignment_typet::GUARD);
790+
791+
guard_expr = state.rename(boolean_negate(guard_symbol_expr), ns).get();
792+
}
793+
794+
if(choose_goto)
795+
{
796+
symex_assume_l2(state, guard_expr);
797+
state.guard.add(guard_expr);
798+
}
799+
else
800+
{
801+
symex_assume_l2(state, boolean_negate(guard_expr));
802+
state.guard.add(boolean_negate(guard_expr));
803+
}
804+
return;
805+
}
806+
617807
bool goto_symext::check_break(const irep_idt &loop_id, unsigned unwind)
618808
{
619809
// dummy implementation

src/goto-symex/symex_main.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Author: Daniel Kroening, [email protected]
3030
symex_configt::symex_configt(const optionst &options)
3131
: max_depth(options.get_unsigned_int_option("depth")),
3232
doing_path_exploration(options.is_set("paths")),
33+
retracing(options.is_set("retrace")),
34+
retrace_input(options.get_option("retrace")),
3335
allow_pointer_unsoundness(
3436
options.get_bool_option("allow-pointer-unsoundness")),
3537
constant_propagation(options.get_bool_option("propagation")),
@@ -641,7 +643,9 @@ void goto_symext::execute_next_instruction(
641643
break;
642644

643645
case GOTO:
644-
if(state.reachable)
646+
if(symex_config.retracing)
647+
symex_goto_retrace(state);
648+
else if(state.reachable)
645649
symex_goto(state);
646650
else
647651
symex_unreachable_goto(state);

0 commit comments

Comments
 (0)