Skip to content

Commit ae7d464

Browse files
committed
Synthesize loop assigns before loop invariants.
Loop invariants synthesis is dependent on loop-assigns clauses. So we don't target non assignable-violations until all assignable-violations are fixed.
1 parent 942e797 commit ae7d464

File tree

5 files changed

+205
-118
lines changed

5 files changed

+205
-118
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <stdlib.h>
2+
#define SIZE 80
3+
4+
void main()
5+
{
6+
unsigned long len;
7+
__CPROVER_assume(len <= SIZE);
8+
__CPROVER_assume(len >= 8);
9+
const char *i = malloc(len);
10+
const char *end = i + len;
11+
char s = 0;
12+
13+
while(i != end)
14+
{
15+
s = *i++;
16+
}
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--pointer-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[main.pointer\_dereference.\d+\] .* SUCCESS$
7+
^\[main.\d+\] .* Check loop invariant before entry: SUCCESS$
8+
^\[main.assigns.\d+\] .* Check that i is assignable: SUCCESS$
9+
^VERIFICATION SUCCESSFUL$
10+
--
11+
--
12+
Check whether assigns clauses are synthesize before invariant clauses.

src/goto-synthesizer/cegis_verifier.cpp

Lines changed: 168 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,44 @@ static bool contains_symbol_prefix(const exprt &expr, const std::string &prefix)
5353
return false;
5454
}
5555

56+
static const exprt &
57+
get_checked_pointer_from_null_pointer_check(const exprt &violation)
58+
{
59+
// A NULL-pointer check is the negation of an equation between the checked
60+
// pointer and a NULL pointer.
61+
// ! (POINTER_OBJECT(NULL) == POINTER_OBJECT(ptr))
62+
const equal_exprt &equal_expr = to_equal_expr(to_not_expr(violation).op());
63+
64+
const exprt &lhs_pointer_object = equal_expr.lhs();
65+
const exprt &rhs_pointer_object = equal_expr.rhs();
66+
INVARIANT(
67+
lhs_pointer_object.id() == ID_pointer_object &&
68+
rhs_pointer_object.id() == ID_pointer_object,
69+
"Null pointer check is an equation between two pointer objects of "
70+
"pointer-type operands.");
71+
72+
const exprt &lhs_pointer = lhs_pointer_object.operands()[0];
73+
const exprt &rhs_pointer = rhs_pointer_object.operands()[0];
74+
75+
// NULL == ptr
76+
if(
77+
can_cast_expr<constant_exprt>(lhs_pointer) &&
78+
is_null_pointer(*expr_try_dynamic_cast<constant_exprt>(lhs_pointer)))
79+
{
80+
return rhs_pointer;
81+
}
82+
// ptr == NULL
83+
else if(
84+
can_cast_expr<constant_exprt>(rhs_pointer) &&
85+
is_null_pointer(*expr_try_dynamic_cast<constant_exprt>(rhs_pointer)))
86+
{
87+
return lhs_pointer;
88+
}
89+
90+
// Not a equation with NULL on one side.
91+
UNREACHABLE;
92+
}
93+
5694
optionst cegis_verifiert::get_options()
5795
{
5896
optionst options;
@@ -94,6 +132,45 @@ optionst cegis_verifiert::get_options()
94132
return options;
95133
}
96134

135+
cext::violation_typet
136+
cegis_verifiert::extract_violation_type(const std::string &description)
137+
{
138+
cext::violation_typet violation_type = cext::violation_typet::cex_other;
139+
140+
// The violation is a pointer OOB check.
141+
if((description.find(
142+
"dereference failure: pointer outside object bounds in") !=
143+
std::string::npos))
144+
{
145+
violation_type = cext::violation_typet::cex_out_of_boundary;
146+
}
147+
148+
// The violation is a null pointer check.
149+
if(description.find("pointer NULL") != std::string::npos)
150+
{
151+
violation_type = cext::violation_typet::cex_null_pointer;
152+
}
153+
154+
// The violation is a loop-invariant-preservation check.
155+
if(description.find("preserved") != std::string::npos)
156+
{
157+
violation_type = cext::violation_typet::cex_not_preserved;
158+
}
159+
160+
// The violation is a loop-invariant-preservation check.
161+
if(description.find("invariant before entry") != std::string::npos)
162+
{
163+
violation_type = cext::violation_typet::cex_not_hold_upon_entry;
164+
}
165+
166+
// The violation is an assignable check.
167+
if(description.find("assignable") != std::string::npos)
168+
{
169+
violation_type = cext::violation_typet::cex_assignable;
170+
}
171+
return violation_type;
172+
}
173+
97174
std::list<loop_idt>
98175
cegis_verifiert::get_cause_loop_id_for_assigns(const goto_tracet &goto_trace)
99176
{
@@ -586,143 +663,118 @@ optionalt<cext> cegis_verifiert::verify()
586663
}
587664

588665
properties = checker->get_properties();
589-
// Find the violation and construct counterexample from its trace.
590-
for(const auto &property_it : properties)
666+
bool target_violation_found = false;
667+
auto target_violation_info = properties.begin()->second;
668+
669+
// Find target violation---the violation we want to fix next.
670+
// A target violation is an assignable violation or the first violation that
671+
// is not assignable violation.
672+
for(const auto &property : properties)
591673
{
592-
if(property_it.second.status != property_statust::FAIL)
674+
if(property.second.status != property_statust::FAIL)
593675
continue;
594676

595-
// Store the violation that we want to fix with synthesized
596-
// assigns/invariant.
597-
first_violation = property_it.first;
598-
599-
// Type of the violation
600-
cext::violation_typet violation_type = cext::violation_typet::cex_other;
601-
602-
// Decide the violation type from the description of violation
603-
604-
// The violation is a pointer OOB check.
605-
if((property_it.second.description.find(
606-
"dereference failure: pointer outside object bounds in") !=
607-
std::string::npos))
677+
// assignable violation found
678+
if(property.second.description.find("assignable") != std::string::npos)
608679
{
609-
violation_type = cext::violation_typet::cex_out_of_boundary;
610-
}
611-
612-
// The violation is a null pointer check.
613-
if(property_it.second.description.find("pointer NULL") != std::string::npos)
614-
{
615-
violation_type = cext::violation_typet::cex_null_pointer;
616-
}
617-
618-
// The violation is a loop-invariant-preservation check.
619-
if(property_it.second.description.find("preserved") != std::string::npos)
620-
{
621-
violation_type = cext::violation_typet::cex_not_preserved;
622-
}
623-
624-
// The violation is a loop-invariant-preservation check.
625-
if(
626-
property_it.second.description.find("invariant before entry") !=
627-
std::string::npos)
628-
{
629-
violation_type = cext::violation_typet::cex_not_hold_upon_entry;
680+
target_violation = property.first;
681+
target_violation_info = property.second;
682+
break;
630683
}
631684

632-
// The violation is an assignable check.
633-
if(property_it.second.description.find("assignable") != std::string::npos)
685+
// Store the violation that we want to fix with synthesized
686+
// assigns/invariant.
687+
if(!target_violation_found)
634688
{
635-
violation_type = cext::violation_typet::cex_assignable;
689+
target_violation = property.first;
690+
target_violation_info = property.second;
691+
target_violation_found = true;
636692
}
693+
}
637694

638-
// Compute the cause loop---the loop for which we synthesize loop contracts,
639-
// and the counterexample.
640-
641-
// If the violation is an assignable check, we synthesize assigns targets.
642-
// In the case, cause loops are all loops the violation is in. We keep
643-
// adding the new assigns target to the most-inner loop that does not
644-
// contain the new target until the assignable violation is resolved.
645-
646-
// For other cases, we synthesize loop invariant clauses. We synthesize
647-
// invariants for one loop at a time. So we return only the first cause loop
648-
// although there can be multiple ones.
695+
// Decide the violation type from the description of violation
696+
cext::violation_typet violation_type =
697+
extract_violation_type(target_violation_info.description);
649698

650-
log.debug() << "Start to compute cause loop ids." << messaget::eom;
699+
// Compute the cause loop---the loop for which we synthesize loop contracts,
700+
// and the counterexample.
651701

652-
const auto &trace = checker->get_traces()[property_it.first];
702+
// If the violation is an assignable check, we synthesize assigns targets.
703+
// In the case, cause loops are all loops the violation is in. We keep
704+
// adding the new assigns target to the most-inner loop that does not
705+
// contain the new target until the assignable violation is resolved.
653706

654-
// Doing assigns-synthesis or invariant-synthesis
655-
if(violation_type == cext::violation_typet::cex_assignable)
656-
{
657-
cext result(violation_type);
658-
result.cause_loop_ids = get_cause_loop_id_for_assigns(trace);
659-
result.checked_pointer = static_cast<const exprt &>(
660-
property_it.second.pc->condition().find(ID_checked_assigns));
661-
restore_functions();
662-
return result;
663-
}
707+
// For other cases, we synthesize loop invariant clauses. We synthesize
708+
// invariants for one loop at a time. So we return only the first cause loop
709+
// although there can be multiple ones.
664710

665-
// We construct the full counterexample only for violations other than
666-
// assignable checks.
711+
log.debug() << "Start to compute cause loop ids." << messaget::eom;
667712

668-
// Although there can be multiple cause loop ids. We only synthesize
669-
// loop invariants for the first cause loop.
670-
const std::list<loop_idt> cause_loop_ids =
671-
get_cause_loop_id(trace, property_it.second.pc);
713+
const auto &trace = checker->get_traces()[target_violation];
714+
// Doing assigns-synthesis or invariant-synthesis
715+
if(violation_type == cext::violation_typet::cex_assignable)
716+
{
717+
cext result(violation_type);
718+
result.cause_loop_ids = get_cause_loop_id_for_assigns(trace);
719+
result.checked_pointer = static_cast<const exprt &>(
720+
target_violation_info.pc->condition().find(ID_checked_assigns));
721+
restore_functions();
722+
return result;
723+
}
672724

673-
if(cause_loop_ids.empty())
674-
{
675-
log.debug() << "No cause loop found!" << messaget::eom;
676-
restore_functions();
725+
// We construct the full counterexample only for violations other than
726+
// assignable checks.
677727

678-
return cext(violation_type);
679-
}
728+
// Although there can be multiple cause loop ids. We only synthesize
729+
// loop invariants for the first cause loop.
730+
const std::list<loop_idt> cause_loop_ids =
731+
get_cause_loop_id(trace, target_violation_info.pc);
680732

681-
log.debug() << "Found cause loop with function id: "
682-
<< cause_loop_ids.front().function_id
683-
<< ", and loop number: " << cause_loop_ids.front().loop_number
684-
<< messaget::eom;
733+
if(cause_loop_ids.empty())
734+
{
735+
log.debug() << "No cause loop found!" << messaget::eom;
736+
restore_functions();
685737

686-
auto violation_location = cext::violation_locationt::in_loop;
687-
// We always strengthen in_clause if the violation is
688-
// invariant-not-preserved.
689-
if(violation_type != cext::violation_typet::cex_not_preserved)
690-
{
691-
// Get the location of the violation
692-
violation_location = get_violation_location(
693-
cause_loop_ids.front(),
694-
goto_model.get_goto_function(cause_loop_ids.front().function_id),
695-
property_it.second.pc->location_number);
696-
}
738+
return cext(violation_type);
739+
}
697740

698-
restore_functions();
741+
log.debug() << "Found cause loop with function id: "
742+
<< cause_loop_ids.front().function_id
743+
<< ", and loop number: " << cause_loop_ids.front().loop_number
744+
<< messaget::eom;
699745

700-
auto return_cex = build_cex(
701-
trace,
702-
get_loop_head(
703-
cause_loop_ids.front().loop_number,
704-
goto_model.goto_functions
705-
.function_map[cause_loop_ids.front().function_id])
706-
->source_location());
707-
return_cex.violated_predicate = property_it.second.pc->condition();
708-
return_cex.cause_loop_ids = cause_loop_ids;
709-
return_cex.violation_location = violation_location;
710-
return_cex.violation_type = violation_type;
711-
712-
// The pointer checked in the null-pointer-check violation.
713-
if(violation_type == cext::violation_typet::cex_null_pointer)
714-
{
715-
return_cex.checked_pointer = property_it.second.pc->condition()
716-
.operands()[0]
717-
.operands()[1]
718-
.operands()[0];
719-
INVARIANT(
720-
return_cex.checked_pointer.id() == ID_symbol,
721-
"Checking pointer symbol");
722-
}
746+
auto violation_location = cext::violation_locationt::in_loop;
747+
// We always strengthen in_clause if the violation is
748+
// invariant-not-preserved.
749+
if(violation_type != cext::violation_typet::cex_not_preserved)
750+
{
751+
// Get the location of the violation
752+
violation_location = get_violation_location(
753+
cause_loop_ids.front(),
754+
goto_model.get_goto_function(cause_loop_ids.front().function_id),
755+
target_violation_info.pc->location_number);
756+
}
723757

724-
return return_cex;
758+
restore_functions();
759+
760+
auto return_cex = build_cex(
761+
trace,
762+
get_loop_head(
763+
cause_loop_ids.front().loop_number,
764+
goto_model.goto_functions
765+
.function_map[cause_loop_ids.front().function_id])
766+
->source_location());
767+
return_cex.violated_predicate = target_violation_info.pc->condition();
768+
return_cex.cause_loop_ids = cause_loop_ids;
769+
return_cex.violation_location = violation_location;
770+
return_cex.violation_type = violation_type;
771+
772+
// The pointer checked in the null-pointer-check violation.
773+
if(violation_type == cext::violation_typet::cex_null_pointer)
774+
{
775+
return_cex.checked_pointer = get_checked_pointer_from_null_pointer_check(
776+
target_violation_info.pc->condition());
725777
}
726778

727-
UNREACHABLE;
779+
return return_cex;
728780
}

src/goto-synthesizer/cegis_verifier.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class cegis_verifiert
117117

118118
/// Result counterexample.
119119
propertiest properties;
120-
irep_idt first_violation;
120+
irep_idt target_violation;
121121

122122
protected:
123123
// Get the options same as using CBMC api without any flag, and
@@ -137,6 +137,9 @@ class cegis_verifiert
137137
std::list<loop_idt>
138138
get_cause_loop_id_for_assigns(const goto_tracet &goto_trace);
139139

140+
// Extract the violation type from violation description.
141+
cext::violation_typet extract_violation_type(const std::string &description);
142+
140143
// Compute the location of the violation.
141144
cext::violation_locationt get_violation_location(
142145
const loop_idt &loop_id,

src/goto-synthesizer/enumerative_loop_contracts_synthesizer.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ void enumerative_loop_contracts_synthesizert::init_candidates()
9494

9595
loop_idt new_id(function_p.first, loop_end->loop_number);
9696

97+
log.debug() << "Initialize candidates for the loop at "
98+
<< loop_end->source_location() << messaget::eom;
99+
97100
// we only synthesize invariants and assigns for unannotated loops
98101
if(loop_end->condition().find(ID_C_spec_loop_invariant).is_nil())
99102
{
@@ -372,7 +375,7 @@ invariant_mapt enumerative_loop_contracts_synthesizert::synthesize_all()
372375
new_invariant_clause = synthesize_strengthening_clause(
373376
terminal_symbols,
374377
return_cex->cause_loop_ids.front(),
375-
verifier.first_violation);
378+
verifier.target_violation);
376379
break;
377380

378381
case cext::violation_typet::cex_assignable:

0 commit comments

Comments
 (0)