Skip to content

Commit 399a2c2

Browse files
author
Remi Delmas
committed
CONTRACTS: Front-end extensions for assigns clauses and frees clauses.
Add new functions to specify assignable targets in assigns clauses: - add type __CPROVER_assignable_t - add builtin __CPROVER_assignable - add builtin __CPROVER_whole_object - add builtin __CPROVER_object_upto - add builtin __CPROVER_typed_target - allow function call expressions as assigns clause targets as long as they have the return type __CPROVER_assignable_t and are one of the built-ins. Add support for `__CPROVER_frees()` clauses to the contract language to let users specify the pointers a function (or loop) is allowed to free.
1 parent 585039a commit 399a2c2

File tree

46 files changed

+826
-95
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+826
-95
lines changed

doc/cprover-manual/contracts-assigns.md

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,46 @@ value(s) therein are not modified.
1717
1818
### Object slice expressions
1919
20-
The following functions can be used in assigns clause to specify ranges of
21-
assignable addresses.
20+
The following functions can be used in assigns clauses to specify ranges of assignable bytes.
2221
23-
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
24-
specifies that all bytes starting from the given pointer and until the end of
25-
the object are assignable:
22+
Given an lvalue expression `expr` with a complete type `expr_t`,
23+
`__CPROVER_typed_target(expr)` specifies that the range
24+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable:
2625
```c
27-
__CPROVER_size_t __CPROVER_object_from(void *ptr);
26+
__CPROVER_assignable_t __CPROVER_typed_target(expr_t expr);
2827
```
2928

30-
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr, size)`
31-
specifies that `size` bytes starting from the given pointer and until the end of the object are assignable.
32-
The `size` value must such that `size <= __CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr)` holds:
29+
Given a pointer `ptr` pointing into some object `o`,
30+
`__CPROVER_whole_object(ptr)` specifies that all bytes of the object `o`
31+
are assignable:
32+
```c
33+
__CPROVER_assignable_t __CPROVER_whole_object(void *ptr);
34+
```
3335
36+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
37+
specifies that the range of bytes starting from the pointer and until the end of
38+
the object `o` are assignable:
3439
```c
35-
__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size);
40+
__CPROVER_assignable_t __CPROVER_object_from(void *ptr);
3641
```
3742

38-
Caveats and limitations: The slices in question must *not*
39-
be interpreted as pointers by the program. During call-by-contract replacement,
40-
`__CPROVER_havoc_slice(ptr, size)` is used to havoc these targets,
41-
and `__CPROVER_havoc_slice` does not support havocing pointers.
43+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)`
44+
specifies that the range of `size` bytes of `o` starting at `ptr` are assignable:
45+
The `size` value must such that the range does not exceed the object boundary,
46+
that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold:
47+
48+
```c
49+
__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size);
50+
```
51+
52+
CAVEAT: The ranges specified by `__CPROVER_whole_object`,
53+
`__CPROVER_object_from` and `__CPROVER_object_upto` must *not*
54+
be interpreted as pointers by the program. This is because during
55+
call-by-contract replacement, `__CPROVER_havoc_slice(ptr, size)` is used to
56+
havoc these byte ranges, and `__CPROVER_havoc_slice` does not support
57+
havocing pointers. `__CPROVER_typed_target` must be used to specify targets
58+
that are pointers.
59+
4260
### Parameters
4361
4462
An _assigns_ clause currently supports simple variable types and their pointers,

doc/cprover-manual/contracts-frees.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
[CPROVER Manual TOC](../../)
2+
3+
# Frees Clause
4+
5+
### Syntax
6+
7+
```c
8+
__CPROVER_frees(*pointer-typed-expression*, ...)
9+
```
10+
11+
A _frees_ clause allows the user to specify a set of pointers that may be freed
12+
by a function or the body of a loop.
13+
A function or loop contract contract may have zero or more _frees_ clauses.
14+
15+
_Frees_ clause targets must be pointer-typed expressions.
16+
17+
_Frees_ clause targets can also be _conditional_ and written as follows:
18+
19+
```
20+
condition: list-of-pointer-typed-expressions;
21+
```
22+
23+
### Examples
24+
25+
In a function contract
26+
```c
27+
int foo(char *arr1, char *arr2, size_t size)
28+
__CPROVER_frees(
29+
// arr1 freeable only if the condition holds
30+
size > 0 && arr1: arr1;
31+
// arr2 always freeable
32+
arr2
33+
)
34+
{
35+
if(size > 0 && arr1)
36+
free(arr1);
37+
free(arr2);
38+
return 0;
39+
}
40+
```
41+
42+
In a loop contract:
43+
44+
```c
45+
int main()
46+
{
47+
size_t size = 10;
48+
char *arr = malloc(size);
49+
50+
for(size_t i = 0; i <= size; i++)
51+
// clang-format off
52+
__CPROVER_assigns(i, __CPROVER_POINTER_OBJECT(arr))
53+
__CPROVER_frees(arr)
54+
// clang-format on2
55+
{
56+
if(i < size)
57+
arr[i] = 0;
58+
else
59+
free(arr);
60+
}
61+
return 0;
62+
}
63+
```
64+
65+
### Semantics
66+
67+
The set of pointers specified by the frees clause of the contract is interpreted
68+
at the function call-site for function contracts, and right before entering the
69+
loop for loop contracts.
70+
71+
#### For contract checking
72+
When checking a contract against a function or a loop, each pointer that the
73+
function or loop body attempts to free gets checked for membership in the set of
74+
pointers specified by the contract.
75+
76+
#### For replacement of function calls or loops by contracts
77+
When replacing a function call or a loop by a contract, each pointer of the
78+
_frees_ clause is non-deterministically freed after the function call
79+
or after the loop.
80+

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

Lines changed: 18 additions & 4 deletions
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

Lines changed: 58 additions & 4 deletions
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

Lines changed: 8 additions & 2 deletions
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

Lines changed: 23 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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.

0 commit comments

Comments
 (0)