Skip to content

Commit 70acafe

Browse files
author
Remi Delmas
committed
CONTRACTS: Front-end extensions for __CPROVER_frees clauses
Add the following to the front-end: - add __CPROVER_frees as a new contract clause for functons and loops - add `__CPROVER_freeable_t` built-in type - add `__CPROVER_freeable` built-in function - allow calls to `__CPROVER_freeable_t` functions in frees clauses - add `__CPROVER_is_freeable` built-in predicate - add `__CPROVER_is_freed` built-in predicate The __CPROVER_frees clause and predicates are not yet supported in the back-end.
1 parent 3699ac2 commit 70acafe

File tree

40 files changed

+806
-15
lines changed

40 files changed

+806
-15
lines changed

doc/cprover-manual/contracts-frees.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
[CPROVER Manual TOC](../../)
2+
3+
# Frees Clauses
4+
5+
A _frees clause_ lets the user specify the set of pointers to dynamically
6+
allocated objects that may be freed by a function or a loop.
7+
8+
## Syntax
9+
10+
The clause has the following syntax:
11+
12+
```c
13+
__CPROVER_frees( *targets* )
14+
```
15+
16+
Where `targets` have the following syntax:
17+
18+
```
19+
targets ::= conditional-target-group (';' conditional-target-group)* ';'?
20+
conditional-target-group ::= (condition ':')? target (',' target)*
21+
target ::= pointer-typed-expression
22+
| user-defined-function(args)
23+
```
24+
25+
Where `user-defined-function` is a user-defined function that must return
26+
the built-in type `__CPROVER_freeable_t` and be side-effect free.
27+
28+
The condition expression may contain calls to side-effect-free functions.
29+
30+
For function contracts, the target expressions may only involve function
31+
parameters, and variable or function identifiers defined in the global scope.
32+
33+
For a loop contract, the target expressions may involve any identifier that is
34+
in scope at loop entry (parameters of the surrounding function, local variables,
35+
global variables, function identifiers, etc.).
36+
37+
### Pointer-typed targets
38+
39+
Pointer-typed targets can be directly listed in the clause.
40+
41+
In a function contract:
42+
```c
43+
int foo(char *arr1, char *arr2, size_t size)
44+
__CPROVER_frees(
45+
// arr1 freeable only if the condition holds
46+
size > 0 && arr1: arr1;
47+
// arr2 always freeable
48+
arr2
49+
)
50+
{
51+
if(size > 0 && arr1)
52+
free(arr1);
53+
54+
free(arr2);
55+
return 0;
56+
}
57+
```
58+
59+
In a loop contract:
60+
61+
```c
62+
int main()
63+
{
64+
size_t size = 10;
65+
char *arr = malloc(size);
66+
67+
for(size_t i = 0; i <= size; i++)
68+
__CPROVER_assigns(i, __CPROVER_POINTER_OBJECT(arr))
69+
__CPROVER_frees(arr)
70+
{
71+
if(i < size)
72+
arr[i] = 0;
73+
else
74+
free(arr);
75+
}
76+
return 0;
77+
}
78+
```
79+
80+
## Using calls to __CPROVER_freable_t functions as targets
81+
82+
Users can define parametric sets of freeable pointers using functions that
83+
return the built-in type `__CPROVER_freeable_t`.
84+
85+
```c
86+
__CPROVER_freeable_t my_freeable_set(char **arr, size_t size)
87+
{
88+
if (arr && size > 3) {
89+
__CPROVER_freeable(arr[0]);
90+
__CPROVER_freeable(arr[1]);
91+
__CPROVER_freeable(arr[2]);
92+
}
93+
}
94+
```
95+
96+
In such functions, calls to the built-in function:
97+
98+
```c
99+
__CPROVER_freeable_t __CPROVER_freeable(void *ptr);
100+
```
101+
102+
add the given pointer to the freeable set described by the enclosing function.
103+
One can also call user-defined functions returning `__CPROVER_freeable_t`, which
104+
also add their targets to the set defined by the enclosing function.
105+
106+
Calls to `__CPROVER_freeable_t` functions are accepted as targets in
107+
`__CPROVER_frees` clauses:
108+
109+
```c
110+
void my_function(char **arr, size_t size)
111+
// adds the locations defined by my_freeable_set to the freeable locations for
112+
// my_function.
113+
__CPROVER_frees(my_freeable_set(arr, size))
114+
{
115+
...
116+
}
117+
```
118+
## Semantics
119+
120+
The set of pointers specified by the frees clause of the contract is interpreted
121+
at the function call-site for function contracts, and right before entering the
122+
loop for loop contracts.
123+
124+
### For contract checking
125+
When checking a contract against a function or a loop, each pointer that the
126+
function or loop body attempts to free gets checked for membership in the set of
127+
pointers specified by the contract.
128+
129+
### For replacement of function calls (or loops) by contracts
130+
When replacing a function call or a loop by a contract, each pointer of the
131+
_frees clause_ that satisfies the preconditions of the `free` function defined
132+
in `stdlib.h` (the pointer points to a valid dynamically allocated object and
133+
has offset zero) is non-deterministically freed.
134+
135+
# Specifying pre and post conditions about freeable pointers
136+
137+
Two predicates meant to express pre- and post-conditions about pointers declared
138+
as freeable in the _frees clause_ are provided to the user.
139+
140+
## In pre-conditions
141+
142+
The predicate
143+
144+
```c
145+
__CPROVER_bool __CPROVER_is_freeable(void *ptr);
146+
```
147+
can only be used in pre-conditions, in contract checking or replacement
148+
modes. It returns `true` if and only if the pointer satisfies the preconditions
149+
of the `free` function from `stdlib.h`, that is if and only if the pointer
150+
points to a valid dynamically allocated object and has offset zero.
151+
152+
## In post-conditions
153+
154+
The predicate
155+
156+
```c
157+
__CPROVER_bool __CPROVER_is_freed(void *ptr);
158+
```
159+
can only be used in post-conditions and returns `true` if and only if the
160+
pointer was effectively freed during the execution of the function or the
161+
loop under analysis.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <stdlib.h>
2+
3+
// A function defining a conditionally freeable target
4+
__CPROVER_freeable_t
5+
foo_frees(char *arr, const size_t size, const size_t new_size)
6+
{
7+
__CPROVER_freeable(arr);
8+
}
9+
10+
char *foo(char *arr, const size_t size, const size_t new_size)
11+
// clang-format off
12+
// error is_freed cannot be used in preconditions
13+
__CPROVER_requires(!__CPROVER_is_freed(arr))
14+
__CPROVER_requires(__CPROVER_is_freeable(arr))
15+
__CPROVER_assigns(__CPROVER_object_whole(arr))
16+
__CPROVER_frees(foo_frees(arr, size, new_size))
17+
__CPROVER_ensures(
18+
(arr && new_size > size) ==>
19+
__CPROVER_is_fresh(__CPROVER_return_value, new_size))
20+
__CPROVER_ensures(
21+
(arr && new_size > size) ==>
22+
__CPROVER_is_freed(__CPROVER_old(arr)))
23+
__CPROVER_ensures(
24+
!(arr && new_size > size) ==>
25+
__CPROVER_return_value == __CPROVER_old(arr))
26+
// clang-format on
27+
{
28+
if(arr && new_size > size)
29+
{
30+
free(arr);
31+
return malloc(new_size);
32+
}
33+
else
34+
{
35+
return arr;
36+
}
37+
}
38+
39+
int main()
40+
{
41+
size_t size;
42+
size_t new_size;
43+
char *arr = malloc(size);
44+
arr = foo(arr, size, new_size);
45+
return 0;
46+
}
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: 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: 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 __CPROVER_freeable_t 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-__CPROVER_freeable_t-typed
11+
function calls in frees clauses.
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 freeable target
4+
__CPROVER_freeable_t
5+
foo_frees(char *arr, const size_t size, const size_t new_size)
6+
{
7+
__CPROVER_freeable(arr);
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: 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+
- __CPROVER_freeable_t 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 __CPROVER_freeable_t$
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)