Skip to content

Commit dbb5163

Browse files
committed
Fix evaluation of decreases clause on loops
`__CPROVER_decreases` clauses on loops were evaluated outside of loop iterations (before checking the loop guard), which was buggy. We fix this behavior in this commit. The clause is now evaluated inside the loop block (after the loop guard is satisfied).
1 parent 78d23cf commit dbb5163

File tree

4 files changed

+84
-46
lines changed

4 files changed

+84
-46
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
int main()
2+
{
3+
unsigned start, max;
4+
unsigned i = start;
5+
6+
while(i < max)
7+
// clang-format off
8+
__CPROVER_loop_invariant(
9+
(start > max && i == start) ||
10+
(start <= i && i <= max)
11+
)
12+
__CPROVER_decreases(max - i)
13+
// clang-format on
14+
{
15+
i++;
16+
}
17+
18+
return 0;
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts _ --unsigned-overflow-check
4+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
5+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
6+
^\[main.3\] .* Check decreases clause on loop iteration: SUCCESS$
7+
^\[main.overflow.1\] .* arithmetic overflow on unsigned - in max - i: SUCCESS$
8+
^\[main.overflow.3\] .* arithmetic overflow on unsigned - in max - i: SUCCESS$
9+
^\[main.overflow.2\] .* arithmetic overflow on unsigned \+ in i \+ 1u: SUCCESS$
10+
^VERIFICATION SUCCESSFUL$
11+
^EXIT=0$
12+
^SIGNAL=0$
13+
--
14+
--
15+
This test checks that the decreases clause is evaluated only within the loop iteration,
16+
not outside of it (before the loop guard).
17+
The `main.overflow.1` check would fail if the decreases clause `(max - i)` is evaluated
18+
before the loop guard is satisfied. This would occur when `start > max` and therefore
19+
`i > max` after assuming the invariant.

src/goto-instrument/contracts/contracts.cpp

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ static exprt create_lexicographic_less_than(
5555
// In fact, the last element is unnecessary, so we do not create it.
5656
exprt::operandst equality_conjunctions(lhs.size());
5757
equality_conjunctions[0] = binary_relation_exprt(lhs[0], ID_equal, rhs[0]);
58-
for(unsigned int i = 1; i < equality_conjunctions.size() - 1; i++)
58+
for(size_t i = 1; i < equality_conjunctions.size() - 1; i++)
5959
{
6060
binary_relation_exprt component_i_equality{lhs[i], ID_equal, rhs[i]};
6161
equality_conjunctions[i] =
@@ -71,7 +71,7 @@ static exprt create_lexicographic_less_than(
7171
exprt::operandst lexicographic_individual_comparisons(lhs.size());
7272
lexicographic_individual_comparisons[0] =
7373
binary_relation_exprt(lhs[0], ID_lt, rhs[0]);
74-
for(unsigned int i = 1; i < lexicographic_individual_comparisons.size(); i++)
74+
for(size_t i = 1; i < lexicographic_individual_comparisons.size(); i++)
7575
{
7676
binary_relation_exprt component_i_less_than{lhs[i], ID_lt, rhs[i]};
7777
lexicographic_individual_comparisons[i] =
@@ -80,10 +80,20 @@ static exprt create_lexicographic_less_than(
8080
return disjunction(lexicographic_individual_comparisons);
8181
}
8282

83+
static void insert_before_swap_and_advance(
84+
goto_programt &program,
85+
goto_programt::targett &target,
86+
goto_programt &payload)
87+
{
88+
const auto offset = payload.instructions.size();
89+
program.insert_before_swap(target, payload);
90+
std::advance(target, offset);
91+
}
92+
8393
void code_contractst::check_apply_loop_contracts(
8494
goto_functionst::goto_functiont &goto_function,
8595
const local_may_aliast &local_may_alias,
86-
const goto_programt::targett loop_head,
96+
goto_programt::targett loop_head,
8797
const loopt &loop,
8898
const irep_idt &mode)
8999
{
@@ -138,8 +148,8 @@ void code_contractst::check_apply_loop_contracts(
138148
// H: assert(invariant);
139149
// havoc;
140150
// assume(invariant);
141-
// old_decreases_value = decreases_clause(current_environment);
142151
// if(guard) goto E:
152+
// old_decreases_value = decreases_clause(current_environment);
143153
// loop;
144154
// new_decreases_value = decreases_clause(current_environment);
145155
// assert(invariant);
@@ -191,26 +201,37 @@ void code_contractst::check_apply_loop_contracts(
191201
// decreases clause's value before and after the loop
192202
for(const auto &clause : decreases_clause.operands())
193203
{
194-
old_decreases_vars.push_back(
204+
const auto old_decreases_var =
195205
new_tmp_symbol(clause.type(), loop_head->source_location, mode)
196-
.symbol_expr());
197-
new_decreases_vars.push_back(
206+
.symbol_expr();
207+
havoc_code.add(
208+
goto_programt::make_decl(old_decreases_var, loop_head->source_location));
209+
old_decreases_vars.push_back(old_decreases_var);
210+
211+
const auto new_decreases_var =
198212
new_tmp_symbol(clause.type(), loop_head->source_location, mode)
199-
.symbol_expr());
213+
.symbol_expr();
214+
havoc_code.add(
215+
goto_programt::make_decl(new_decreases_var, loop_head->source_location));
216+
new_decreases_vars.push_back(new_decreases_var);
200217
}
201218

202-
if(!decreases_clause.is_nil())
219+
// non-deterministically skip the loop if it is a do-while loop
220+
if(!loop_head->is_goto())
203221
{
204-
// Generate: declarations of the temporary variables that stores the
205-
// multidimensional decreases clause's value before the loop
206-
for(const auto &old_temp_var : old_decreases_vars)
207-
{
208-
havoc_code.add(
209-
goto_programt::make_decl(old_temp_var, loop_head->source_location));
210-
}
222+
havoc_code.add(goto_programt::make_goto(
223+
loop_end,
224+
side_effect_expr_nondett(bool_typet(), loop_head->source_location)));
225+
}
226+
227+
// Now havoc at the loop head.
228+
// Use insert_before_swap to preserve jumps to loop head.
229+
insert_before_swap_and_advance(goto_function.body, loop_head, havoc_code);
211230

212-
// Generate: assignments to store the multidimensional decreases clause's
213-
// value before the loop
231+
// Generate: assignments to store the multidimensional decreases clause's
232+
// value before the loop
233+
if(!decreases_clause.is_nil())
234+
{
214235
for(size_t i = 0; i < old_decreases_vars.size(); i++)
215236
{
216237
code_assignt old_decreases_assignment{old_decreases_vars[i],
@@ -219,20 +240,10 @@ void code_contractst::check_apply_loop_contracts(
219240
loop_head->source_location;
220241
converter.goto_convert(old_decreases_assignment, havoc_code, mode);
221242
}
222-
}
223243

224-
// non-deterministically skip the loop if it is a do-while loop
225-
if(!loop_head->is_goto())
226-
{
227-
havoc_code.add(goto_programt::make_goto(
228-
loop_end,
229-
side_effect_expr_nondett(bool_typet(), loop_head->source_location)));
244+
goto_function.body.destructive_insert(std::next(loop_head), havoc_code);
230245
}
231246

232-
// Now havoc at the loop head.
233-
// Use insert_before_swap to preserve jumps to loop head.
234-
goto_function.body.insert_before_swap(loop_head, havoc_code);
235-
236247
// Generate: assert(invariant) just after the loop exits
237248
// We use a block scope to create a temporary assertion,
238249
// and immediately convert it to goto instructions.
@@ -244,19 +255,10 @@ void code_contractst::check_apply_loop_contracts(
244255
"Check that loop invariant is preserved");
245256
}
246257

258+
// Generate: assignments to store the multidimensional decreases clause's
259+
// value after one iteration of the loop
247260
if(!decreases_clause.is_nil())
248261
{
249-
// Generate: declarations of temporary variables that stores the
250-
// multidimensional decreases clause's value after one arbitrary iteration
251-
// of the loop
252-
for(const auto &new_temp_var : new_decreases_vars)
253-
{
254-
havoc_code.add(
255-
goto_programt::make_decl(new_temp_var, loop_head->source_location));
256-
}
257-
258-
// Generate: assignments to store the multidimensional decreases clause's
259-
// value after one iteration of the loop
260262
for(size_t i = 0; i < new_decreases_vars.size(); i++)
261263
{
262264
code_assignt new_decreases_assignment{new_decreases_vars[i],
@@ -269,8 +271,8 @@ void code_contractst::check_apply_loop_contracts(
269271
// Generate: assertion that the multidimensional decreases clause's value
270272
// after the loop is smaller than the value before the loop.
271273
// Here, we use the lexicographic order.
272-
code_assertt monotonic_decreasing_assertion{create_lexicographic_less_than(
273-
new_decreases_vars, old_decreases_vars)};
274+
code_assertt monotonic_decreasing_assertion{
275+
create_lexicographic_less_than(new_decreases_vars, old_decreases_vars)};
274276
monotonic_decreasing_assertion.add_source_location() =
275277
loop_head->source_location;
276278
converter.goto_convert(monotonic_decreasing_assertion, havoc_code, mode);
@@ -287,9 +289,7 @@ void code_contractst::check_apply_loop_contracts(
287289
}
288290
}
289291

290-
auto offset = havoc_code.instructions.size();
291-
goto_function.body.insert_before_swap(loop_end, havoc_code);
292-
std::advance(loop_end, offset);
292+
insert_before_swap_and_advance(goto_function.body, loop_end, havoc_code);
293293

294294
// change the back edge into assume(false) or assume(guard)
295295
loop_end->targets.clear();

src/goto-instrument/contracts/contracts.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class code_contractst
123123
void check_apply_loop_contracts(
124124
goto_functionst::goto_functiont &goto_function,
125125
const local_may_aliast &local_may_alias,
126-
const goto_programt::targett loop_head,
126+
goto_programt::targett loop_head,
127127
const loopt &loop,
128128
const irep_idt &mode);
129129

0 commit comments

Comments
 (0)