Skip to content

Commit 178df3d

Browse files
Merge pull request #7091 from remi-delmas-3000/contracts-frees-clauses
CONTRACTS: Add __CPROVER_frees clauses in the front end
2 parents 057fe4e + 5982243 commit 178df3d

File tree

26 files changed

+529
-4
lines changed

26 files changed

+529
-4
lines changed

doc/cprover-manual/contracts-frees.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 by the function it calls, transitively.
7+
A function contract may have zero or more frees clauses.
8+
When no clause is provided the empty set is used as default.
9+
Contracts can also have an empty frees clause.
10+
When more than one frees clause is given, the sets of pointers they contain are
11+
unioned together to yield a single set of pointers.
12+
13+
## Syntax
14+
15+
The clause has the following syntax (square brackets denote optional expressions
16+
`[` `]` ):
17+
```c
18+
__CPROVER_frees([targets])
19+
```
20+
21+
Where `targets` has the following syntax:
22+
```
23+
targets ::= cond-target-group (';' cond-target-group)* ';'?
24+
cond-target-group ::= (condition ':')? target (',' target)*
25+
target ::= lvalue-expr
26+
| __CPROVER_freeable(lvalue-expr)
27+
```
28+
29+
A frees clause target must be either:
30+
- an lvalue expression with a pointer type,
31+
- a call to the built-in function `__CPROVER_freeable`
32+
- a call to a user-defined side effect free and deterministic function returning
33+
the type `void` (itself containing direct or indirect calls to
34+
`__CPROVER_freeable` or to functions that call `__CPROVER_freeable`);
35+
36+
### Example
37+
38+
```c
39+
int foo(char *arr1, char *arr2, size_t size)
40+
__CPROVER_frees(
41+
// `arr1` is freeable only if the condition `size > 0 && arr1` holds
42+
size > 0 && arr1: arr1;
43+
44+
// `arr2` is always freeable
45+
arr2;
46+
)
47+
{
48+
if(size > 0 && arr1)
49+
free(arr1);
50+
free(arr2);
51+
return 0;
52+
}
53+
```
54+
55+
## Semantics
56+
57+
The set of pointers specified by the frees clause of the contract is interpreted
58+
at the function call-site where the contract is being checked or used to abstract
59+
a function call.
60+
61+
### For contract checking
62+
63+
When checking a contract against a function, each pointer that the
64+
function attempts to free gets checked for membership in the set of
65+
pointers specified by the _frees clause_.
66+
67+
### For replacement of function calls by contracts
68+
69+
When replacing a function call by a contract, each pointer of the
70+
_frees clause_ is non-deterministically freed after the function call.
71+
72+
## Specifying parametric sets of freeable pointers using C functions
73+
74+
Users can define parametric sets of freeable pointers by writing functions that
75+
return the built-in type void and call the built-in function
76+
`__CPROVER_freeable` (directly or indirectly through some other user-defined
77+
function). The functions must be side-effect free and deterministic,
78+
as well as loop-free and recursion-free.
79+
80+
```c
81+
void my_freeable_set(char **arr, size_t size)
82+
{
83+
// The first 3 pointers are freeable
84+
// if the array is at least of size 3.
85+
if (arr && size > 3) {
86+
__CPROVER_freeable(arr[0]);
87+
__CPROVER_freeable(arr[1]);
88+
__CPROVER_freeable(arr[2]);
89+
}
90+
}
91+
```
92+
93+
The built-in function:
94+
```c
95+
void __CPROVER_freeable(void *ptr);
96+
```
97+
adds the given pointer to the freeable set described by the enclosing function.
98+
99+
Calls to such functions can be used as targets in `__CPROVER_frees` clauses:
100+
```c
101+
void my_function(char **arr, size_t size)
102+
__CPROVER_frees(my_freeable_set(arr, size))
103+
{
104+
...
105+
}
106+
```
107+
108+
## Frees clause related predicates
109+
110+
The predicate:
111+
```c
112+
__CPROVER_bool __CPROVER_is_freeable(void *ptr);
113+
```
114+
can only be used in pre and post conditions, in contract checking or replacement
115+
modes. It returns `true` if and only if the pointer satisfies the preconditions
116+
of the `free` function from `stdlib.h`
117+
([see here](https://github.com/diffblue/cbmc/blob/cf00a53bbcc388748be9668f393276f6d84b1a60/src/ansi-c/library/stdlib.c#L269)),
118+
that is if and only if the pointer points to a valid dynamically allocated object and has offset zero.
119+
120+
The predicate:
121+
```c
122+
__CPROVER_bool __CPROVER_was_freed(void *ptr);
123+
```
124+
can only be used in post conditions and returns `true` if and only if the
125+
pointer was freed during the execution of the function under 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 was_freed cannot be used in preconditions
12+
__CPROVER_requires(!__CPROVER_was_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_was_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_was_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_was_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_was_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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <stdlib.h>
2+
3+
void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr, 1))
4+
{
5+
}
6+
7+
int main()
8+
{
9+
size_t size;
10+
char arr[size];
11+
foo(arr);
12+
return 0;
13+
}
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: wrong number of function arguments: expected 1, but got 2$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks bad uses of __CPROVER_is_freeable are rejected.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <stdlib.h>
2+
3+
void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr))
4+
__CPROVER_ensures(__CPROVER_was_freed(__CPROVER_old(arr), 1))
5+
{
6+
free(arr);
7+
}
8+
9+
int main()
10+
{
11+
size_t size;
12+
char arr[size];
13+
foo(arr);
14+
return 0;
15+
}
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: wrong number of function arguments: expected 1, but got 2$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks bad uses of __CPROVER_was_freed are rejected.
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 freeable pointers
4+
void foo_frees(char *arr, const size_t size, const size_t new_size)
5+
{
6+
__CPROVER_freeable(arr);
7+
}
8+
9+
// Function that resizes the array
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_was_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 typechecks 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_was_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.

src/ansi-c/ansi_c_convert_type.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ void ansi_c_convert_typet::read_rec(const typet &type)
273273
for(const exprt &target : to_unary_expr(as_expr).op().operands())
274274
assigns.push_back(target);
275275
}
276+
else if(type.id() == ID_C_spec_frees)
277+
{
278+
const exprt &as_expr =
279+
static_cast<const exprt &>(static_cast<const irept &>(type));
280+
281+
for(const exprt &target : to_unary_expr(as_expr).op().operands())
282+
frees.push_back(target);
283+
}
276284
else if(type.id() == ID_C_spec_ensures)
277285
{
278286
const exprt &as_expr =
@@ -341,6 +349,9 @@ void ansi_c_convert_typet::write(typet &type)
341349
if(!assigns.empty())
342350
to_code_with_contract_type(type).assigns() = std::move(assigns);
343351

352+
if(!frees.empty())
353+
to_code_with_contract_type(type).frees() = std::move(frees);
354+
344355
if(!ensures.empty())
345356
to_code_with_contract_type(type).ensures() = std::move(ensures);
346357

src/ansi-c/ansi_c_convert_type.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ansi_c_convert_typet:public messaget
4747
bool constructor, destructor;
4848

4949
// contracts
50-
exprt::operandst assigns, ensures, requires, ensures_contract,
50+
exprt::operandst assigns, frees, ensures, requires, ensures_contract,
5151
requires_contract;
5252

5353
// storage spec

0 commit comments

Comments
 (0)