Skip to content

Commit e2b70d7

Browse files
author
Remi Delmas
committed
CONTRACTS: Add __CPROVER_frees clauses
Add the following to the front-end: - add `__CPROVER_frees` clause for function and loop contracts in the parser and internal data structures - add `__CPROVER_freeable` built-in function - add `__CPROVER_is_freeable` built-in predicate - add `__CPROVER_is_freed` built-in predicate Effective support for the new clause type and related predicates will come in a later PR.
1 parent af8e924 commit e2b70d7

File tree

35 files changed

+732
-15
lines changed

35 files changed

+732
-15
lines changed

doc/cprover-manual/contracts-frees.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
[CPROVER Manual TOC](../../)
2+
3+
# Frees Clauses
4+
5+
A _frees clause_ allows the user to specify a set of pointers that may be freed
6+
by a function or the body of a loop. A function or loop contract contract may
7+
have zero or more frees clauses. When no clause is provided the empty set is
8+
used as default. When more than one frees clause is given, the sets of pointers
9+
they contain are unioned together to yield a single set of pointers.
10+
11+
## Syntax
12+
13+
```c
14+
__CPROVER_frees(targets)
15+
```
16+
17+
Where `targets` has the following syntax:
18+
19+
```
20+
targets ::= cond-target-group (';' cond-target-group)* ';'?
21+
cond-target-group ::= (condition ':')? target (',' target)*
22+
target ::= lvalue-expr
23+
| __CPROVER_freeable(lvalue-expr)
24+
```
25+
26+
A frees clause target must be eiter:
27+
- an lvalue expression with a pointer type,
28+
- a call to the built-in function `__CPROVER_freeable`
29+
- a call to a user-defined side effect free and deterministic function returning
30+
the type `void` (itself containing direct or indirect calls to
31+
`__CPROVER_freeable` or to functions that call `__CPROVER_freeable`);
32+
33+
34+
### Examples
35+
36+
In a function contract
37+
```c
38+
int foo(char *arr1, char *arr2, size_t size)
39+
__CPROVER_frees(
40+
// arr1 freeable only if the condition holds
41+
size > 0 && arr1: arr1;
42+
// arr2 always freeable
43+
arr2
44+
)
45+
{
46+
if(size > 0 && arr1)
47+
free(arr1);
48+
free(arr2);
49+
return 0;
50+
}
51+
```
52+
53+
In a loop contract:
54+
55+
```c
56+
int main()
57+
{
58+
size_t size = 10;
59+
char *arr = malloc(size);
60+
61+
for(size_t i = 0; i <= size; i++)
62+
// clang-format off
63+
__CPROVER_assigns(i, __CPROVER_POINTER_OBJECT(arr))
64+
__CPROVER_frees(arr)
65+
// clang-format on2
66+
{
67+
if(i < size)
68+
arr[i] = 0;
69+
else
70+
free(arr);
71+
}
72+
return 0;
73+
}
74+
```
75+
76+
## Semantics
77+
78+
The set of pointers specified by the frees clause of the contract is interpreted
79+
at the function call-site for function contracts, and right before entering the
80+
loop for loop contracts.
81+
82+
### For contract checking
83+
When checking a contract against a function or a loop, each pointer that the
84+
function or loop body attempts to free gets checked for membership in the set of
85+
pointers specified by the contract.
86+
87+
### For replacement of function calls or loops by contracts
88+
When replacing a function call or a loop by a contract, each pointer of the
89+
_frees_ clause is non-deterministically freed after the function call
90+
or after the loop.
91+
92+
## Specifying parametric sets of freeable pointers using C functions
93+
94+
Users can define parametric sets of freeable pointers by writing functions that
95+
return the built-in type void and call the built-in function
96+
`__CPROVER_freeable` (directly or indirectly through some other user-defined
97+
function):
98+
99+
```c
100+
void my_freeable_set(char **arr, size_t size)
101+
{
102+
if (arr && size > 3) {
103+
__CPROVER_freeable(arr[0]);
104+
__CPROVER_freeable(arr[1]);
105+
__CPROVER_freeable(arr[2]);
106+
}
107+
}
108+
```
109+
110+
The built-in function:
111+
112+
```c
113+
void __CPROVER_freeable(void *ptr);
114+
```
115+
adds the given pointer to the freeable set described by the enclosing function.
116+
117+
Calls to functions returning void can then be used as targets in
118+
`__CPROVER_frees` clauses:
119+
120+
```c
121+
void my_function(char **arr, size_t size)
122+
__CPROVER_frees(my_freeable_set(arr, size))
123+
{
124+
...
125+
}
126+
```
127+
128+
## Frees clause related predicates
129+
130+
The predicate:
131+
132+
```c
133+
__CPROVER_bool __CPROVER_is_freeable(void *ptr);
134+
```
135+
can only be used in pre and post conditions, in contract checking or replacement
136+
modes. It returns `true` if and only if the pointer satisfies the preconditions
137+
of the `free` function from `stdlib.h`, that is if and only if the pointer
138+
points to a valid dynamically allocated object and has offset zero.
139+
140+
The predicate:
141+
142+
```c
143+
__CPROVER_bool __CPROVER_is_freed(void *ptr);
144+
```
145+
can only be used in post conditions and returns `true` if and only if the
146+
pointer was freed during the execution of the function or the loop under
147+
analysis.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
void foo_frees(char *arr, const size_t size, const size_t new_size)
5+
{
6+
__CPROVER_freeable(arr);
7+
}
8+
9+
char *foo(char *arr, const size_t size, const size_t new_size)
10+
// clang-format off
11+
// error is_freed cannot be used in preconditions
12+
__CPROVER_requires(!__CPROVER_is_freed(arr))
13+
__CPROVER_requires(__CPROVER_is_freeable(arr))
14+
__CPROVER_assigns(__CPROVER_object_whole(arr))
15+
__CPROVER_frees(foo_frees(arr, size, new_size))
16+
__CPROVER_ensures(
17+
(arr && new_size > size) ==>
18+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
19+
__CPROVER_ensures(
20+
(arr && new_size > size) ==>
21+
__CPROVER_is_freed(__CPROVER_old(arr)))
22+
__CPROVER_ensures(
23+
!(arr && new_size > size) ==>
24+
__CPROVER_return_value == __CPROVER_old(arr))
25+
// clang-format on
26+
{
27+
if(arr && new_size > size)
28+
{
29+
free(arr);
30+
return malloc(new_size);
31+
}
32+
else
33+
{
34+
return arr;
35+
}
36+
}
37+
38+
int main()
39+
{
40+
size_t size;
41+
size_t new_size;
42+
char *arr = malloc(size);
43+
arr = foo(arr, size, new_size);
44+
return 0;
45+
}
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+
^main.c.* error: __CPROVER_is_freed is not allowed in preconditions.$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front end rejects __CPROVER_is_freed in preconditions.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
int foo_frees(char *arr, const size_t size, const size_t new_size)
5+
{
6+
__CPROVER_freeable(arr);
7+
return 0;
8+
}
9+
10+
char *foo(char *arr, const size_t size, const size_t new_size)
11+
// clang-format off
12+
__CPROVER_requires(__CPROVER_is_freeable(arr))
13+
__CPROVER_assigns(__CPROVER_object_whole(arr))
14+
__CPROVER_frees(foo_frees(arr, size, new_size))
15+
__CPROVER_ensures(
16+
(arr && new_size > size) ==>
17+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
18+
__CPROVER_ensures(
19+
(arr && new_size > size) ==>
20+
__CPROVER_is_freed(__CPROVER_old(arr)))
21+
__CPROVER_ensures(
22+
!(arr && new_size > size) ==>
23+
__CPROVER_return_value == __CPROVER_old(arr))
24+
// clang-format on
25+
{
26+
if(arr && new_size > size)
27+
{
28+
free(arr);
29+
return malloc(new_size);
30+
}
31+
else
32+
{
33+
return arr;
34+
}
35+
}
36+
37+
int main()
38+
{
39+
size_t size;
40+
size_t new_size;
41+
char *arr = malloc(size);
42+
arr = foo(arr, size, new_size);
43+
return 0;
44+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^main.c.* error: expecting void return type for function foo_frees called in frees clause$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front-end rejects non-void-typed
11+
function calls in frees clauses.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
void foo_frees(char *arr, const size_t size, const size_t new_size)
5+
{
6+
__CPROVER_freeable(arr);
7+
}
8+
9+
char *foo(char *arr, const size_t size, const size_t new_size)
10+
// clang-format off
11+
__CPROVER_requires(__CPROVER_is_freeable(arr))
12+
__CPROVER_assigns(__CPROVER_object_whole(arr))
13+
__CPROVER_frees(foo_frees(arr, size, new_size))
14+
__CPROVER_ensures(
15+
(arr && new_size > size) ==>
16+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
17+
__CPROVER_ensures(
18+
(arr && new_size > size) ==>
19+
__CPROVER_is_freed(__CPROVER_old(arr)))
20+
__CPROVER_ensures(
21+
!(arr && new_size > size) ==>
22+
__CPROVER_return_value == __CPROVER_old(arr))
23+
// clang-format on
24+
{
25+
if(arr && new_size > size)
26+
{
27+
free(arr);
28+
return malloc(new_size);
29+
}
30+
else
31+
{
32+
return arr;
33+
}
34+
}
35+
36+
int main()
37+
{
38+
size_t size;
39+
size_t new_size;
40+
char *arr = malloc(size);
41+
arr = foo(arr, size, new_size);
42+
return 0;
43+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^\[foo.postcondition.\d+\] line \d+ Check ensures clause: FAILURE$
5+
^VERIFICATION FAILED$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks that the front end parses and typchecks correct uses of:
11+
- void function calls as frees clause targets
12+
- the predicate __CPROVER_freeable
13+
- the predicate __CPROVER_is_freeable
14+
- the predicate __CPROVER_is_freed
15+
16+
The post condition of the contract is expected to fail because the predicates
17+
have no interpretation in the back-end yet.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <stdlib.h>
2+
int main()
3+
{
4+
size_t size = 10;
5+
char *arr = malloc(size);
6+
7+
for(size_t i = 0; i <= size; i++)
8+
// clang-format off
9+
__CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr))
10+
__CPROVER_frees(arr[2])
11+
// clang-format on
12+
{
13+
if(i == 2)
14+
arr[i] = 0;
15+
if(i == size)
16+
free(arr);
17+
}
18+
return 0;
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^main.c.* error: frees clause target must be a pointer-typed expression or a call to a function returning void$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test is expected trigger a typchecking error on a frees clause target.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <stdlib.h>
2+
int main()
3+
{
4+
size_t size = 10;
5+
char *arr = malloc(size);
6+
7+
for(size_t i = 0; i <= size; i++)
8+
// clang-format off
9+
__CPROVER_assigns(i, arr[2], __CPROVER_POINTER_OBJECT(arr))
10+
__CPROVER_frees(arr)
11+
// clang-format on
12+
{
13+
if(i == 2)
14+
arr[i] = 0;
15+
if(i == size)
16+
free(arr);
17+
}
18+
return 0;
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--apply-loop-contracts
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
--
9+
This test checks that frees clauses are parsed and typechecked for for loops.

0 commit comments

Comments
 (0)