Skip to content

Commit f23c73a

Browse files
author
Remi Delmas
committed
CONTRACTS: Add __CPROVER_frees clauses
Add the following to the front-end: - add `__CPROVER_frees` clause for function contracts - 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 f23c73a

File tree

27 files changed

+557
-4
lines changed

27 files changed

+557
-4
lines changed

doc/cprover-manual/contracts-frees.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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. A function contract may have zero or more frees clauses.
7+
When no clause is provided the empty set is used as default.
8+
When more than one frees clause is given, the sets of pointers they contain are
9+
unioned together to yield a single set of pointers.
10+
11+
## Syntax
12+
13+
The clause has the following syntax:
14+
```c
15+
__CPROVER_frees(targets)
16+
```
17+
18+
Where `targets` has the following syntax:
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+
### Example
34+
35+
```c
36+
int foo(char *arr1, char *arr2, size_t size)
37+
__CPROVER_frees(
38+
// `arr1` is freeable only if the condition `size > 0 && arr1` holds
39+
size > 0 && arr1: arr1;
40+
41+
// `arr2` is always freeable
42+
arr2;
43+
)
44+
{
45+
if(size > 0 && arr1)
46+
free(arr1);
47+
free(arr2);
48+
return 0;
49+
}
50+
```
51+
52+
## Semantics
53+
54+
The set of pointers specified by the frees clause of the contract is interpreted
55+
at the function call-site.
56+
57+
### For contract checking
58+
59+
When checking a contract against a function, each pointer that the
60+
function attempts to free gets checked for membership in the set of
61+
pointers specified by the _frees clause_.
62+
63+
### For replacement of function calls by contracts
64+
65+
When replacing a function call by a contract, each pointer of the
66+
_frees clause_ is non-deterministically freed after the function call.
67+
68+
## Specifying parametric sets of freeable pointers using C functions
69+
70+
Users can define parametric sets of freeable pointers by writing functions that
71+
return the built-in type void and call the built-in function
72+
`__CPROVER_freeable` (directly or indirectly through some other user-defined
73+
function):
74+
75+
```c
76+
void my_freeable_set(char **arr, size_t size)
77+
{
78+
// The first 3 pointers are freeable
79+
// if the array is at least of size 3.
80+
if (arr && size > 3) {
81+
__CPROVER_freeable(arr[0]);
82+
__CPROVER_freeable(arr[1]);
83+
__CPROVER_freeable(arr[2]);
84+
}
85+
}
86+
```
87+
88+
The built-in function:
89+
```c
90+
void __CPROVER_freeable(void *ptr);
91+
```
92+
adds the given pointer to the freeable set described by the enclosing function.
93+
94+
Calls to such functions can be used as targets in `__CPROVER_frees` clauses:
95+
```c
96+
void my_function(char **arr, size_t size)
97+
__CPROVER_frees(my_freeable_set(arr, size))
98+
{
99+
...
100+
}
101+
```
102+
103+
## Frees clause related predicates
104+
105+
The predicate:
106+
```c
107+
__CPROVER_bool __CPROVER_is_freeable(void *ptr);
108+
```
109+
can only be used in pre and post conditions, in contract checking or replacement
110+
modes. It returns `true` if and only if the pointer satisfies the preconditions
111+
of the `free` function from `stdlib.h`
112+
([see here](https://github.com/diffblue/cbmc/blob/cf00a53bbcc388748be9668f393276f6d84b1a60/src/ansi-c/library/stdlib.c#L269)),
113+
that is if and only if the pointer points to a valid dynamically allocated object and has offset zero.
114+
115+
The predicate:
116+
```c
117+
__CPROVER_bool __CPROVER_is_freed(void *ptr);
118+
```
119+
can only be used in post conditions and returns `true` if and only if the
120+
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 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: 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: __CPROVER_is_freeable expects one operand; 2 provided$
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_is_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: __CPROVER_is_freed expects one operand; 2 provided$
5+
^CONVERSION ERROR$
6+
^EXIT=(1|64)$
7+
^SIGNAL=0$
8+
--
9+
--
10+
This test checks bad uses of __CPROVER_is_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_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+
- 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.

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

src/ansi-c/ansi_c_internal_additions.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,14 @@ void ansi_c_internal_additions(std::string &code)
227227
" " CPROVER_PREFIX "size_t size);\n"
228228
// Declares bytes from ptr to the end of the object as assignable
229229
"void " CPROVER_PREFIX "object_from(void *ptr);\n"
230-
// Declares the whole object pointer to by ptr
230+
// Declares the whole object pointed to by ptr as assignable
231231
"void " CPROVER_PREFIX "object_whole(void *ptr);\n"
232+
// Declares a pointer as freeable
233+
"void " CPROVER_PREFIX "freeable(void *ptr);\n"
234+
// True iff ptr satisfies the preconditions of the free stdlib function
235+
CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freeable(void *ptr);\n"
236+
// True iff ptr was freed during function execution or loop execution
237+
CPROVER_PREFIX "bool " CPROVER_PREFIX "is_freed(void *ptr);\n"
232238
"\n";
233239
// clang-format on
234240

0 commit comments

Comments
 (0)