-
Notifications
You must be signed in to change notification settings - Fork 274
CONTRACTS: Add __CPROVER_frees clauses in the front end #7091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,125 @@ | ||||||
[CPROVER Manual TOC](../../) | ||||||
|
||||||
# Frees Clauses | ||||||
|
||||||
A _frees clause_ allows the user to specify a set of pointers that may be freed | ||||||
by a function or by the function it calls, transitively. | ||||||
A function contract may have zero or more frees clauses. | ||||||
When no clause is provided the empty set is used as default. | ||||||
Contracts can also have an empty frees clause. | ||||||
When more than one frees clause is given, the sets of pointers they contain are | ||||||
unioned together to yield a single set of pointers. | ||||||
|
||||||
## Syntax | ||||||
|
||||||
The clause has the following syntax (square brackets denote optional expressions | ||||||
`[` `]` ): | ||||||
```c | ||||||
__CPROVER_frees([targets]) | ||||||
``` | ||||||
|
||||||
Where `targets` has the following syntax: | ||||||
``` | ||||||
targets ::= cond-target-group (';' cond-target-group)* ';'? | ||||||
cond-target-group ::= (condition ':')? target (',' target)* | ||||||
target ::= lvalue-expr | ||||||
| __CPROVER_freeable(lvalue-expr) | ||||||
``` | ||||||
|
||||||
A frees clause target must be either: | ||||||
- an lvalue expression with a pointer type, | ||||||
- a call to the built-in function `__CPROVER_freeable` | ||||||
- a call to a user-defined side effect free and deterministic function returning | ||||||
the type `void` (itself containing direct or indirect calls to | ||||||
`__CPROVER_freeable` or to functions that call `__CPROVER_freeable`); | ||||||
|
||||||
### Example | ||||||
|
||||||
```c | ||||||
int foo(char *arr1, char *arr2, size_t size) | ||||||
__CPROVER_frees( | ||||||
// `arr1` is freeable only if the condition `size > 0 && arr1` holds | ||||||
size > 0 && arr1: arr1; | ||||||
|
||||||
// `arr2` is always freeable | ||||||
arr2; | ||||||
) | ||||||
{ | ||||||
if(size > 0 && arr1) | ||||||
free(arr1); | ||||||
free(arr2); | ||||||
return 0; | ||||||
} | ||||||
``` | ||||||
|
||||||
## Semantics | ||||||
|
||||||
The set of pointers specified by the frees clause of the contract is interpreted | ||||||
at the function call-site where the contract is being checked or used to abstract | ||||||
a function call. | ||||||
|
||||||
### For contract checking | ||||||
|
||||||
When checking a contract against a function, each pointer that the | ||||||
function attempts to free gets checked for membership in the set of | ||||||
pointers specified by the _frees clause_. | ||||||
|
||||||
### For replacement of function calls by contracts | ||||||
|
||||||
When replacing a function call by a contract, each pointer of the | ||||||
_frees clause_ is non-deterministically freed after the function call. | ||||||
|
||||||
## Specifying parametric sets of freeable pointers using C functions | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any restrictions on what control flow is permitted in such functions? For example, are recursion or loops without constant bound permitted? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in this first version recursion is prohibited, the function body is going to be inlined, and any remaining loops must be unwound to completion before the analysis. Adding this to the documentation. |
||||||
|
||||||
Users can define parametric sets of freeable pointers by writing functions that | ||||||
return the built-in type void and call the built-in function | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||||||
`__CPROVER_freeable` (directly or indirectly through some other user-defined | ||||||
function). The functions must be side-effect free and deterministic, | ||||||
as well as loop-free and recursion-free. | ||||||
|
||||||
```c | ||||||
void my_freeable_set(char **arr, size_t size) | ||||||
{ | ||||||
// The first 3 pointers are freeable | ||||||
// if the array is at least of size 3. | ||||||
if (arr && size > 3) { | ||||||
__CPROVER_freeable(arr[0]); | ||||||
__CPROVER_freeable(arr[1]); | ||||||
__CPROVER_freeable(arr[2]); | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
The built-in function: | ||||||
```c | ||||||
void __CPROVER_freeable(void *ptr); | ||||||
``` | ||||||
adds the given pointer to the freeable set described by the enclosing function. | ||||||
|
||||||
Calls to such functions can be used as targets in `__CPROVER_frees` clauses: | ||||||
```c | ||||||
void my_function(char **arr, size_t size) | ||||||
__CPROVER_frees(my_freeable_set(arr, size)) | ||||||
{ | ||||||
... | ||||||
} | ||||||
``` | ||||||
|
||||||
## Frees clause related predicates | ||||||
|
||||||
The predicate: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for this line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
```c | ||||||
__CPROVER_bool __CPROVER_is_freeable(void *ptr); | ||||||
``` | ||||||
can only be used in pre and post conditions, in contract checking or replacement | ||||||
modes. It returns `true` if and only if the pointer satisfies the preconditions | ||||||
of the `free` function from `stdlib.h` | ||||||
([see here](https://github.com/diffblue/cbmc/blob/cf00a53bbcc388748be9668f393276f6d84b1a60/src/ansi-c/library/stdlib.c#L269)), | ||||||
that is if and only if the pointer points to a valid dynamically allocated object and has offset zero. | ||||||
|
||||||
The predicate: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for this line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
```c | ||||||
__CPROVER_bool __CPROVER_was_freed(void *ptr); | ||||||
``` | ||||||
can only be used in post conditions and returns `true` if and only if the | ||||||
pointer was freed during the execution of the function under analysis. | ||||||
Comment on lines
+124
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this also work when the pointer is re-allocated after having been freed? As in:
with the call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll change the name of the predicate to In a situation like this both The instrumentation keeps track of deallocation events by recording ids of deallocated objects in a map, that gets passed to the predicate as a ghost parameter for evaluation. if we wanted instead to assert that a pointer does not point into a live object as a post we would need to track the lifetime and current size of all objects in the program separately (without nondeterminism as is currently done). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#include <stdlib.h> | ||
|
||
// A function defining a conditionally freeable target | ||
void foo_frees(char *arr, const size_t size, const size_t new_size) | ||
{ | ||
__CPROVER_freeable(arr); | ||
} | ||
|
||
char *foo(char *arr, const size_t size, const size_t new_size) | ||
// clang-format off | ||
// error was_freed cannot be used in preconditions | ||
__CPROVER_requires(!__CPROVER_was_freed(arr)) | ||
__CPROVER_requires(__CPROVER_is_freeable(arr)) | ||
__CPROVER_assigns(__CPROVER_object_whole(arr)) | ||
__CPROVER_frees(foo_frees(arr, size, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_is_fresh(__CPROVER_return_value, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_was_freed(__CPROVER_old(arr))) | ||
__CPROVER_ensures( | ||
!(arr && new_size > size) ==> | ||
__CPROVER_return_value == __CPROVER_old(arr)) | ||
// clang-format on | ||
{ | ||
if(arr && new_size > size) | ||
{ | ||
free(arr); | ||
return malloc(new_size); | ||
} | ||
else | ||
{ | ||
return arr; | ||
} | ||
} | ||
|
||
int main() | ||
{ | ||
size_t size; | ||
size_t new_size; | ||
char *arr = malloc(size); | ||
arr = foo(arr, size, new_size); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CORE | ||
main.c | ||
--enforce-contract foo | ||
^main.c.* error: __CPROVER_was_freed is not allowed in preconditions.$ | ||
^CONVERSION ERROR$ | ||
^EXIT=(1|64)$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test checks that the front end rejects __CPROVER_was_freed in preconditions. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include <stdlib.h> | ||
|
||
// A function defining a conditionally freeable target | ||
int foo_frees(char *arr, const size_t size, const size_t new_size) | ||
{ | ||
__CPROVER_freeable(arr); | ||
return 0; | ||
} | ||
|
||
char *foo(char *arr, const size_t size, const size_t new_size) | ||
// clang-format off | ||
__CPROVER_requires(__CPROVER_is_freeable(arr)) | ||
__CPROVER_assigns(__CPROVER_object_whole(arr)) | ||
__CPROVER_frees(foo_frees(arr, size, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_is_fresh(__CPROVER_return_value, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_was_freed(__CPROVER_old(arr))) | ||
__CPROVER_ensures( | ||
!(arr && new_size > size) ==> | ||
__CPROVER_return_value == __CPROVER_old(arr)) | ||
// clang-format on | ||
{ | ||
if(arr && new_size > size) | ||
{ | ||
free(arr); | ||
return malloc(new_size); | ||
} | ||
else | ||
{ | ||
return arr; | ||
} | ||
} | ||
|
||
int main() | ||
{ | ||
size_t size; | ||
size_t new_size; | ||
char *arr = malloc(size); | ||
arr = foo(arr, size, new_size); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
CORE | ||
main.c | ||
--enforce-contract foo | ||
^main.c.* error: expecting void return type for function 'foo_frees' called in frees clause$ | ||
^CONVERSION ERROR$ | ||
^EXIT=(1|64)$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test checks that the front-end rejects non-void-typed | ||
function calls in frees clauses. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#include <stdlib.h> | ||
|
||
void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr, 1)) | ||
{ | ||
} | ||
|
||
int main() | ||
{ | ||
size_t size; | ||
char arr[size]; | ||
foo(arr); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CORE | ||
main.c | ||
--enforce-contract foo | ||
^main.c.*error: wrong number of function arguments: expected 1, but got 2$ | ||
^CONVERSION ERROR$ | ||
^EXIT=(1|64)$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test checks bad uses of __CPROVER_is_freeable are rejected. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#include <stdlib.h> | ||
|
||
void foo(char *arr) __CPROVER_requires(__CPROVER_is_freeable(arr)) | ||
__CPROVER_ensures(__CPROVER_was_freed(__CPROVER_old(arr), 1)) | ||
{ | ||
free(arr); | ||
} | ||
|
||
int main() | ||
{ | ||
size_t size; | ||
char arr[size]; | ||
foo(arr); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CORE | ||
main.c | ||
--enforce-contract foo | ||
^main.c.*error: wrong number of function arguments: expected 1, but got 2$ | ||
^CONVERSION ERROR$ | ||
^EXIT=(1|64)$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test checks bad uses of __CPROVER_was_freed are rejected. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include <stdlib.h> | ||
|
||
// A function defining freeable pointers | ||
void foo_frees(char *arr, const size_t size, const size_t new_size) | ||
{ | ||
__CPROVER_freeable(arr); | ||
} | ||
|
||
// Function that resizes the array | ||
char *foo(char *arr, const size_t size, const size_t new_size) | ||
// clang-format off | ||
__CPROVER_requires(__CPROVER_is_freeable(arr)) | ||
__CPROVER_assigns(__CPROVER_object_whole(arr)) | ||
__CPROVER_frees(foo_frees(arr, size, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_is_fresh(__CPROVER_return_value, new_size)) | ||
__CPROVER_ensures( | ||
(arr && new_size > size) ==> | ||
__CPROVER_was_freed(__CPROVER_old(arr))) | ||
__CPROVER_ensures( | ||
!(arr && new_size > size) ==> | ||
__CPROVER_return_value == __CPROVER_old(arr)) | ||
// clang-format on | ||
{ | ||
if(arr && new_size > size) | ||
{ | ||
free(arr); | ||
return malloc(new_size); | ||
} | ||
else | ||
{ | ||
return arr; | ||
} | ||
} | ||
|
||
int main() | ||
{ | ||
size_t size; | ||
size_t new_size; | ||
char *arr = malloc(size); | ||
arr = foo(arr, size, new_size); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
CORE | ||
feliperodri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
main.c | ||
--enforce-contract foo | ||
^\[foo.postcondition.\d+\] line \d+ Check ensures clause: FAILURE$ | ||
^VERIFICATION FAILED$ | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
-- | ||
-- | ||
This test checks that the front end parses and typechecks correct uses of | ||
- void function calls as frees clause targets | ||
- the predicate __CPROVER_freeable | ||
- the predicate __CPROVER_is_freeable | ||
- the predicate __CPROVER_was_freed | ||
|
||
The post condition of the contract is expected to fail because the predicates | ||
have no interpretation in the back-end yet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these examples included as regression tests? If so, perhaps there is some way to link to them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no these are just embedded in the doc.