Skip to content

Commit ff62607

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 ff62607

File tree

5 files changed

+194
-118
lines changed

5 files changed

+194
-118
lines changed
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+
}
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

+157-116
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,34 @@ 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 pointer_object_exprt &lhs_pointer_object =
65+
to_pointer_object_expr(equal_expr.lhs());
66+
const pointer_object_exprt &rhs_pointer_object =
67+
to_pointer_object_expr(equal_expr.rhs());
68+
69+
const exprt &lhs_pointer = lhs_pointer_object.operands()[0];
70+
const exprt &rhs_pointer = rhs_pointer_object.operands()[0];
71+
72+
// NULL == ptr
73+
if(
74+
can_cast_expr<constant_exprt>(lhs_pointer) &&
75+
is_null_pointer(*expr_try_dynamic_cast<constant_exprt>(lhs_pointer)))
76+
{
77+
return rhs_pointer;
78+
}
79+
80+
// Not a equation with NULL on one side.
81+
UNREACHABLE;
82+
}
83+
5684
optionst cegis_verifiert::get_options()
5785
{
5886
optionst options;
@@ -94,6 +122,44 @@ optionst cegis_verifiert::get_options()
94122
return options;
95123
}
96124

125+
cext::violation_typet
126+
cegis_verifiert::extract_violation_type(const std::string &description)
127+
{
128+
// The violation is a pointer OOB check.
129+
if((description.find(
130+
"dereference failure: pointer outside object bounds in") !=
131+
std::string::npos))
132+
{
133+
return cext::violation_typet::cex_out_of_boundary;
134+
}
135+
136+
// The violation is a null pointer check.
137+
if(description.find("pointer NULL") != std::string::npos)
138+
{
139+
return cext::violation_typet::cex_null_pointer;
140+
}
141+
142+
// The violation is a loop-invariant-preservation check.
143+
if(description.find("preserved") != std::string::npos)
144+
{
145+
return cext::violation_typet::cex_not_preserved;
146+
}
147+
148+
// The violation is a loop-invariant-preservation check.
149+
if(description.find("invariant before entry") != std::string::npos)
150+
{
151+
return cext::violation_typet::cex_not_hold_upon_entry;
152+
}
153+
154+
// The violation is an assignable check.
155+
if(description.find("assignable") != std::string::npos)
156+
{
157+
return cext::violation_typet::cex_assignable;
158+
}
159+
160+
return cext::violation_typet::cex_other;
161+
}
162+
97163
std::list<loop_idt>
98164
cegis_verifiert::get_cause_loop_id_for_assigns(const goto_tracet &goto_trace)
99165
{
@@ -586,143 +652,118 @@ optionalt<cext> cegis_verifiert::verify()
586652
}
587653

588654
properties = checker->get_properties();
589-
// Find the violation and construct counterexample from its trace.
590-
for(const auto &property_it : properties)
655+
bool target_violation_found = false;
656+
auto target_violation_info = properties.begin()->second;
657+
658+
// Find target violation---the violation we want to fix next.
659+
// A target violation is an assignable violation or the first violation that
660+
// is not assignable violation.
661+
for(const auto &property : properties)
591662
{
592-
if(property_it.second.status != property_statust::FAIL)
663+
if(property.second.status != property_statust::FAIL)
593664
continue;
594665

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))
608-
{
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)
666+
// assignable violation found
667+
if(property.second.description.find("assignable") != std::string::npos)
614668
{
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;
669+
target_violation = property.first;
670+
target_violation_info = property.second;
671+
break;
630672
}
631673

632-
// The violation is an assignable check.
633-
if(property_it.second.description.find("assignable") != std::string::npos)
674+
// Store the violation that we want to fix with synthesized
675+
// assigns/invariant.
676+
if(!target_violation_found)
634677
{
635-
violation_type = cext::violation_typet::cex_assignable;
678+
target_violation = property.first;
679+
target_violation_info = property.second;
680+
target_violation_found = true;
636681
}
682+
}
637683

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.
684+
// Decide the violation type from the description of violation
685+
cext::violation_typet violation_type =
686+
extract_violation_type(target_violation_info.description);
649687

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

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

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-
}
696+
// For other cases, we synthesize loop invariant clauses. We synthesize
697+
// invariants for one loop at a time. So we return only the first cause loop
698+
// although there can be multiple ones.
664699

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

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);
702+
const auto &trace = checker->get_traces()[target_violation];
703+
// Doing assigns-synthesis or invariant-synthesis
704+
if(violation_type == cext::violation_typet::cex_assignable)
705+
{
706+
cext result(violation_type);
707+
result.cause_loop_ids = get_cause_loop_id_for_assigns(trace);
708+
result.checked_pointer = static_cast<const exprt &>(
709+
target_violation_info.pc->condition().find(ID_checked_assigns));
710+
restore_functions();
711+
return result;
712+
}
672713

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

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

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;
722+
if(cause_loop_ids.empty())
723+
{
724+
log.debug() << "No cause loop found!" << messaget::eom;
725+
restore_functions();
685726

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-
}
727+
return cext(violation_type);
728+
}
697729

698-
restore_functions();
730+
log.debug() << "Found cause loop with function id: "
731+
<< cause_loop_ids.front().function_id
732+
<< ", and loop number: " << cause_loop_ids.front().loop_number
733+
<< messaget::eom;
699734

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-
}
735+
auto violation_location = cext::violation_locationt::in_loop;
736+
// We always strengthen in_clause if the violation is
737+
// invariant-not-preserved.
738+
if(violation_type != cext::violation_typet::cex_not_preserved)
739+
{
740+
// Get the location of the violation
741+
violation_location = get_violation_location(
742+
cause_loop_ids.front(),
743+
goto_model.get_goto_function(cause_loop_ids.front().function_id),
744+
target_violation_info.pc->location_number);
745+
}
723746

724-
return return_cex;
747+
restore_functions();
748+
749+
auto return_cex = build_cex(
750+
trace,
751+
get_loop_head(
752+
cause_loop_ids.front().loop_number,
753+
goto_model.goto_functions
754+
.function_map[cause_loop_ids.front().function_id])
755+
->source_location());
756+
return_cex.violated_predicate = target_violation_info.pc->condition();
757+
return_cex.cause_loop_ids = cause_loop_ids;
758+
return_cex.violation_location = violation_location;
759+
return_cex.violation_type = violation_type;
760+
761+
// The pointer checked in the null-pointer-check violation.
762+
if(violation_type == cext::violation_typet::cex_null_pointer)
763+
{
764+
return_cex.checked_pointer = get_checked_pointer_from_null_pointer_check(
765+
target_violation_info.pc->condition());
725766
}
726767

727-
UNREACHABLE;
768+
return return_cex;
728769
}

src/goto-synthesizer/cegis_verifier.h

+4-1
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

+4-1
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)