Skip to content

Commit a0ba201

Browse files
author
Long Pham
committed
Full implementation of loop variants
1 parent 99ccbcd commit a0ba201

File tree

21 files changed

+287
-33
lines changed

21 files changed

+287
-33
lines changed

regression/contracts/invar_check_multiple_loops/test.desc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ main.c
55
^SIGNAL=0$
66
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
77
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
8-
^\[main.3\] .* Check loop invariant before entry: SUCCESS$
9-
^\[main.4\] .* Check that loop invariant is preserved: SUCCESS$
8+
^\[main.3\] .* Compare the old and new variant values: SUCCESS$
9+
^\[main.4\] .* Check loop invariant before entry: SUCCESS$
10+
^\[main.5\] .* Check that loop invariant is preserved: SUCCESS$
11+
^\[main.6\] .* Compare the old and new variant values: SUCCESS$
1012
^\[main.assertion.1\] .* assertion x == y \+ 2 \* n: SUCCESS$
1113
^VERIFICATION SUCCESSFUL$
1214
--

regression/contracts/invar_check_nested_loops/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ int main()
77

88
for(int i = 0; i < n; ++i)
99
// clang-format off
10-
__CPROVER_loop_invariant(i <= n && s == i)
10+
__CPROVER_loop_invariant(0 <= i && i <= n && s == i)
1111
__CPROVER_decreases(n - i)
1212
// clang-format on
1313
{

regression/contracts/invar_check_nested_loops/test.desc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ main.c
44
^EXIT=0$
55
^SIGNAL=0$
66
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
7-
^\[main.4\] .* Check that loop invariant is preserved: SUCCESS$
7+
^\[main.5\] .* Check that loop invariant is preserved: SUCCESS$
8+
^\[main.6\] .* Compare the old and new variant values: SUCCESS$
89
^\[main.2\] .* Check loop invariant before entry: SUCCESS$
910
^\[main.3\] .* Check that loop invariant is preserved: SUCCESS$
11+
^\[main.4\] .* Compare the old and new variant values: SUCCESS$
1012
^\[main.assertion.1\] .* assertion s == n: SUCCESS$
1113
^VERIFICATION SUCCESSFUL$
1214
--
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#define N 10
2+
3+
int foo(int i);
4+
5+
int main()
6+
{
7+
int i = 0;
8+
while (i != N)
9+
__CPROVER_loop_invariant(0 <= i && i <= N)
10+
__CPROVER_decreases(foo(i))
11+
{
12+
i++;
13+
}
14+
15+
return 0;
16+
}
17+
18+
int foo(int i)
19+
{
20+
return N - i;
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^Loop variant is not side-effect free. \(at: file main.c line .* function main\)$
5+
^EXIT=70$
6+
^SIGNAL=0$
7+
--
8+
--
9+
This test fails because the loop invariant contains a function call. Loop
10+
variants must not contain loops, since we want to ensure that the
11+
calculation of loop variants themselves will terminate. To enforce this
12+
requirement, for now, we simply ban loop variants from containing function
13+
calls. In the future, we may allow function calls (but definitely not loops) to
14+
appear inside loop variants.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
int main()
3+
{
4+
int i = 0;
5+
int N = 10;
6+
while (i != N)
7+
__CPROVER_loop_invariant(0 <= i && i <= N)
8+
__CPROVER_decreases(i)
9+
{
10+
i++;
11+
}
12+
13+
return 0;
14+
}
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
4+
^main.c function main$
5+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
6+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
7+
^\[main.3\] .* Compare the old and new variant values: FAILURE$
8+
^.* 1 of 3 failed \(2 iterations\)$
9+
^VERIFICATION FAILED$
10+
^EXIT=10$
11+
^SIGNAL=0$
12+
--
13+
--
14+
This test fails because the loop invariant contains a function call. Loop
15+
variants must not contain loops, since we want to ensure that the
16+
calculation of loop variants themselves will terminate. To enforce this
17+
requirement, we simply ban loop variants from containing function calls.
18+
In the future, we may allow function calls (but definitely not loops) to appear
19+
inside loop variants.

regression/contracts/variant_parsing_statement_fail/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--enforce-all-contracts
3+
--apply-loop-contracts
44
activate-multi-line-match
55
^main.c.*error: syntax error before 'int'\n.*__CPROVER_decreases\(int x = 0\)$
66
^PARSING ERROR$

regression/contracts/variant_parsing_tuple_fail/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--enforce-all-contracts
3+
--apply-loop-contracts
44
activate-multi-line-match
55
^main.c.*error: syntax error before ','\n.*__CPROVER_decreases\(N - i, 42\)$
66
^PARSING ERROR$
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
int main()
2+
{
3+
int i = 0;
4+
int N = 10;
5+
while (i != N)
6+
__CPROVER_loop_invariant(0 <= i && i <= N)
7+
__CPROVER_decreases((N+=0) - i)
8+
{
9+
i++;
10+
}
11+
12+
return 0;
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^Loop variant is not side-effect free. \(at: file main.c line .* function main\)$
5+
^EXIT=70$
6+
^SIGNAL=0$
7+
--
8+
--
9+
This test fails because the loop invariant contains a side effect, namely
10+
incrementing variable N and by zero. In this case, although the value of N
11+
remains unchanged after the increment operation, we read from and write to N.
12+
So this constitues a side effect. Loop variants are banned from containing
13+
side effects, since loop variants should not modify program states.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
int main()
3+
{
4+
int i = 0;
5+
int N = 10;
6+
while (i != N)
7+
__CPROVER_loop_invariant(i <= N)
8+
__CPROVER_decreases(N - i)
9+
{
10+
i++;
11+
}
12+
13+
return 0;
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^main.c function main$
5+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
6+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
7+
^\[main.3\] .* Compare the old and new variant values: FAILURE$
8+
^.* 1 of 3 failed \(2 iterations\)$
9+
^VERIFICATION FAILED$
10+
^EXIT=10$
11+
^SIGNAL=0$
12+
--
13+
--
14+
This test fails because the loop invariant is not strong enough. We
15+
additionally need 0 <= i in the loop invariant.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
int main()
3+
{
4+
int i = 0;
5+
int N = 10;
6+
while (1 == 1)
7+
__CPROVER_loop_invariant(0 <= i && i <= N)
8+
__CPROVER_decreases(N - i)
9+
{
10+
if (i == 10) {break;}
11+
i++;
12+
}
13+
14+
return 0;
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^main.c function main$
5+
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
6+
^\[main.2\] .* Check that loop invariant is preserved: SUCCESS$
7+
^\[main.3\] .* Compare the old and new variant values: SUCCESS$
8+
^.*0 of 3 failed \(1 iterations\)$
9+
^VERIFICATION SUCCESSFUL$
10+
^EXIT=0$
11+
^SIGNAL=0$
12+
--
13+
--
14+
This test succeeds. The purpose of this test is to check whether a loop variant
15+
can successfully prove the termination of a loop (i) whose exit condition
16+
is 1 == 1 (i.e. true) and (ii) that will eventually exit via a break statement.

src/goto-instrument/code_contracts.cpp

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,9 @@ exprt get_size(const typet &type, const namespacet &ns, messaget &log)
112112
return result;
113113
}
114114

115-
void code_contractst::check_apply_invariants(
115+
void code_contractst::check_apply_invariants_and_decreases(
116116
goto_functionst::goto_functiont &goto_function,
117+
const irep_idt &function_name,
117118
const local_may_aliast &local_may_alias,
118119
const goto_programt::targett loop_head,
119120
const loopt &loop,
@@ -129,11 +130,18 @@ void code_contractst::check_apply_invariants(
129130
t->location_number > loop_end->location_number)
130131
loop_end = t;
131132

132-
// see whether we have an invariant
133+
// see whether we have an invariant and a variant
133134
exprt invariant = static_cast<const exprt &>(
134135
loop_end->get_condition().find(ID_C_spec_loop_invariant));
135-
if(invariant.is_nil())
136-
return;
136+
exprt variant = static_cast<const exprt &>(
137+
loop_end->get_condition().find(ID_C_spec_decreases));
138+
if(invariant.is_nil())
139+
{
140+
if (variant.is_nil())
141+
return;
142+
else
143+
invariant = true_exprt(); // The Boolean true expression
144+
}
137145

138146
// change
139147
// H: loop;
@@ -142,9 +150,12 @@ void code_contractst::check_apply_invariants(
142150
// H: assert(invariant);
143151
// havoc;
144152
// assume(invariant);
153+
// old_variant = variant(current_environment);
145154
// if(guard) goto E:
146155
// loop;
156+
// new_variant = variant(current_environment);
147157
// assert(invariant);
158+
// assert(new_variant < old_variant);
148159
// assume(false);
149160
// E: ...
150161

@@ -188,6 +199,27 @@ void code_contractst::check_apply_invariants(
188199
converter.goto_convert(assumption, havoc_code, mode);
189200
}
190201

202+
// Temproary variables that store the variant before and after the loop
203+
symbol_exprt old_variant_symbol = new_tmp_symbol(variant.type(), loop_head->source_location, function_name, mode).symbol_expr();
204+
symbol_exprt new_variant_symbol = new_tmp_symbol(variant.type(), loop_head->source_location, function_name, mode).symbol_expr();
205+
206+
if (!variant.is_nil())
207+
{
208+
// Generate: a declaration of the temporary variable that stores the loop
209+
// variant before the loop entry
210+
havoc_code.add(goto_programt::make_decl(old_variant_symbol, loop_head->source_location));
211+
212+
// Generate: an assignment to store the loop variant before the loop entry
213+
// We would like to set a comment associated with a source location.
214+
// However, we have already used loop_head->source_location to set a
215+
// comment. Furthermore, the third argument of make_assignment is passed by
216+
// reference rather than by value. Therefore, it is necessary to clone
217+
// loop_head->source_location.
218+
source_locationt declaration_old_variant_source_location {loop_head->source_location};
219+
havoc_code.add(goto_programt::make_assignment(old_variant_symbol, variant, declaration_old_variant_source_location));
220+
havoc_code.instructions.back().source_location.set_comment("Evaluate and store the loop variant before the loop");
221+
}
222+
191223
// non-deterministically skip the loop if it is a do-while loop
192224
if(!loop_head->is_goto())
193225
{
@@ -209,11 +241,35 @@ void code_contractst::check_apply_invariants(
209241
converter.goto_convert(assertion, havoc_code, mode);
210242
havoc_code.instructions.back().source_location.set_comment(
211243
"Check that loop invariant is preserved");
212-
auto offset = havoc_code.instructions.size();
213-
goto_function.body.insert_before_swap(loop_end, havoc_code);
214-
std::advance(loop_end, offset);
215244
}
216245

246+
if (!variant.is_nil())
247+
{
248+
// Generate: a declaration of a temporary variable that stores the variant
249+
// after one arbitrary iteration of the loop
250+
havoc_code.add(goto_programt::make_decl(new_variant_symbol, loop_head->source_location));
251+
252+
// Generate: an assignment to store the loop variant after one iteration of
253+
// the loop
254+
source_locationt declaration_new_variant_source_location {loop_head->source_location};
255+
havoc_code.add(goto_programt::make_assignment(new_variant_symbol, variant, declaration_new_variant_source_location));
256+
havoc_code.instructions.back().source_location.set_comment("Evaluate and store the loop variant after the loop");
257+
258+
// Generate: assertion that the loop variant after the loop is smaller than
259+
// the loop variant before the loop
260+
source_locationt assignment_source_location {loop_head->source_location};
261+
havoc_code.add(goto_programt::make_inequality(new_variant_symbol, old_variant_symbol, assignment_source_location));
262+
havoc_code.instructions.back().source_location.set_comment("Compare the old and new variant values");
263+
264+
// Discartd the temporary variables that store loop variants
265+
havoc_code.add(goto_programt::make_dead(old_variant_symbol, loop_head->source_location));
266+
havoc_code.add(goto_programt::make_dead(new_variant_symbol, loop_head->source_location));
267+
}
268+
269+
auto offset = havoc_code.instructions.size();
270+
goto_function.body.insert_before_swap(loop_end, havoc_code);
271+
std::advance(loop_end, offset);
272+
217273
// change the back edge into assume(false) or assume(guard)
218274
loop_end->targets.clear();
219275
loop_end->type=ASSUME;
@@ -558,13 +614,16 @@ void code_contractst::apply_loop_contract(
558614

559615
// Iterate over the (natural) loops in the function,
560616
// and apply any invariant annotations that we find.
561-
for(const auto &loop : natural_loops.loop_map)
562-
check_apply_invariants(
617+
for(const auto &loop : natural_loops.loop_map)
618+
{
619+
check_apply_invariants_and_decreases(
563620
goto_function,
621+
function_name,
564622
local_may_alias,
565623
loop.first,
566624
loop.second,
567625
symbol_table.lookup_ref(function_name).mode);
626+
}
568627
}
569628

570629
const symbolt &code_contractst::new_tmp_symbol(

src/goto-instrument/code_contracts.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ class code_contractst
9898
const irep_idt &function_id,
9999
const irep_idt &mode);
100100

101-
void check_apply_invariants(
101+
void check_apply_invariants_and_decreases(
102102
goto_functionst::goto_functiont &goto_function,
103+
const irep_idt &function_name,
103104
const local_may_aliast &local_may_alias,
104105
const goto_programt::targett loop_head,
105106
const loopt &loop,
106107
const irep_idt &mode);
108+
107109
const namespacet &get_namespace() const;
108110

109111
// for "helper" classes to update symbol table.

0 commit comments

Comments
 (0)