Skip to content

Commit 4eed7a9

Browse files
committed
CONTRACTS: Several fixes for loop contracts
+ Disabled loop contract instrumentation along program paths that result in vacuous loops, i.e., when the loop guard doesn't hold initially and the loop never runs. This allows for significantly simpler loop contracts in some cases. + Fixed the instrumentation for decreases clauses. It no longer requires the `source_location` hack to find the beginning of loop "body". + Added some regression tests for loops that have side effects within guards
1 parent de9fa65 commit 4eed7a9

File tree

12 files changed

+485
-191
lines changed

12 files changed

+485
-191
lines changed

regression/contracts/invar_check_nested_loops/test.desc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ main.c
99
^\[main\.\d+\] .* Check loop invariant before entry: SUCCESS$
1010
^\[main\.\d+\] .* Check that loop invariant is preserved: SUCCESS$
1111
^\[main\.\d+\] .* Check decreases clause on loop iteration: SUCCESS$
12-
^\[main.assigns.\d+] line 23 Check that s is assignable: SUCCESS$
13-
^\[main.assigns.\d+] line 24 Check that a is assignable: SUCCESS$
14-
^\[main.assigns.\d+] line 27 Check that s is assignable: SUCCESS$
12+
^\[main.assigns.\d+] .* line 23 Check that s is assignable: SUCCESS$
13+
^\[main.assigns.\d+] .* line 24 Check that a is assignable: SUCCESS$
14+
^\[main.assigns.\d+] .* line 27 Check that s is assignable: SUCCESS$
1515
^\[main\.assertion.\d+\] .* assertion s == n: SUCCESS$
1616
^VERIFICATION SUCCESSFUL$
1717
--
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include <assert.h>
2+
#include <limits.h>
3+
#include <stdbool.h>
4+
5+
bool check(unsigned *i, unsigned *j, unsigned *k)
6+
{
7+
(*j)++;
8+
return *i < *k;
9+
}
10+
11+
int main()
12+
{
13+
unsigned i, j, k;
14+
__CPROVER_assume(k < UINT_MAX);
15+
16+
i = j = 0;
17+
18+
while(check(&i, &j, &k))
19+
// clang-format off
20+
__CPROVER_assigns(i, j)
21+
__CPROVER_loop_invariant(0 <= i && i == j && i <= k)
22+
// clang-format on
23+
{
24+
i++;
25+
}
26+
27+
assert(i == k);
28+
assert(j == k + 1);
29+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts _ --unsigned-overflow-check
4+
\[check.assigns\.\d+\] line \d+ Check that \*j is assignable: SUCCESS$
5+
\[check.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in \*j \+ 1u: SUCCESS
6+
\[check.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in \*j \+ 1u: SUCCESS
7+
\[check.assigns\.\d+\] line \d+ Check that return_value_check is assignable: SUCCESS$
8+
\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
9+
\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
10+
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
11+
\[main.assigns\.\d+\] line \d+ Check that j is assignable: SUCCESS$
12+
\[main.assigns\.\d+\] line \d+ Check that k is assignable: SUCCESS$
13+
\[main.assigns\.\d+\] line \d+ Check that i is valid: SUCCESS$
14+
\[main.assigns\.\d+\] line \d+ Check that j is valid: SUCCESS$
15+
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
16+
\[main.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in i \+ 1u: SUCCESS
17+
\[main.assertion\.\d+\] line \d+ assertion i == k: SUCCESS$
18+
\[main.overflow\.\d+\] line \d+ arithmetic overflow on unsigned \+ in k \+ \(unsigned int\)1: SUCCESS
19+
\[main.assertion\.\d+\] line \d+ assertion j == k \+ 1: SUCCESS$
20+
^EXIT=0$
21+
^SIGNAL=0$
22+
--
23+
--
24+
This test demonstrates a case where the loop guard has side effects.
25+
The loop contracts must specify the state of all modified variables,
26+
including those in the loop guard.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <assert.h>
2+
#include <stdbool.h>
3+
4+
bool check(unsigned *i, unsigned *j, unsigned *k)
5+
{
6+
(*j)++;
7+
return *i < *k;
8+
}
9+
10+
int main()
11+
{
12+
unsigned i, j, k;
13+
14+
i = j = 0;
15+
16+
while(check(&i, &j, &k))
17+
// clang-format off
18+
__CPROVER_assigns(i)
19+
__CPROVER_loop_invariant(0 <= i && i <= k)
20+
// clang-format on
21+
{
22+
i++;
23+
}
24+
25+
assert(i == k);
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts _ --unsigned-overflow-check
4+
\[check.assigns\.\d+\] line \d+ Check that \*j is assignable: FAILURE$
5+
\[check.assigns\.\d+\] line \d+ Check that return_value_check is assignable: SUCCESS$
6+
\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
7+
\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
8+
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
9+
\[main.assigns\.\d+\] line \d+ Check that j is assignable: SUCCESS$
10+
\[main.assigns\.\d+\] line \d+ Check that k is assignable: SUCCESS$
11+
\[main.assigns\.\d+\] line \d+ Check that i is valid: SUCCESS$
12+
\[main.assigns\.\d+\] line \d+ Check that i is assignable: SUCCESS$
13+
\[main.assertion\.\d+\] line \d+ assertion i == k: SUCCESS$
14+
^EXIT=(6|10)$
15+
^SIGNAL=0$
16+
--
17+
--
18+
This test demonstrates a case where the loop guard has side effects.
19+
The writes performed in the guard must also be specified
20+
in the assigns clause of the loop.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int main()
5+
{
6+
size_t size;
7+
8+
char *buf = malloc(size);
9+
char c;
10+
11+
size_t start;
12+
size_t end = start;
13+
14+
while(end < size)
15+
// clang-format off
16+
__CPROVER_loop_invariant(start <= end && end <= size)
17+
__CPROVER_decreases(size - end)
18+
// clang-format on
19+
{
20+
if(buf[end] != c)
21+
break;
22+
end++;
23+
}
24+
25+
if(start > size)
26+
{
27+
assert(end == start);
28+
}
29+
else
30+
{
31+
assert(start <= end && end <= size);
32+
}
33+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts _ --signed-overflow-check --unsigned-overflow-check
4+
\[main.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
5+
\[main.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
6+
\[main.assigns.\d+\] line \d+ Check that end is valid: SUCCESS$
7+
\[main.assigns.\d+\] line \d+ Check that end is assignable: SUCCESS$
8+
\[main.assertion.\d+\] line \d+ assertion end == start: SUCCESS$
9+
\[main.assertion.\d+\] line \d+ assertion start <= end && end <= size: SUCCESS$
10+
^EXIT=0$
11+
^SIGNAL=0$
12+
--
13+
--
14+
This test demonstrates a simplification that is now possible on loop contracts.
15+
16+
Prior to this commit, loop contracts were unconditionally applied on loops,
17+
and thus had to also consider the case when the loop doesn't run even once.
18+
19+
The contracts that were previously necessary were:
20+
__CPROVER_loop_invariant(
21+
(start > size && end == start) || (start <= end && end <= size)
22+
)
23+
__CPROVER_decreases(
24+
max(start, size) - end
25+
)

regression/contracts/variant_missing_invariant_warning/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ CORE
22
main.c
33
--apply-loop-contracts
44
activate-multi-line-match
5-
^The loop at file main.c line 4 function main does not have an invariant.*.\nHence, a default invariant \('true'\) is being used to prove those.$
5+
^The loop at file main.c line 4 function main does not have an invariant.*.\nHence, a default invariant \('true'\) is being used.*.$
66
^\[main\.\d+\] .* Check loop invariant before entry: SUCCESS$
77
^\[main\.\d+\] .* Check that loop invariant is preserved: SUCCESS$
88
^\[main\.\d+\] .* Check decreases clause on loop iteration: SUCCESS$

regression/contracts/variant_multi_instruction_loop_head/main.c

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

66
while(*y <= 0 && *y < 10)
77
// clang-format off
8-
__CPROVER_loop_invariant(1 == 1)
8+
__CPROVER_loop_invariant(0 <= *y && *y <= 10)
99
__CPROVER_decreases(10 - x)
1010
// clang-format on
1111
{
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
CORE
22
main.c
33
--apply-loop-contracts
4-
^VERIFICATION FAILED$
5-
^EXIT=10$
4+
\[main\.\d+\] line \d+ Check loop invariant before entry: SUCCESS$
5+
\[main\.\d+\] line \d+ Check that loop invariant is preserved: SUCCESS$
6+
\[main\.\d+\] line \d+ Check decreases clause on loop iteration: SUCCESS$
7+
\[main\.\d+\] line \d+ Check that loop instrumentation was not truncated: SUCCESS$
8+
\[main\.assigns\.\d+\] line \d+ Check that x is valid: SUCCESS$
9+
\[main\.assigns\.\d+\] line \d+ Check that x is assignable: SUCCESS$
10+
^VERIFICATION SUCCESSFUL$
11+
^EXIT=0$
612
^SIGNAL=0$
713
--
814
--
9-
This test fails even without the fix proposed in the commit, so it should be improved.
10-
It is expected to fail because the proposed invariant isn't strong enough to help prove
11-
termination using the specified variant.
12-
13-
The test highlights a case where a C loop guard is compiled to multiple GOTO instructions.
14-
Therefore the loop_head pointer needs to be advanced multiple times to get to the loop body,
15-
where the initial value of the loop variant (/ ranking function) must be recorded.
16-
17-
The proposed fix advances the pointer until the source_location differs from the original
18-
loop_head's source_location. However, this might still not work if the loop guard in the
19-
original C code was split across multiple lines in the first place.
15+
This test checks that decreases clause is properly initialized
16+
when the loop head and loop invariant compilation emit several goto instructions.

0 commit comments

Comments
 (0)