Skip to content

Commit 60f8feb

Browse files
author
Remi Delmas
committed
CONTRACTS: Front-end extensions for assigns clauses.
Add new functions to specify assignable targets in assigns clauses: - add type __CPROVER_assignable_t - add polymorhpic builtin __CPROVER_typed_target - add builtin __CPROVER_assignable - add builtin __CPROVER_object_whole - add builtin __CPROVER_object_from - add builtin __CPROVER_object_upto - allow function call expressions as assigns clause targets as long as they have the return type __CPROVER_assignable_t and are one of the built-ins.
1 parent 39f78f1 commit 60f8feb

File tree

38 files changed

+770
-186
lines changed

38 files changed

+770
-186
lines changed

doc/cprover-manual/contracts-assigns.md

Lines changed: 164 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,144 @@
11
[CPROVER Manual TOC](../../)
22

3-
# Assigns Clause
3+
# Assigns Clauses
44

5-
## In Function Contracts
5+
An _assigns_ clause lets the user to specify which memory locations may be
6+
assigned to by a function or a loop.
67

7-
### Syntax
8+
The set of locations described by the contract is the union of the locations
9+
specified by each of its assigns clauses, or the empty set of no _assigns_
10+
clause is specified.
11+
12+
The assigns clause may only refer to locations that exist in the calling context
13+
of the function or before entering the loop, i.e. that are part of the _frame_
14+
of the function call or loop execution.
15+
16+
Any memory that is locally stack- or heap-allocated during function or loop
17+
execution is by definition not part of the frame and always assignable by the
18+
function or the loop.
19+
20+
## Syntax
821

922
```c
10-
__CPROVER_assigns(*identifier*, ...)
23+
__CPROVER_assigns(*targets*)
24+
```
25+
26+
Where targets have the following syntax:
27+
28+
```
29+
targets ::= conditional-target-group (';' conditional-target-group)* ';'?
30+
conditional-target-group ::= (condition ':')? target (',' target)*
31+
target ::= l-value
32+
| __CPROVER_typed_target(l-value)
33+
| __CPROVER_object_whole(pointer-typed-expression)
34+
| __CPROVER_object_from(pointer-typed-expression)
35+
| __CPROVER_object_upto(pointer-typed-expression,
36+
unsigned-integer-typed-expression)
1137
```
1238
13-
An _assigns_ clause allows the user to specify that a memory location may be written by a function. The set of locations writable by a function is the union of the locations specified by the assigns clauses, or the empty set of no _assigns_ clause is specified. While, in general, an _assigns_ clause could be interpreted with either _writes_ or _modifies_ semantics, this
14-
design is based on the former. This means that memory not captured by an
15-
_assigns_ clause must not be written within the given function, even if the
16-
value(s) therein are not modified.
39+
We use the _assigns_ interpretation for these locations (as opposed to _modifies_)
40+
meaning that locations not listed in the contract must not be assigned to
41+
by the function or the loop, even if they end up holding the same value as they
42+
held before the function call or before entering the loop.
1743
18-
### Object slice expressions
44+
### Lvalue targets
1945
20-
The following functions can be used in assigns clause to specify ranges of
21-
assignable addresses.
46+
An l-value target `expr` with a complete type `expr_t` specifies that the range
47+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable.
2248
23-
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
24-
specifies that all bytes starting from the given pointer and until the end of
25-
the object are assignable:
49+
Similarly, given an l-value expression `expr` with a complete type `expr_t`,
50+
`__CPROVER_typed_target(expr)` specifies that the range
51+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable:
2652
```c
27-
__CPROVER_size_t __CPROVER_object_from(void *ptr);
53+
__CPROVER_assignable_t __CPROVER_typed_target(expr_t expr);
2854
```
2955

30-
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr, size)`
31-
specifies that `size` bytes starting from the given pointer and until the end of the object are assignable.
32-
The `size` value must such that `size <= __CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr)` holds:
56+
To specify assignable memory locations
57+
that are interpreted as pointers by the program, one must use
58+
`__CPROVER_assigns(ptr)` or `__CPROVER_assigns(__CPROVER_typed_target(ptr))`.
59+
60+
This is to ensure that during call by contract replacement they will be havoced
61+
as pointers, in a type directed way. For instance:
3362

3463
```c
35-
__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size);
64+
struct circular_buffer_t {
65+
int *first;
66+
int *last;
67+
int *current;
68+
}
69+
70+
void step(struct circular_buffer_t *buf)
71+
// correct
72+
__CPROVER_assigns(__CPROVER_typed_target(buf->current))
73+
// not correct
74+
__CPROVER_assigns(__CPROVER_object_upto(&(buf->current), sizeof(buf->current))
75+
{
76+
if(buf->current == buf->last)
77+
buf->current = buf->first;
78+
else
79+
buf->current += 1;
80+
}
3681
```
3782
38-
Caveats and limitations: The slices in question must *not*
39-
be interpreted as pointers by the program. During call-by-contract replacement,
40-
`__CPROVER_havoc_slice(ptr, size)` is used to havoc these targets,
41-
and `__CPROVER_havoc_slice` does not support havocing pointers.
42-
### Parameters
83+
### Object slice targets
4384
44-
An _assigns_ clause currently supports simple variable types and their pointers,
45-
structs, and arrays. Recursive pointer structures are left to future work, as
46-
their support would require changes to CBMC's memory model.
85+
Given a pointer `ptr` pointing into some object `o`,
86+
`__CPROVER_object_whole(ptr)` specifies that all bytes of the object `o`
87+
are assignable:
88+
```c
89+
__CPROVER_assignable_t __CPROVER_object_whole(void *ptr);
90+
```
91+
REMARK: this also includes bytes in the object that are before the address given
92+
by `ptr`;
93+
94+
95+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
96+
specifies that the range of bytes starting from the pointer and until the end of
97+
the object `o` are assignable:
98+
```c
99+
__CPROVER_assignable_t __CPROVER_object_from(void *ptr);
100+
```
47101
102+
```c
103+
const size_t SIZE = 100;
104+
105+
void init_slice_all(char *arr)
106+
__CPROVER_assigns(__CPROVER_object_from(arr))
107+
{
108+
for(size_t i=0; i<SIZE; i++)
109+
arr[i] = 0;
110+
}
111+
```
112+
113+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)`
114+
specifies that the range of `size` bytes of `o` starting at `ptr` are assignable:
115+
The `size` value must such that the range does not exceed the object boundary,
116+
that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold:
117+
118+
```c
119+
__CPROVER_assignable_t __CPROVER_object_upto(void *ptr, __CPROVER_size_t size);
120+
```
121+
122+
```c
123+
const size_t SIZE = 100;
124+
125+
void init_slice_n(char *arr, size_t n)
126+
__CPROVER_requires(n <= SIZE)
127+
__CPROVER_assigns(__CPROVER_object_upto(arr, n))
128+
{
129+
for(size_t i=0; i<n; i++)
130+
arr[i] = 0;
131+
}
132+
```
133+
### Function parameters
134+
135+
The memory locations storing function parameters values are considered local
136+
to the function and are hence always assignable.
137+
138+
### Inductive data structures
139+
Inductive data structures are not supported yet in assigns clauses.
140+
141+
## Examples
48142
```c
49143
/* Examples */
50144
int err_signal; // Global variable
@@ -59,17 +153,18 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
59153
__CPROVER_assigns(*out, err_signal)
60154
```
61155
62-
### Semantics
156+
## Semantics
63157
64158
The semantics of an _assigns_ clause of a given function can be understood
65-
in two contexts: enforcement and replacement.
159+
in two contexts: contract enforcement and replacement.
66160
67-
#### Enforcement
161+
### Enforcement
68162
69-
In order to determine whether an _assigns_ clause is a sound abstraction of
70-
the write set of a function *f*, the body of the function is instrumented with
163+
In order to determine whether a function (or loop) complies with the _assigns_
164+
clause of the contract, the body of the function (or loop) is instrumented with
71165
assertion statements before each statement which may write to memory (e.g., an
72-
assignment). These assertions are based on the writable locations identified by the _assigns_ clauses.
166+
assignment). These assertions check that the location about to be assigned to
167+
is among the targets specified by the _assigns_ clauses.
73168
74169
For example, consider the following implementation of `sum` function.
75170
@@ -85,8 +180,11 @@ __CPROVER_assigns(*out)
85180
}
86181
```
87182

88-
Assignable variables in the function are just those specified so with
89-
`__CPROVER_assigns`, together with any local variables.
183+
Assignable locations for the `sum` function are the locations specified with
184+
`__CPROVER_assigns`, together with any location storing a function parameter,
185+
or any location that is locally stack- or heap-allocated as part of function (or loop)
186+
execution.
187+
90188
In the case of `sum` that is `*out` and `result`. Each assignment will be
91189
instrumented with an assertion to check that the target of the assignment
92190
is one of those options.
@@ -119,30 +217,40 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
119217
}
120218
```
121219
122-
Additionally, the set of assignable target expressions is updated while
123-
traversing the function body when new memory is allocated. For example, the
124-
statement `x = (int *)malloc(sizeof(int))` would create a pointer, stored in
125-
`x`, to assignable memory. Since the memory is allocated within the current
126-
function, there is no way an assignment to this memory can affect the memory of
127-
the calling context. If memory is allocated for a struct, the subcomponents are
128-
considered assignable as well.
220+
### Replacement
129221
130-
Finally, a set of freely-assignable symbols *free* is tracked during the
131-
traversal of the function body. These are locally-defined variables and formal
132-
parameters without dereferences. For example, in a variable declaration `<type>
133-
x = <initial_value>`, `x` would be added to the *free* set. Assignment statements
134-
where the left-hand-side is in the *free* set are not instrumented with the above assertions.
222+
Assuming the _assigns_ clause of the contract correctly captures the set of
223+
locations assigned to by a function (checked during _contract enforcement_),
224+
CBMC will use the contract's [Requires \& Ensures Clauses](../../contracts/requires-and-ensures/#replacement),
225+
and its _assigns clause_ to generate a sound abstraction of the function behaviour from the contract.
135226
136-
#### Replacement
227+
Given the contract:
228+
229+
```c
230+
int f(params)
231+
__CPROVER_requires(R);
232+
__CPROVER_assigns(A);
233+
__CPROVER_ensures(E);
234+
{ ... }
235+
```
137236

138-
Assuming _assigns_ clauses are a sound abstraction of the write set for
139-
a given function, CBMC will use the function contract in place of the function
140-
implementation as described by
141-
[Requires \& Ensures Clauses](../../contracts/requires-and-ensures/#replacement), and it will add
142-
non-deterministic assignments for each object listed in the `__CPROVER_assigns`
143-
clause. Since these objects might be modified by the function, CBMC uses
144-
non-deterministic assignments to havoc them and restrict their values only by
145-
assuming the postconditions (i.e., ensures clauses).
237+
Function calls `f(args)` get replaced with a sequence of instuctions equivalent to:
238+
239+
```c
240+
// check preconditions
241+
__CPROVER_assert(R[args/params], "Check f preconditions");
242+
243+
// havoc the assignable targets
244+
// for each target t1, t2, ... of A[args/params];
245+
t1 = nondet();
246+
t2 = nondet();
247+
...
248+
// assume post conditions
249+
__CPROVER_assume(E[args/params]);
250+
```
251+
Where `R[args/params]`, `A[args/params]`, `E[args/params]` denote the contract
252+
clause expressions rewritten by substituting
253+
function parameters with the argyments passed at the call site.
146254
147255
In our example, consider that a function `foo` may call `sum`.
148256
@@ -202,7 +310,3 @@ int foo()
202310
return rval;
203311
}
204312
```
205-
206-
## In Loop Contracts
207-
208-
TODO: Document `__CPROVER_assigns` for loops.

regression/contracts/assigns-slice-targets/main-enforce.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ struct st
77
int c;
88
};
99

10-
void foo(struct st *s)
10+
void foo(struct st *s, struct st *ss)
1111
// clang-format off
1212
__CPROVER_requires(__CPROVER_is_fresh(s, sizeof(*s)))
13-
__CPROVER_assigns(__CPROVER_object_slice(s->arr1, 5))
14-
__CPROVER_assigns(__CPROVER_object_from(s->arr2 + 5))
13+
__CPROVER_assigns(
14+
__CPROVER_object_upto(s->arr1, 5);
15+
__CPROVER_object_from(s->arr2 + 5);
16+
__CPROVER_object_whole(ss);
17+
)
1518
// clang-format on
1619
{
1720
// PASS
@@ -41,13 +44,24 @@ void foo(struct st *s)
4144
s->arr2[7] = 0;
4245
s->arr2[8] = 0;
4346
s->arr2[9] = 0;
47+
48+
// PASS
49+
ss->a = 0;
50+
ss->arr1[0] = 0;
51+
ss->arr1[7] = 0;
52+
ss->arr1[9] = 0;
53+
ss->b = 0;
54+
ss->arr2[6] = 0;
55+
ss->arr2[8] = 0;
56+
ss->c = 0;
4457
}
4558

4659
int main()
4760
{
4861
struct st s;
62+
struct st ss;
4963

50-
foo(&s);
64+
foo(&s, &ss);
5165

5266
return 0;
5367
}

0 commit comments

Comments
 (0)