Skip to content

CONTRACTS: Front-end: frees clause improvements #7087

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions doc/cprover-manual/contracts-assigns.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,46 @@ value(s) therein are not modified.

### Object slice expressions

The following functions can be used in assigns clause to specify ranges of
assignable addresses.
The following functions can be used in assigns clauses to specify ranges of assignable bytes.

Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
specifies that all bytes starting from the given pointer and until the end of
the object are assignable:
Given an lvalue expression `expr` with a complete type `expr_t`,
`__CPROVER_typed_target(expr)` specifies that the range
of `sizeof(expr_t)` bytes starting at `&expr` is assignable:
```c
__CPROVER_size_t __CPROVER_object_from(void *ptr);
__CPROVER_assignable_t __CPROVER_typed_target(expr_t expr);
```

Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr, size)`
specifies that `size` bytes starting from the given pointer and until the end of the object are assignable.
The `size` value must such that `size <= __CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr)` holds:
Given a pointer `ptr` pointing into some object `o`,
`__CPROVER_whole_object(ptr)` specifies that all bytes of the object `o`
are assignable:
```c
__CPROVER_assignable_t __CPROVER_whole_object(void *ptr);
```

Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
specifies that the range of bytes starting from the pointer and until the end of
the object `o` are assignable:
```c
__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size);
__CPROVER_assignable_t __CPROVER_object_from(void *ptr);
```

Caveats and limitations: The slices in question must *not*
be interpreted as pointers by the program. During call-by-contract replacement,
`__CPROVER_havoc_slice(ptr, size)` is used to havoc these targets,
and `__CPROVER_havoc_slice` does not support havocing pointers.
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)`
specifies that the range of `size` bytes of `o` starting at `ptr` are assignable:
The `size` value must such that the range does not exceed the object boundary,
that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold:

```c
__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size);
```

CAVEAT: The ranges specified by `__CPROVER_whole_object`,
`__CPROVER_object_from` and `__CPROVER_object_upto` must *not*
be interpreted as pointers by the program. This is because during
call-by-contract replacement, `__CPROVER_havoc_slice(ptr, size)` is used to
havoc these byte ranges, and `__CPROVER_havoc_slice` does not support
havocing pointers. `__CPROVER_typed_target` must be used to specify targets
that are pointers.

### Parameters

An _assigns_ clause currently supports simple variable types and their pointers,
Expand Down
136 changes: 136 additions & 0 deletions doc/cprover-manual/contracts-frees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
[CPROVER Manual TOC](../../)

# Frees Clause

### Syntax

```c
__CPROVER_frees(*pointer-typed-expression*, ...)
```

A _frees_ clause allows the user to specify a set of pointers that may be freed
by a function or the body of a loop.
A function or loop contract contract may have zero or more _frees_ clauses.

_Frees_ clause targets must be pointer-typed expressions.

_Frees_ clause targets can also be _conditional_ and written as follows:

```
condition: list-of-pointer-typed-expressions;
```

### Examples

In a function contract
```c
int foo(char *arr1, char *arr2, size_t size)
__CPROVER_frees(
// arr1 freeable only if the condition holds
size > 0 && arr1: arr1;
// arr2 always freeable
arr2
)
{
if(size > 0 && arr1)
free(arr1);
free(arr2);
return 0;
}
```

In a loop contract:

```c
int main()
{
size_t size = 10;
char *arr = malloc(size);

for(size_t i = 0; i <= size; i++)
// clang-format off
__CPROVER_assigns(i, __CPROVER_POINTER_OBJECT(arr))
__CPROVER_frees(arr)
// clang-format on2
{
if(i < size)
arr[i] = 0;
else
free(arr);
}
return 0;
}
```

### Semantics

The set of pointers specified by the frees clause of the contract is interpreted
at the function call-site for function contracts, and right before entering the
loop for loop contracts.

#### For contract checking
When checking a contract against a function or a loop, each pointer that the
function or loop body attempts to free gets checked for membership in the set of
pointers specified by the contract.

#### For replacement of function calls or loops by contracts
When replacing a function call or a loop by a contract, each pointer of the
_frees_ clause is non-deterministically freed after the function call
or after the loop.

# Using functions to specify parametric sets of freeable pointers

Users can define parametric sets of freeable pointers by writing functions that
return the built-in type `__CPROVER_freeable_t` and call the built-in function
`__CPROVER_freeable` or any user-defined function returning
`__CPROVER_freeable_t`:

```c
__CPROVER_freeable_t my_freeable_set(char **arr, size_t size)
{
if (arr && size > 3) {
__CPROVER_freeable(arr[0]);
__CPROVER_freeable(arr[1]);
__CPROVER_freeable(arr[2]);
}
}
```

The built-in function:

```c
__CPROVER_freeable_t __CPROVER_freeable(void *ptr);
```
adds the given pointer to the freeable set described by the enclosing function.

Calls to functions returning `__CPROVER_freeable_t` can then 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:

```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`, that is if and only if the pointer
points to a valid dynamically allocated object and has offset zero.

The predicate:

```c
__CPROVER_bool __CPROVER_is_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 or the loop under
analysis.
22 changes: 18 additions & 4 deletions regression/contracts/assigns-slice-targets/main-enforce.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ struct st
int c;
};

void foo(struct st *s)
void foo(struct st *s, struct st *ss)
// clang-format off
__CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s)))
__CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5))
__CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5))
__CPROVER_assigns(
__CPROVER_object_upto(s->arr1, 5);
__CPROVER_object_from(s->arr2 + 5);
__CPROVER_whole_object(ss);
)
// clang-format on
{
// PASS
Expand Down Expand Up @@ -41,13 +44,24 @@ void foo(struct st *s)
s->arr2[7] = 0;
s->arr2[8] = 0;
s->arr2[9] = 0;

// PASS
ss->a = 0;
ss->arr1[0] = 0;
ss->arr1[7] = 0;
ss->arr1[9] = 0;
ss->b = 0;
ss->arr2[6] = 0;
ss->arr2[8] = 0;
ss->c = 0;
}

int main()
{
struct st s;
struct st ss;

foo(&s);
foo(&s, &ss);

return 0;
}
62 changes: 58 additions & 4 deletions regression/contracts/assigns-slice-targets/main-replace.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ struct st
int c;
};

void foo(struct st *s)
void foo(struct st *s, struct st *ss)
// clang-format off
__CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s)))
__CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5))
__CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5))
__CPROVER_assigns(
__CPROVER_object_upto(s->arr1, 5);
__CPROVER_object_from(s->arr2 + 5);
__CPROVER_whole_object(ss);
)
// clang-format on
{
s->arr1[0] = 0;
Expand Down Expand Up @@ -54,7 +57,32 @@ int main()
s.arr2[9] = 0;
s.c = 0;

foo(&s);
struct st ss;
ss.a = 0;
ss.arr1[0] = 0;
ss.arr1[1] = 0;
ss.arr1[2] = 0;
ss.arr1[3] = 0;
ss.arr1[4] = 0;
ss.arr1[5] = 0;
ss.arr1[6] = 0;
ss.arr1[7] = 0;
ss.arr1[8] = 0;
ss.arr1[9] = 0;

ss.arr2[0] = 0;
ss.arr2[1] = 0;
ss.arr2[2] = 0;
ss.arr2[3] = 0;
ss.arr2[4] = 0;
ss.arr2[5] = 0;
ss.arr2[6] = 0;
ss.arr2[7] = 0;
ss.arr2[8] = 0;
ss.arr2[9] = 0;
ss.c = 0;

foo(&s, &ss);

// PASS
assert(s.a == 0);
Expand Down Expand Up @@ -92,5 +120,31 @@ int main()

// PASS
assert(s.c == 0);

// FAIL
assert(ss.a == 0);
assert(ss.arr1[0] == 0);
assert(ss.arr1[1] == 0);
assert(ss.arr1[2] == 0);
assert(ss.arr1[3] == 0);
assert(ss.arr1[4] == 0);
assert(ss.arr1[5] == 0);
assert(ss.arr1[6] == 0);
assert(ss.arr1[7] == 0);
assert(ss.arr1[8] == 0);
assert(ss.arr1[9] == 0);
assert(ss.b == 0);
assert(ss.arr2[0] == 0);
assert(ss.arr2[1] == 0);
assert(ss.arr2[2] == 0);
assert(ss.arr2[3] == 0);
assert(ss.arr2[4] == 0);
assert(ss.arr2[5] == 0);
assert(ss.arr2[6] == 0);
assert(ss.arr2[7] == 0);
assert(ss.arr2[8] == 0);
assert(ss.arr2[9] == 0);
assert(ss.c == 0);

return 0;
}
10 changes: 8 additions & 2 deletions regression/contracts/assigns-slice-targets/test-enforce.desc
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
CORE
main-enforce.c
--enforce-contract foo
^\[foo.assigns.\d+\].* Check that __CPROVER_object_slice\(\(void \*\)s->arr1, \(.*\)5\) is valid: SUCCESS$
^\[foo.assigns.\d+\].* Check that __CPROVER_object_from\(\(void \*\)\(s->arr2 \+ \(.*\)5\)\) is valid: SUCCESS$
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)0\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)1\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that s->arr1\[\(.*\)2\] is assignable: SUCCESS$
Expand All @@ -23,6 +21,14 @@ main-enforce.c
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)7\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)8\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that s->arr2\[\(.*\)9\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->a is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)0\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)7\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->arr1\[\(.*\)9\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->b is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)6\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->arr2\[\(.*\)8\] is assignable: SUCCESS$
^\[foo.assigns.\d+\].* Check that ss->c is assignable: SUCCESS$
^VERIFICATION FAILED$
^EXIT=10$
^SIGNAL=0$
Expand Down
23 changes: 23 additions & 0 deletions regression/contracts/assigns-slice-targets/test-replace.desc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ main-replace.c
^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)8\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)s.arr2\[\(.*\)9\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion s.c == 0: FAILURE$
^\[main.assertion.\d+\].*assertion ss.a == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)0\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)1\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)2\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)3\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)4\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)5\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)6\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)7\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)8\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr1\[\(.*\)9\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion ss.b == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)0\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)1\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)2\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)3\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)4\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)5\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)6\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)7\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)8\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion \(.*\)ss.arr2\[\(.*\)9\] == 0: FAILURE$
^\[main.assertion.\d+\].*assertion ss.c == 0: FAILURE$
^VERIFICATION FAILED$
^EXIT=10$
^SIGNAL=0$
Expand Down
2 changes: 1 addition & 1 deletion regression/contracts/assigns_enforce_address_of/test.desc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ main.c
--enforce-contract foo
^EXIT=(1|64)$
^SIGNAL=0$
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
^CONVERSION ERROR$
--
--
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CORE
main.c
--enforce-contract foo
^.*error: assigns clause target must be an lvalue or a __CPROVER_POINTER_OBJECT, __CPROVER_object_from, __CPROVER_object_slice expression$
^.*error: assigns clause target must be an lvalue or a call to __CPROVER_POINTER_OBJECT or to a function returning __CPROVER_assignable_t$
^CONVERSION ERROR
^EXIT=(1|64)$
^SIGNAL=0$
Expand Down
Loading