Skip to content

Commit 23b2b35

Browse files
author
Remi Delmas
committed
CONTRACTS: introduce __CPROVER_assignable_t built-ins and allow
function calls in assigns clauses.
1 parent c6f83a2 commit 23b2b35

File tree

25 files changed

+496
-51
lines changed

25 files changed

+496
-51
lines changed

doc/cprover-manual/contracts-assigns.md

+30-11
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,44 @@ value(s) therein are not modified.
1818
The following functions can be used in assigns clause to specify ranges of
1919
assignable addresses.
2020
21+
Given an lvalue expression `expr` with a complete type `expr_t`,
22+
`__CPROVER_typed_target(expr)` specifies that the range
23+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable:
24+
```c
25+
__CPROVER_assignable_t __CPROVER_typed_target(expr_t expr);
26+
```
27+
28+
Given a pointer `ptr` pointing into some object `o`,
29+
`__CPROVER_whole_object(ptr)` specifies that all bytes of the object `o`
30+
are assignable:
31+
```c
32+
__CPROVER_assignable_t __CPROVER_whole_object(void *ptr);
33+
```
34+
2135
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
22-
specifies that all bytes starting from the given pointer and until the end of
23-
the object are assignable:
36+
specifies that the range of bytes starting from the pointer and until the end of
37+
the object `o` are assignable:
2438
```c
25-
__CPROVER_size_t __CPROVER_object_from(void *ptr);
39+
__CPROVER_assignable_t __CPROVER_object_from(void *ptr);
2640
```
2741

28-
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr, size)`
29-
specifies that `size` bytes starting from the given pointer and until the end of the object are assignable.
30-
The `size` value must such that `size <= __CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr)` holds:
42+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)`
43+
specifies that the range of `size` bytes of `o` starting at `ptr` are assignable:
44+
The `size` value must such that the range does not exceed the object boundary,
45+
that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold:
3146

3247
```c
33-
__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size);
48+
__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size);
3449
```
3550
36-
Caveats and limitations: The slices in question must *not*
37-
be interpreted as pointers by the program. During call-by-contract replacement,
38-
`__CPROVER_havoc_slice(ptr, size)` is used to havoc these targets,
39-
and `__CPROVER_havoc_slice` does not support havocing pointers.
51+
CAVEAT: The ranges specified by `__CPROVER_whole_object`,
52+
`__CPROVER_object_from` and `__CPROVER_object_upto` must *not*
53+
be interpreted as pointers by the program. This is because during
54+
call-by-contract replacement, `__CPROVER_havoc_slice(ptr, size)` is used to
55+
havoc these byte ranges, and `__CPROVER_havoc_slice` does not support
56+
havocing pointers. `__CPROVER_typed_target` must be used to specify targets
57+
that are pointers.
58+
4059
### Parameters
4160
4261
An _assigns_ clause currently supports simple variable types and their pointers,

regression/contracts/assigns-slice-targets/main-enforce.c

+18-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ struct st
77
int c;
88
};
99

10-
void foo(struct st *s)
10+
void foo(struct st *s, struct st *ss)
1111
// clang-format off
1212
__CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s)))
13-
__CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5))
14-
__CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5))
13+
__CPROVER_assigns(
14+
__CPROVER_object_upto(s->arr1, 5);
15+
__CPROVER_object_from(s->arr2 + 5);
16+
__CPROVER_whole_object(ss);
17+
)
1518
// clang-format on
1619
{
1720
// PASS
@@ -41,13 +44,24 @@ void foo(struct st *s)
4144
s->arr2[7] = 0;
4245
s->arr2[8] = 0;
4346
s->arr2[9] = 0;
47+
48+
// PASS
49+
ss->a = 0;
50+
ss->arr1[0] = 0;
51+
ss->arr1[7] = 0;
52+
ss->arr1[9] = 0;
53+
ss->b = 0;
54+
ss->arr2[6] = 0;
55+
ss->arr2[8] = 0;
56+
ss->c = 0;
4457
}
4558

4659
int main()
4760
{
4861
struct st s;
62+
struct st ss;
4963

50-
foo(&s);
64+
foo(&s, &ss);
5165

5266
return 0;
5367
}

regression/contracts/assigns-slice-targets/main-replace.c

+58-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ struct st
77
int c;
88
};
99

10-
void foo(struct st *s)
10+
void foo(struct st *s, struct st *ss)
1111
// clang-format off
1212
__CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s)))
13-
__CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5))
14-
__CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5))
13+
__CPROVER_assigns(
14+
__CPROVER_object_upto(s->arr1, 5);
15+
__CPROVER_object_from(s->arr2 + 5);
16+
__CPROVER_whole_object(ss);
17+
)
1518
// clang-format on
1619
{
1720
s->arr1[0] = 0;
@@ -54,7 +57,32 @@ int main()
5457
s.arr2[9] = 0;
5558
s.c = 0;
5659

57-
foo(&s);
60+
struct st ss;
61+
ss.a = 0;
62+
ss.arr1[0] = 0;
63+
ss.arr1[1] = 0;
64+
ss.arr1[2] = 0;
65+
ss.arr1[3] = 0;
66+
ss.arr1[4] = 0;
67+
ss.arr1[5] = 0;
68+
ss.arr1[6] = 0;
69+
ss.arr1[7] = 0;
70+
ss.arr1[8] = 0;
71+
ss.arr1[9] = 0;
72+
73+
ss.arr2[0] = 0;
74+
ss.arr2[1] = 0;
75+
ss.arr2[2] = 0;
76+
ss.arr2[3] = 0;
77+
ss.arr2[4] = 0;
78+
ss.arr2[5] = 0;
79+
ss.arr2[6] = 0;
80+
ss.arr2[7] = 0;
81+
ss.arr2[8] = 0;
82+
ss.arr2[9] = 0;
83+
ss.c = 0;
84+
85+
foo(&s, &ss);
5886

5987
// PASS
6088
assert(s.a == 0);
@@ -92,5 +120,31 @@ int main()
92120

93121
// PASS
94122
assert(s.c == 0);
123+
124+
// FAIL
125+
assert(ss.a == 0);
126+
assert(ss.arr1[0] == 0);
127+
assert(ss.arr1[1] == 0);
128+
assert(ss.arr1[2] == 0);
129+
assert(ss.arr1[3] == 0);
130+
assert(ss.arr1[4] == 0);
131+
assert(ss.arr1[5] == 0);
132+
assert(ss.arr1[6] == 0);
133+
assert(ss.arr1[7] == 0);
134+
assert(ss.arr1[8] == 0);
135+
assert(ss.arr1[9] == 0);
136+
assert(ss.b == 0);
137+
assert(ss.arr2[0] == 0);
138+
assert(ss.arr2[1] == 0);
139+
assert(ss.arr2[2] == 0);
140+
assert(ss.arr2[3] == 0);
141+
assert(ss.arr2[4] == 0);
142+
assert(ss.arr2[5] == 0);
143+
assert(ss.arr2[6] == 0);
144+
assert(ss.arr2[7] == 0);
145+
assert(ss.arr2[8] == 0);
146+
assert(ss.arr2[9] == 0);
147+
assert(ss.c == 0);
148+
95149
return 0;
96150
}

regression/contracts/assigns-slice-targets/test-enforce.desc

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
CORE
22
main-enforce.c
33
--enforce-contract foo
4-
^\[foo.assigns.\d+\].* Check that __CPROVER_object_slice\(\(void \*\)s->arr1, \(.*\)5\) is valid: SUCCESS$
5-
^\[foo.assigns.\d+\].* Check that __CPROVER_object_from\(\(void \*\)\(s->arr2 \+ \(.*\)5\)\) is valid: SUCCESS$
64
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)0\] is assignable: SUCCESS$
75
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)1\] is assignable: SUCCESS$
86
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)2\] is assignable: SUCCESS$
@@ -23,6 +21,14 @@ main-enforce.c
2321
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)7\] is assignable: SUCCESS$
2422
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)8\] is assignable: SUCCESS$
2523
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)9\] is assignable: SUCCESS$
24+
^\[foo.assigns.\d+\].* Check that ss->a is assignable: SUCCESS$
25+
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)0\] is assignable: SUCCESS$
26+
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)7\] is assignable: SUCCESS$
27+
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)9\] is assignable: SUCCESS$
28+
^\[foo.assigns.\d+\].* Check that ss->b is assignable: SUCCESS$
29+
^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)6\] is assignable: SUCCESS$
30+
^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)8\] is assignable: SUCCESS$
31+
^\[foo.assigns.\d+\].* Check that ss->c is assignable: SUCCESS$
2632
^VERIFICATION FAILED$
2733
^EXIT=10$
2834
^SIGNAL=0$

regression/contracts/assigns-slice-targets/test-replace.desc

+23
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,29 @@ main-replace.c
2424
^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)8\] == 0: FAILURE$
2525
^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)9\] == 0: FAILURE$
2626
^\[main.assertion.\d+\].*assertion s.c == 0: FAILURE$
27+
^\[main.assertion.\d+\].*assertion ss.a == 0: FAILURE$
28+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)0\] == 0: FAILURE$
29+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)1\] == 0: FAILURE$
30+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)2\] == 0: FAILURE$
31+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)3\] == 0: FAILURE$
32+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)4\] == 0: FAILURE$
33+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)5\] == 0: FAILURE$
34+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)6\] == 0: FAILURE$
35+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)7\] == 0: FAILURE$
36+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)8\] == 0: FAILURE$
37+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)9\] == 0: FAILURE$
38+
^\[main.assertion.\d+\].*assertion ss.b == 0: FAILURE$
39+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)0\] == 0: FAILURE$
40+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)1\] == 0: FAILURE$
41+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)2\] == 0: FAILURE$
42+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)3\] == 0: FAILURE$
43+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)4\] == 0: FAILURE$
44+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)5\] == 0: FAILURE$
45+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)6\] == 0: FAILURE$
46+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)7\] == 0: FAILURE$
47+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)8\] == 0: FAILURE$
48+
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)9\] == 0: FAILURE$
49+
^\[main.assertion.\d+\].*assertion ss.c == 0: FAILURE$
2750
^VERIFICATION FAILED$
2851
^EXIT=10$
2952
^SIGNAL=0$

regression/contracts/assigns_enforce_address_of/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo
44
^EXIT=(1|64)$
55
^SIGNAL=0$
6-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
6+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
77
^CONVERSION ERROR$
88
--
99
--

regression/contracts/assigns_enforce_conditional_non_lvalue_target/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--enforce-contract foo
4-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
4+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
55
^CONVERSION ERROR
66
^EXIT=(1|64)$
77
^SIGNAL=0$

regression/contracts/assigns_enforce_conditional_non_lvalue_target_list/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--enforce-contract foo
4-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
4+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
55
^CONVERSION ERROR
66
^EXIT=(1|64)$
77
^SIGNAL=0$

regression/contracts/assigns_enforce_function_calls/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo
44
^EXIT=(1|64)$
55
^SIGNAL=0$
6-
^.*error: function calls in assigns clause targets must be to __CPROVER_object_from, __CPROVER_object_slice$
6+
^.*error: expecting __CPROVER_assignable_t return type for function bar called in assigns clause$
77
^CONVERSION ERROR$
88
--
99
--

regression/contracts/assigns_enforce_literal/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo
44
^EXIT=(1|64)$
55
^SIGNAL=0$
6-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
6+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
77
^CONVERSION ERROR$
88
--
99
--

regression/contracts/assigns_enforce_side_effects_2/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo
44
^EXIT=(1|64)$
55
^SIGNAL=0$
6-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
6+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
77
^CONVERSION ERROR$
88
--
99
--

regression/contracts/assigns_enforce_side_effects_3/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--enforce-contract foo
44
^EXIT=(1|64)$
55
^SIGNAL=0$
6-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
6+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
77
^CONVERSION ERROR$
88
--
99
--

regression/contracts/assigns_type_checking_invalid_case_01/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ main.c
44
^EXIT=(1|64)$
55
^SIGNAL=0$
66
^CONVERSION ERROR$
7-
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
7+
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
88
--
99
Checks whether type checking rejects literal constants in assigns clause.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <stdlib.h>
2+
3+
__CPROVER_assignable_t my_write_set(char *arr, size_t size)
4+
{
5+
__CPROVER_assert(
6+
!arr || __CPROVER_rw_ok(arr, size), "target null or writable");
7+
8+
if(arr && size > 0)
9+
{
10+
__CPROVER_whole_object(arr);
11+
__CPROVER_object_upto(arr, size);
12+
__CPROVER_object_from(arr);
13+
__CPROVER_typed_target(arr[0]);
14+
}
15+
}
16+
17+
void main()
18+
{
19+
size_t size;
20+
char *arr;
21+
int do_init;
22+
if(do_init)
23+
{
24+
int nondet;
25+
arr = nondet ? malloc(size) : NULL;
26+
}
27+
my_write_set(arr, size);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
CORE
2+
main.c
3+
4+
^\[my_write_set.assertion.\d+\] .* target null or writable: FAILURE$
5+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: pointer NULL in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): FAILURE$
6+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: pointer invalid in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): FAILURE$
7+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: deallocated dynamic object in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): FAILURE$
8+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: dead object in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): FAILURE$
9+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: pointer outside object bounds in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): SUCCESS$
10+
^\[my_write_set.pointer_arithmetic.\d+\] .* pointer arithmetic: invalid integer address in \(char \*\)\(void \*\)arr - POINTER_OFFSET\(\(void \*\)arr\): SUCCESS$
11+
^\[my_write_set.overflow.\d+\] .* arithmetic overflow on unsigned - in OBJECT_SIZE\(\(void \*\)arr\) - POINTER_OFFSET\(\(void \*\)arr\): FAILURE$
12+
^VERIFICATION FAILED$
13+
^EXIT=10$
14+
^SIGNAL=0$
15+
--
16+
CALL __CPROVER_whole_object
17+
CALL __CPROVER_object_upto
18+
CALL __CPROVER_object_from
19+
CALL __CPROVER_typed_target
20+
--
21+
This test checks that:
22+
- the built-in functions used to specify assignable memory locations are
23+
supported;
24+
- that GOTO conversion rewrites calls to __CPROVER_assignable(...) calls;
25+
- checks for invalid pointers and of overflows in the computations of
26+
interval bounds are in place.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <stdlib.h>
2+
3+
__CPROVER_assignable_t my_write_set(char *arr, size_t size)
4+
{
5+
__CPROVER_assert(
6+
!arr || __CPROVER_rw_ok(arr, size), "target null or writable");
7+
8+
if(arr && size > 0)
9+
{
10+
__CPROVER_whole_object(arr);
11+
__CPROVER_object_upto(arr, size);
12+
__CPROVER_object_from(arr);
13+
__CPROVER_typed_target(arr[0]);
14+
}
15+
}
16+
17+
void main()
18+
{
19+
int nondet;
20+
size_t size;
21+
char *arr;
22+
arr = nondet ? malloc(size) : NULL;
23+
my_write_set(arr, size);
24+
}

0 commit comments

Comments
 (0)