Skip to content

Commit 3ee6728

Browse files
author
Remi Delmas
committed
Function contracts: new method for havocking assigns clause targets
that works when there are dependencies between targets. We now take a snapshot of the target addresses if they are valid and havoc them in a second step if they are valid. Previously we were havocking targets directly in an arbitrary sequence, and havocking s->ptr before *(s->ptr) or __CPROVER_POINTER_OBJECT(s->ptr) would cause spurious errors. Added a regression test with dependent assigns targets that fails using the previous method.
1 parent c143a27 commit 3ee6728

File tree

24 files changed

+667
-16
lines changed

24 files changed

+667
-16
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <stdlib.h>
2+
#include <stdbool.h>
3+
4+
int bar(int l, int r)
5+
__CPROVER_requires(0 <= l && l <= 10)
6+
__CPROVER_requires(0 <= r && r <= 10)
7+
__CPROVER_assigns()
8+
__CPROVER_ensures(__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r))
9+
{
10+
return l + r;
11+
}
12+
13+
int *baz(int l, int r)
14+
__CPROVER_requires(0 <= l && l <= 10)
15+
__CPROVER_requires(0 <= r && r <= 10)
16+
__CPROVER_assigns()
17+
__CPROVER_ensures(*__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r))
18+
{
19+
int *res = malloc(sizeof(int));
20+
*res = l + r;
21+
return res;
22+
}
23+
24+
int foo(int l, int r)
25+
__CPROVER_requires(0 <= l && l <= 10)
26+
__CPROVER_requires(0 <= r && r <= 10)
27+
__CPROVER_assigns()
28+
__CPROVER_ensures(__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r))
29+
{
30+
bar(l, r);
31+
bar(l, r);
32+
baz(l, r);
33+
baz(l, r);
34+
return l + r;
35+
}
36+
37+
int main()
38+
{
39+
int l;
40+
int r;
41+
foo(l,r);
42+
return 0;
43+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--replace-call-with-contract bar --replace-call-with-contract baz --enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^[.*] Check that .*ignored_return_value.* is assignable: FAILURE
9+
--
10+
This test checks that when replacing a call by a contract for a call that
11+
ignores the return value of the function, the temporary introduced to
12+
receive the call result does not trigger errors with assigns clause Checking
13+
in the function under verification.

regression/contracts/assigns_enforce_21/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ main.c
44
^EXIT=10$
55
^SIGNAL=0$
66
main.c function bar
7-
^\[bar.\d+\] line \d+ Check that \*y is assignable: SUCCESS$
8-
^\[bar.\d+\] line \d+ Check that x is assignable: FAILURE$
7+
^\[bar\.\d+\] line \d+ Check that \*y is assignable: SUCCESS$
8+
^\[bar\.\d+\] line \d+ Check that x \(assigned by the contract of quz\) is assignable: FAILURE
99
^VERIFICATION FAILED$
1010
--
1111
--

regression/contracts/assigns_replace_08/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo --replace-call-with-contract bar _ --pointer-primitive-check
44
^EXIT=10$
55
^SIGNAL=0$
6-
\[foo.\d+\] line \d+ Check that \*z is assignable: FAILURE$
6+
\[foo.\d+\] line \d+ Check that \*z \(assigned by the contract of bar\) is assignable: FAILURE$
77
^.* 1 of \d+ failed \(\d+ iteration.*\)$
88
^VERIFICATION FAILED$
99
--

regression/contracts/assigns_replace_09/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--replace-call-with-contract bar --enforce-contract foo
44
^EXIT=0$
55
^SIGNAL=0$
6-
\[foo.\d+\] line \d+ Check that \*z is assignable: SUCCESS$
6+
\[foo.\d+\] line \d+ Check that \*z \(assigned by the contract of bar\) is assignable: SUCCESS$
77
^VERIFICATION SUCCESSFUL$
88
--
99
^Condition: \!not\_found$
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main_enforce.c
3+
--enforce-contract resize_vec _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
Verifies the contract being replaced in `replace.desc`.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "vect.h"
2+
3+
int main()
4+
{
5+
vect *v;
6+
size_t incr;
7+
resize_vec(v, incr);
8+
return 0;
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "vect.h"
2+
3+
int main()
4+
{
5+
vect *v;
6+
resize_vec_incr10(v);
7+
return 0;
8+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
main_replace.c
3+
--replace-call-with-contract resize_vec --enforce-contract resize_vec_incr10 _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check
4+
^\[.*\] .* Check that v->size \(assigned by the contract of resize_vec\) is assignable: SUCCESS
5+
^\[.*\] .* Check that v->arr \(assigned by the contract of resize_vec\) is assignable: FAILURE
6+
^\[.*\] .* Check that POINTER_OBJECT\(\(const void \*\)v->arr\) \(assigned by the contract of resize_vec\) is assignable: FAILURE
7+
^VERIFICATION FAILED$
8+
^EXIT=10$
9+
^SIGNAL=0$
10+
--
11+
--
12+
Shows that when an assigns clause contains targets that are dependent,
13+
in this case, a pointer variable `v->arr` and
14+
the object it points to `__CPROVER_POINTER_OBJECT(v->arr)`, we can correctly
15+
havoc them when replacing the call by the contract.
16+
In this version of the test the inclusion check must fail.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
typedef struct vect
5+
{
6+
char *arr;
7+
size_t size;
8+
} vect;
9+
10+
void resize_vec(vect *v, size_t incr)
11+
// clang-format off
12+
__CPROVER_requires(
13+
__CPROVER_is_fresh(v, sizeof(vect)) &&
14+
0 < v->size && v->size <= __CPROVER_max_malloc_size &&
15+
0 < incr && incr < __CPROVER_max_malloc_size - v->size &&
16+
__CPROVER_is_fresh(v->arr, v->size)
17+
)
18+
__CPROVER_assigns(v->size, v->arr, __CPROVER_POINTER_OBJECT(v->arr))
19+
__CPROVER_ensures(
20+
v->size == __CPROVER_old(v->size) + __CPROVER_old(incr) &&
21+
__CPROVER_is_fresh(v->arr, v->size)
22+
)
23+
// clang-format on
24+
{
25+
free(v->arr);
26+
v->size += incr;
27+
v->arr = malloc(v->size);
28+
return;
29+
}
30+
31+
void resize_vec_incr10(vect *v)
32+
// clang-format off
33+
__CPROVER_requires(
34+
__CPROVER_is_fresh(v, sizeof(vect)) &&
35+
0 < v->size && v->size <= __CPROVER_max_malloc_size &&
36+
v->size + 10 < __CPROVER_max_malloc_size &&
37+
__CPROVER_is_fresh(v->arr, v->size)
38+
)
39+
__CPROVER_assigns(v->size)
40+
__CPROVER_ensures(
41+
v->size == __CPROVER_old(v->size) + 10 &&
42+
__CPROVER_is_fresh(v->arr, v->size)
43+
)
44+
// clang-format on
45+
{
46+
resize_vec(v, 10);
47+
return;
48+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main_enforce.c
3+
--enforce-contract resize_vec _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
Verifies the contract being replaced in `replace.desc`.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "vect.h"
2+
3+
int main()
4+
{
5+
vect *v;
6+
size_t incr;
7+
resize_vec(v, incr);
8+
return 0;
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "vect.h"
2+
3+
int main()
4+
{
5+
vect *v;
6+
resize_vec_incr10(v);
7+
return 0;
8+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
main_replace.c
3+
--replace-call-with-contract resize_vec --enforce-contract resize_vec_incr10 _ --signed-overflow-check --unsigned-overflow-check --pointer-overflow-check
4+
^VERIFICATION SUCCESSFUL$
5+
^\[.*\] .* Check that v->size \(assigned by the contract of resize_vec\) is assignable: SUCCESS
6+
^\[.*\] .* Check that v->arr \(assigned by the contract of resize_vec\) is assignable: SUCCESS
7+
^\[.*\] .* Check that POINTER_OBJECT\(\(const void \*\)v->arr\) \(assigned by the contract of resize_vec\) is assignable: SUCCESS
8+
^EXIT=0$
9+
^SIGNAL=0$
10+
--
11+
--
12+
Shows that when an assigns clause contains targets that are dependent,
13+
in this case, a pointer variable `v->arr` and
14+
the object it points to `__CPROVER_POINTER_OBJECT(v->arr)`, we can correctly
15+
havoc them when replacing the call by the contract.
16+
In this version of the test the inclusion check must pass.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
typedef struct vect
5+
{
6+
char *arr;
7+
size_t size;
8+
} vect;
9+
10+
void resize_vec(vect *v, size_t incr)
11+
// clang-format off
12+
__CPROVER_requires(
13+
__CPROVER_is_fresh(v, sizeof(vect)) &&
14+
0 < v->size && v->size <= __CPROVER_max_malloc_size &&
15+
0 < incr && incr < __CPROVER_max_malloc_size - v->size &&
16+
__CPROVER_is_fresh(v->arr, v->size)
17+
)
18+
__CPROVER_assigns(v->size, v->arr, __CPROVER_POINTER_OBJECT(v->arr))
19+
__CPROVER_ensures(
20+
v->size == __CPROVER_old(v->size) + __CPROVER_old(incr) &&
21+
__CPROVER_is_fresh(v->arr, v->size)
22+
)
23+
// clang-format on
24+
{
25+
free(v->arr);
26+
v->size += incr;
27+
v->arr = malloc(v->size);
28+
return;
29+
}
30+
31+
void resize_vec_incr10(vect *v)
32+
// clang-format off
33+
__CPROVER_requires(
34+
__CPROVER_is_fresh(v, sizeof(vect)) &&
35+
0 < v->size && v->size <= __CPROVER_max_malloc_size &&
36+
v->size + 10 < __CPROVER_max_malloc_size &&
37+
__CPROVER_is_fresh(v->arr, v->size)
38+
)
39+
__CPROVER_assigns(*v, __CPROVER_POINTER_OBJECT(v->arr))
40+
__CPROVER_ensures(
41+
v->size == __CPROVER_old(v->size) + 10 &&
42+
__CPROVER_is_fresh(v->arr, v->size)
43+
)
44+
// clang-format on
45+
{
46+
resize_vec(v, 10);
47+
return;
48+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <stdio.h>
2+
3+
int foo(int l)
4+
__CPROVER_requires(-10 <= l && l <= 10)
5+
__CPROVER_ensures(__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(10))
6+
{
7+
return l+10;
8+
}
9+
10+
int main()
11+
{
12+
int l;
13+
__CPROVER_assume(-10 <= l && l <= 10);
14+
foo(l);
15+
return 0;
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^Tracking history of constant expressions is not supported yet
9+
--
10+
This test checks that history variables are supported for constant expressions.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <stdio.h>
2+
3+
long bar(long l, long r)
4+
__CPROVER_requires(-10 <= l && l <= 10)
5+
__CPROVER_requires(-10 <= r && r <= 10)
6+
__CPROVER_ensures(__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r))
7+
{
8+
return l + r;
9+
}
10+
11+
int foo(int l, int r)
12+
__CPROVER_requires(-10 <= l && l <= 10)
13+
__CPROVER_requires(-10 <= r && r <= 10)
14+
__CPROVER_ensures(__CPROVER_return_value == __CPROVER_old(l) + __CPROVER_old(r))
15+
{
16+
return bar((long)l, (long)r);
17+
}
18+
19+
int main()
20+
{
21+
int n;
22+
__CPROVER_assume(-10 <= n && n <= 10);
23+
foo(n,n);
24+
return 0;
25+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--replace-call-with-contract bar --enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^Tracking history of typecast expressions is not supported yet
9+
--
10+
This test checks that history variables are supported for typecast expressions.

src/goto-instrument/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ SRC = accelerate/accelerate.cpp \
1818
call_sequences.cpp \
1919
contracts/assigns.cpp \
2020
contracts/contracts.cpp \
21+
contracts/havoc_assigns_clause_targets.cpp \
2122
contracts/memory_predicates.cpp \
2223
contracts/utils.cpp \
2324
concurrency.cpp \

0 commit comments

Comments
 (0)