Skip to content

Commit 75e9ab8

Browse files
committed
CONTRACTS: several fixes for loop contracts
+ disabled loop contract instrumentation along program paths that result in vacuous loop (i.e., the loop guard doesn't hold initially and the loop never runs) - this allows for significantly simpler loop contracts + also fixed the instrumentation for decreases clauses; no longer need 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 412d040 commit 75e9ab8

File tree

8 files changed

+411
-169
lines changed

8 files changed

+411
-169
lines changed
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
#define MAX_SIZE 1000000
5+
6+
int main()
7+
{
8+
size_t size;
9+
__CPROVER_assume(size <= MAX_SIZE);
10+
11+
char *buf = malloc(size);
12+
char c;
13+
14+
size_t start;
15+
size_t end = start;
16+
17+
while(end < size)
18+
// clang-format off
19+
__CPROVER_loop_invariant(start <= end && end <= size)
20+
__CPROVER_decreases(size - end)
21+
// clang-format on
22+
{
23+
if(buf[end] != c)
24+
break;
25+
end++;
26+
}
27+
28+
if(start > size)
29+
{
30+
assert(end == start);
31+
}
32+
else
33+
{
34+
assert(start <= end && end <= size);
35+
}
36+
}
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$

0 commit comments

Comments
 (0)