Skip to content

Commit 964964f

Browse files
committed
Add counterexample-guided loop assigns synthesis
Implement the functionality described below. Motivation --- This loop assigns synthesizer use the idea counter-example-guided synthesis (CEGIS) to synthesize loop assigns targets. It is based on the same CEGIS framework as the loop invariant synthesizer w ith the following difference. Cause Loop ID --- In the loop invariant synthesizer, we say a loop is a cause loop if the violation is dependent on the loop's havoc instructions. Because invariant clauses affect the havoced values which could flow into variables outside the loop. In the loop assigns synthesizer, we say a loop is a cause loop if the violation is in the loop. Because assigns clauses only affect the CAR of its own loop body. Synthesizer --- For an assignable violation with checked target `T`, the new assigns target will be `T`. If `T` is a non-constant target, e.g., `ptr[i]` with `i` also an assigns target, we widen it to `__CPROVER_POINTER_OBJECT(T)`. We add the new target to cause loop one by one starting from the most-inner one. Until the assignable violation is resolved.
1 parent 50a795e commit 964964f

14 files changed

+355
-73
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
#define SIZE 8
5+
6+
struct blob
7+
{
8+
char *data;
9+
};
10+
11+
void main()
12+
{
13+
struct blob *b = malloc(sizeof(struct blob));
14+
b->data = malloc(SIZE);
15+
16+
b->data[5] = 0;
17+
unsigned result = 0;
18+
19+
unsigned arr[3] = {0, 0, 0};
20+
21+
for(unsigned int i = 0; i < SIZE; i++)
22+
// clang-format off
23+
// __CPROVER_assigns(i, result)
24+
// clang-format on
25+
{
26+
result += 1;
27+
}
28+
29+
for(unsigned int i = 0; i < SIZE; i++)
30+
// clang-format off
31+
// __CPROVER_assigns(i, arr[0])
32+
// clang-format on
33+
{
34+
arr[0] += 1;
35+
}
36+
37+
for(unsigned j = 0; j < SIZE; j++)
38+
// __CPROVER_assigns(j, __CPROVER_POINTER_OBJECT(b->data))
39+
{
40+
for(unsigned i = 0; i < SIZE; i++)
41+
// clang-format off
42+
// __CPROVER_assigns(i, j, __CPROVER_POINTER_OBJECT(b->data))
43+
// clang-format on
44+
{
45+
b->data[i] = 1;
46+
}
47+
}
48+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
main.c
3+
--pointer-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
7+
^\[main.assigns.\d+\] .* Check that i is assignable: SUCCESS$
8+
^\[main.assigns.\d+\] .* Check that j is assignable: SUCCESS$
9+
^\[main.assigns.\d+\] .* Check that result is assignable: SUCCESS$
10+
^\[main.assigns.\d+\] .* Check that b->data\[(.*)i\] is assignable: SUCCESS$
11+
^\[main.assigns.\d+\] .* Check that arr\[(.*)0\] is assignable: SUCCESS$
12+
^\[main.\d+\] .* Check that loop invariant is preserved: SUCCESS$
13+
^VERIFICATION SUCCESSFUL$
14+
--
15+
--
16+
This test is a variation of contracts/loop_assigns-01.
17+
It shows that we can synthesize loop assigns pointer_object(b->data) that
18+
cannot be inferred by goto-instrument.

src/goto-instrument/contracts/contracts.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,10 +1458,10 @@ void code_contractst::apply_loop_contracts(
14581458
continue;
14591459

14601460
// Store loop number for
1461-
// ASSIGN ENTERED_LOOP = TRUE
1461+
// ASSIGN ENTERED_LOOP = true
1462+
// ASSIGN ENTERED_LOOP = false
14621463
if(
1463-
is_assignment_to_instrumented_variable(it_instr, ENTERED_LOOP) &&
1464-
it_instr->assign_rhs() == true_exprt())
1464+
is_transformed_loop_end(it_instr) || is_transformed_loop_head(it_instr))
14651465
{
14661466
const auto &assign_lhs =
14671467
expr_try_dynamic_cast<symbol_exprt>(it_instr->assign_lhs());

src/goto-instrument/contracts/instrument_spec_assigns.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,15 @@ void instrument_spec_assignst::inclusion_check_assertion(
708708
comment += " is assignable";
709709
source_location.set_comment(comment);
710710

711-
dest.add(goto_programt::make_assertion(
711+
goto_programt::instructiont to_add = goto_programt::make_assertion(
712712
inclusion_check_full(car, allow_null_lhs, include_stack_allocated),
713-
source_location));
713+
source_location);
714+
// Record what target is checked.
715+
auto &checked_assigns =
716+
static_cast<exprt &>(to_add.condition_nonconst().add(ID_checked_assigns));
717+
checked_assigns = car.target();
718+
719+
dest.add(std::move(to_add));
714720
}
715721

716722
exprt instrument_spec_assignst::inclusion_check_single(

src/goto-instrument/contracts/utils.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,19 @@ void generate_history_variables_initialization(
448448
program.destructive_append(history);
449449
}
450450

451+
bool is_transformed_loop_head(const goto_programt::const_targett &target)
452+
{
453+
// The head of a transformed loop is
454+
// ASSIGN entered_loop = false
455+
if(!is_assignment_to_instrumented_variable(target, ENTERED_LOOP))
456+
return false;
457+
458+
return target->assign_rhs() == false_exprt();
459+
}
460+
451461
bool is_transformed_loop_end(const goto_programt::const_targett &target)
452462
{
453-
// The end of the loop end of transformed loop is
463+
// The end of a transformed loop is
454464
// ASSIGN entered_loop = true
455465
if(!is_assignment_to_instrumented_variable(target, ENTERED_LOOP))
456466
return false;

src/goto-instrument/contracts/utils.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,10 @@ void generate_history_variables_initialization(
210210
const irep_idt &mode,
211211
goto_programt &program);
212212

213-
/// Return true if `target` is the loop end of some transformed code.
213+
/// Return true if `target` is the head of some transformed loop.
214+
bool is_transformed_loop_head(const goto_programt::const_targett &target);
215+
216+
/// Return true if `target` is the end of some transformed loop.
214217
bool is_transformed_loop_end(const goto_programt::const_targett &target);
215218

216219
/// Return true if `target` is an assignment to an instrumented variable with

src/goto-synthesizer/cegis_verifier.cpp

Lines changed: 110 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Author: Qinheping Hu
3232
#include <goto-checker/all_properties_verifier_with_trace_storage.h>
3333
#include <goto-checker/multi_path_symex_checker.h>
3434
#include <goto-instrument/contracts/contracts.h>
35+
#include <goto-instrument/contracts/instrument_spec_assigns.h>
3536
#include <goto-instrument/contracts/utils.h>
3637
#include <goto-instrument/havoc_utils.h>
3738
#include <langapi/language_util.h>
@@ -93,11 +94,45 @@ optionst cegis_verifiert::get_options()
9394
return options;
9495
}
9596

96-
optionalt<loop_idt> cegis_verifiert::get_cause_loop_id(
97+
std::list<loop_idt>
98+
cegis_verifiert::get_cause_loop_id_for_assigns(const goto_tracet &goto_trace)
99+
{
100+
std::list<loop_idt> result;
101+
102+
// We say a loop is the cause loop of an assignable-violation if the inclusion
103+
// check is in the loop.
104+
105+
// So we check what loops the last step of the trace is in.
106+
// Transformed loop head:
107+
// ASSIGN entered_loop = false
108+
// Transformed loop end:
109+
// ASSIGN entered_loop = true
110+
for(const auto &step : goto_trace.steps)
111+
{
112+
if(is_transformed_loop_head(step.pc))
113+
{
114+
result.push_front(
115+
loop_idt(step.function_id, original_loop_number_map[step.pc]));
116+
continue;
117+
}
118+
119+
if(is_transformed_loop_end(step.pc))
120+
{
121+
const loop_idt loop_id(
122+
step.function_id, original_loop_number_map[step.pc]);
123+
INVARIANT(result.front() == loop_id, "Leaving a loop we havn't entered.");
124+
result.pop_front();
125+
}
126+
}
127+
INVARIANT(!result.empty(), "The assignable violation is not in a loop.");
128+
return result;
129+
}
130+
131+
std::list<loop_idt> cegis_verifiert::get_cause_loop_id(
97132
const goto_tracet &goto_trace,
98133
const goto_programt::const_targett violation)
99134
{
100-
optionalt<loop_idt> result;
135+
std::list<loop_idt> result;
101136

102137
// build the dependence graph
103138
const namespacet ns(goto_model.symbol_table);
@@ -165,7 +200,8 @@ optionalt<loop_idt> cegis_verifiert::get_cause_loop_id(
165200
// if it is dependent on the loop havoc.
166201
if(reachable_set.count(dependence_graph[from].get_node_id()))
167202
{
168-
result = loop_idt(step.function_id, original_loop_number_map[step.pc]);
203+
result.push_back(
204+
loop_idt(step.function_id, original_loop_number_map[step.pc]));
169205
return result;
170206
}
171207
}
@@ -434,9 +470,12 @@ optionalt<cext> cegis_verifiert::verify()
434470
original_functions[fun_entry.first].copy_from(fun_entry.second.body);
435471
}
436472

437-
// Annotate the candidates tot the goto_model for checking.
473+
// Annotate the candidates to the goto_model for checking.
438474
annotate_invariants(invariant_candidates, goto_model);
439475

476+
// Annotate assigns
477+
annotate_assigns(assigns_map, goto_model);
478+
440479
// Control verbosity.
441480
// We allow non-error output message only when verbosity is set to at least 9.
442481
const unsigned original_verbosity = log.get_message_handler().get_verbosity();
@@ -496,14 +535,18 @@ optionalt<cext> cegis_verifiert::verify()
496535
continue;
497536

498537
first_violation = property_it.first;
499-
exprt violated_predicate = property_it.second.pc->condition();
538+
const auto &trace = checker->get_traces()[property_it.first];
539+
const exprt &violated_predicate = property_it.second.pc->condition();
500540

501-
// The pointer checked in the null-pointer-check violation.
541+
// The pointer checked in the null-pointer-check violation and assignable
542+
// violation.
502543
exprt checked_pointer = true_exprt();
503544

504545
// Type of the violation
505546
cext::violation_typet violation_type = cext::violation_typet::cex_other;
506547

548+
// Decide the violation type from the description of violation
549+
507550
// The violation is a pointer OOB check.
508551
if((property_it.second.description.find(
509552
"dereference failure: pointer outside object bounds in") !=
@@ -537,49 +580,89 @@ optionalt<cext> cegis_verifiert::verify()
537580
violation_type = cext::violation_typet::cex_not_hold_upon_entry;
538581
}
539582

583+
// The violation is an assignable check.
584+
if(property_it.second.description.find("assignable") != std::string::npos)
585+
{
586+
violation_type = cext::violation_typet::cex_assignable;
587+
}
588+
589+
// Compute the cause loop---the loop for which we synthesize loop contracts,
590+
// and the counterexample.
591+
592+
// If the violation is an assignable check, we synthesize assigns targets.
593+
// In the case, cause loops are all loops the violation is in. We keep
594+
// adding the new assigns target to the most-inner loop that does not
595+
// contain the new target until the assignable violation is resolved.
596+
597+
// For other cases, we synthesize loop invariant clauses. We synthesize
598+
// invariants for one loop at a time. So we return only the first cause loop
599+
// although there can be multiple ones.
600+
601+
log.debug() << "Start to compute cause loop ids." << messaget::eom;
602+
540603
// The loop which could be the cause of the violation.
541-
// We say a loop is the cause loop if the violated predicate is dependent
542-
// upon the write set of the loop.
543-
optionalt<loop_idt> cause_loop_id = get_cause_loop_id(
544-
checker->get_traces()[property_it.first], property_it.second.pc);
604+
std::list<loop_idt> cause_loop_ids;
605+
// Decide whether the violation is in the cause loop.
606+
bool is_violation_in_loop;
607+
608+
// Doing assigns-synthesis or invariant-synthesis
609+
if(violation_type == cext::violation_typet::cex_assignable)
610+
{
611+
is_violation_in_loop = true;
612+
613+
checked_pointer = static_cast<const exprt &>(
614+
property_it.second.pc->condition().find(ID_checked_assigns));
615+
cause_loop_ids = get_cause_loop_id_for_assigns(trace);
616+
cext result(violation_type);
617+
result.cause_loop_ids = cause_loop_ids;
618+
result.checked_pointer = checked_pointer;
619+
restore_functions();
620+
return result;
621+
}
622+
623+
// We construct the full counterexample only for violations other than
624+
// assignable checks.
545625

546-
if(!cause_loop_id.has_value())
626+
// Although there can be multiple cause loop ids. We only synthesize
627+
// loop invariants for the first cause loop.
628+
cause_loop_ids = get_cause_loop_id(trace, property_it.second.pc);
629+
630+
if(cause_loop_ids.empty())
547631
{
548-
log.debug() << "No cause loop found!\n";
632+
log.debug() << "No cause loop found!" << messaget::eom;
549633
restore_functions();
550634

551635
return cext(violation_type);
552636
}
553637

554-
log.debug() << "Found cause loop with function id: "
555-
<< cause_loop_id.value().function_id
556-
<< ", and loop number: " << cause_loop_id.value().loop_number
557-
<< "\n";
558-
559-
// Decide whether the violation is in the cause loop.
560-
bool violation_in_loop = is_instruction_in_transfomed_loop(
561-
cause_loop_id.value(),
562-
goto_model.get_goto_function(cause_loop_id.value().function_id),
638+
is_violation_in_loop = is_instruction_in_transfomed_loop(
639+
cause_loop_ids.front(),
640+
goto_model.get_goto_function(cause_loop_ids.front().function_id),
563641
property_it.second.pc->location_number);
564642

643+
log.debug() << "Found cause loop with function id: "
644+
<< cause_loop_ids.front().function_id
645+
<< ", and loop number: " << cause_loop_ids.front().loop_number
646+
<< messaget::eom;
647+
565648
// We always strengthen in_clause if the violation is
566649
// invariant-not-preserved.
567650
if(violation_type == cext::violation_typet::cex_not_preserved)
568-
violation_in_loop = true;
651+
is_violation_in_loop = true;
569652

570653
restore_functions();
571654

572655
auto return_cex = build_cex(
573-
checker->get_traces()[property_it.first],
656+
trace,
574657
get_loop_head(
575-
cause_loop_id.value().loop_number,
658+
cause_loop_ids.front().loop_number,
576659
goto_model.goto_functions
577-
.function_map[cause_loop_id.value().function_id])
660+
.function_map[cause_loop_ids.front().function_id])
578661
->source_location());
579662
return_cex.violated_predicate = violated_predicate;
580-
return_cex.cause_loop_id = cause_loop_id;
663+
return_cex.cause_loop_ids = cause_loop_ids;
581664
return_cex.checked_pointer = checked_pointer;
582-
return_cex.is_violation_in_loop = violation_in_loop;
665+
return_cex.is_violation_in_loop = is_violation_in_loop;
583666
return_cex.violation_type = violation_type;
584667

585668
return return_cex;

0 commit comments

Comments
 (0)