Skip to content

Commit a118518

Browse files
author
Remi Delmas
committed
CONTRACTS: Front-end extensions for assigns clauses.
Add new functions to specify assignable targets in assigns clauses: - 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 are one of the new built-ins Using __CPROVER_POINTER_OBJECT is still allowed (for loop contracts), will be deprecated in an ulterior PR.
1 parent 5c54265 commit a118518

File tree

72 files changed

+847
-245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+847
-245
lines changed

doc/cprover-manual/contracts-assigns.md

Lines changed: 186 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,166 @@
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+
For a function contract, if no assigns clause is provided, the default set of
9+
locations is the empty set.
10+
11+
For a loop contract, if no assigns clause is provided with the contract, CBMC
12+
attempts to infer the set of locations assigned by the loop from the loop body,
13+
and then checks that this set is correct as if it had been provided by the user.
14+
15+
For both functions and loop contracts, if more than one assigns clause is
16+
provided, they are implicitly merged into a single clause.
17+
18+
Each target listed in the clause gets resolved to a pair of values of type
19+
`(void *, size_t)` specifying the assignable memory location's start address
20+
and extent in bytes.
21+
22+
We use the _assigns_ interpretation for these locations, as opposed to the
23+
_modifies_ interpretation. This means that memory locations that are part of the
24+
_frame_ of the function call or the loop that are not listed in the assigns
25+
clause (or in the inferred clause for a loop contract) must not be assigned to
26+
by the function or the loop, even if they end up holding the same value as they
27+
held before the function call or before entering the loop.
28+
29+
In contrast, memory location that are locally stack- or heap-allocated during
30+
function execution or loop execution are by definition not part of the _frame_
31+
and can always be assigned to by the function or the loop.
32+
33+
## Syntax
834

935
```c
10-
__CPROVER_assigns(*identifier*, ...)
36+
__CPROVER_assigns( *targets* )
37+
```
38+
39+
Where targets have the following syntax:
40+
41+
```
42+
targets ::= conditional-target-group (';' conditional-target-group)* ';'?
43+
conditional-target-group ::= (condition ':')? target (',' target)*
44+
target ::= l-value-expression
45+
| __CPROVER_typed_target(l-value-expression)
46+
| __CPROVER_object_whole(pointer-typed-expression)
47+
| __CPROVER_object_from(pointer-typed-expression)
48+
| __CPROVER_object_upto(pointer-typed-expression,
49+
unsigned-integer-typed-expression)
50+
```
51+
52+
53+
For function contracts, the condition and target expressions
54+
in the assigns clause may only involve function parameters,
55+
global variables or type identifiers in `sizeof` or cast expressions.
56+
The target expression must be free of function calls and side-effects.
57+
The condition expression may contain calls to side-effect-free functions.
58+
59+
For a loop contract, the condition and target expressions in the assigns clause
60+
may involve any identifier that is in scope at loop entry
61+
(parameters of the surrounding function, local variables, global variables,
62+
type identifiers in `sizeof` or cast expressions, etc.).
63+
The target expression must be free of function calls and side-effects.
64+
The condition expression may contain calls to side-effect-free functions.
65+
66+
### Lvalue targets
67+
68+
An l-value target `expr` with a complete type `expr_t` specifies that the range
69+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable.
70+
71+
Similarly, given an l-value expression `expr` with a complete type `expr_t`,
72+
`__CPROVER_typed_target(expr)` specifies that the range
73+
of `sizeof(expr_t)` bytes starting at `&expr` is assignable:
74+
```c
75+
void __CPROVER_typed_target(expr_t expr);
76+
```
77+
78+
To specify assignable memory locations
79+
that are interpreted as pointers by the program, one must use
80+
`__CPROVER_assigns(ptr)` or `__CPROVER_assigns(__CPROVER_typed_target(ptr))`.
81+
82+
This is to ensure that during call by contract replacement they will be havoced
83+
as pointers, in a type directed way. For instance:
84+
85+
```c
86+
struct circular_buffer_t {
87+
int *first;
88+
int *last;
89+
int *current;
90+
}
91+
92+
void step(struct circular_buffer_t *buf)
93+
// correct
94+
__CPROVER_assigns(__CPROVER_typed_target(buf->current))
95+
// not correct
96+
__CPROVER_assigns(__CPROVER_object_upto(&(buf->current), sizeof(buf->current))
97+
{
98+
if(buf->current == buf->last)
99+
buf->current = buf->first;
100+
else
101+
buf->current += 1;
102+
}
11103
```
12104
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.
105+
### Object slice targets
17106
18-
### Object slice expressions
107+
Given a pointer `ptr` pointing into some object `o`,
108+
`__CPROVER_object_whole(ptr)` specifies that all bytes of the object `o`
109+
are assignable:
110+
```c
111+
void __CPROVER_object_whole(void *ptr);
112+
```
113+
REMARK: this also includes bytes in the object that are before the address given
114+
by `ptr`;
19115

20-
The following functions can be used in assigns clause to specify ranges of
21-
assignable addresses.
22116

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:
117+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_from(ptr)`
118+
specifies that the range of bytes starting from the pointer and until the end of
119+
the object `o` are assignable:
26120
```c
27-
__CPROVER_size_t __CPROVER_object_from(void *ptr);
121+
void __CPROVER_object_from(void *ptr);
28122
```
29123
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:
124+
```c
125+
const size_t SIZE = 100;
126+
127+
void init_slice_all(char *arr)
128+
__CPROVER_assigns(__CPROVER_object_from(arr))
129+
{
130+
for(size_t i=0; i<SIZE; i++)
131+
arr[i] = 0;
132+
}
133+
```
134+
135+
Given a pointer `ptr` pointing into some object `o`, `__CPROVER_object_upto(ptr, size)`
136+
specifies that the range of `size` bytes of `o` starting at `ptr` are assignable:
137+
The `size` value must such that the range does not exceed the object boundary,
138+
that is, `__CPROVER_object_size(ptr) - __CPROVER_pointer_offset(ptr) >= size` must hold:
33139

34140
```c
35-
__CPROVER_size_t __CPROVER_object_slice(void *ptr, __CPROVER_size_t size);
141+
void __CPROVER_object_upto(void *ptr, __CPROVER_size_t size);
36142
```
37143
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
144+
```c
145+
const size_t SIZE = 100;
43146
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.
147+
void init_slice_n(char *arr, size_t n)
148+
__CPROVER_requires(n <= SIZE)
149+
__CPROVER_assigns(__CPROVER_object_upto(arr, n))
150+
{
151+
for(size_t i=0; i<n; i++)
152+
arr[i] = 0;
153+
}
154+
```
155+
### Function parameters
156+
157+
The memory locations storing function parameters values are considered local
158+
to the function and are hence always assignable.
159+
160+
### Inductive data structures
161+
Inductive data structures are not supported yet in assigns clauses.
47162

163+
## Examples
48164
```c
49165
/* Examples */
50166
int err_signal; // Global variable
@@ -59,17 +175,18 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
59175
__CPROVER_assigns(*out, err_signal)
60176
```
61177
62-
### Semantics
178+
## Semantics
63179
64180
The semantics of an _assigns_ clause of a given function can be understood
65-
in two contexts: enforcement and replacement.
181+
in two contexts: contract enforcement and replacement.
66182
67-
#### Enforcement
183+
### Enforcement
68184
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
185+
In order to determine whether a function (or loop) complies with the _assigns_
186+
clause of the contract, the body of the function (or loop) is instrumented with
71187
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.
188+
assignment). These assertions check that the location about to be assigned to
189+
is among the targets specified by the _assigns_ clauses.
73190
74191
For example, consider the following implementation of `sum` function.
75192
@@ -85,8 +202,11 @@ __CPROVER_assigns(*out)
85202
}
86203
```
87204

88-
Assignable variables in the function are just those specified so with
89-
`__CPROVER_assigns`, together with any local variables.
205+
Assignable locations for the `sum` function are the locations specified with
206+
`__CPROVER_assigns`, together with any location storing a function parameter,
207+
or any location that is locally stack- or heap-allocated as part of function (or loop)
208+
execution.
209+
90210
In the case of `sum` that is `*out` and `result`. Each assignment will be
91211
instrumented with an assertion to check that the target of the assignment
92212
is one of those options.
@@ -119,30 +239,40 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
119239
}
120240
```
121241
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.
242+
### Replacement
243+
244+
Assuming the _assigns_ clause of the contract correctly captures the set of
245+
locations assigned to by a function (checked during _contract enforcement_),
246+
CBMC will use the contract's [Requires \& Ensures Clauses](../../contracts/requires-and-ensures/#replacement),
247+
and its _assigns clause_ to generate a sound abstraction of the function behaviour from the contract.
248+
249+
Given the contract:
129250
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.
251+
```c
252+
int f(params)
253+
__CPROVER_requires(R);
254+
__CPROVER_assigns(A);
255+
__CPROVER_ensures(E);
256+
{ ... }
257+
```
135258

136-
#### Replacement
259+
Function calls `f(args)` get replaced with a sequence of instuctions equivalent to:
137260

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).
261+
```c
262+
// check preconditions
263+
__CPROVER_assert(R[args/params], "Check f preconditions");
264+
265+
// havoc the assignable targets
266+
// for each target t1, t2, ... of A[args/params];
267+
t1 = nondet();
268+
t2 = nondet();
269+
...
270+
// assume post conditions
271+
__CPROVER_assume(E[args/params]);
272+
```
273+
Where `R[args/params]`, `A[args/params]`, `E[args/params]` denote the contract
274+
clause expressions rewritten by substituting
275+
function parameters with the argyments passed at the call site.
146276
147277
In our example, consider that a function `foo` may call `sum`.
148278
@@ -202,7 +332,3 @@ int foo()
202332
return rval;
203333
}
204334
```
205-
206-
## In Loop Contracts
207-
208-
TODO: Document `__CPROVER_assigns` for loops.

regression/contracts/assigns-enforce-malloc-zero/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ int foo(char *a, int size)
66
// clang-format off
77
__CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size)
88
__CPROVER_requires(a == NULL || __CPROVER_is_fresh(a, size))
9-
__CPROVER_assigns(a: __CPROVER_POINTER_OBJECT(a))
9+
__CPROVER_assigns(a: __CPROVER_object_whole(a))
1010
__CPROVER_ensures(
1111
a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0)
1212
// clang-format on

regression/contracts/assigns-enforce-malloc-zero/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CORE
22
main.c
33
--enforce-contract foo _ --malloc-may-fail --malloc-fail-null
4-
^\[foo.assigns.\d+\] line 9 Check that POINTER_OBJECT\(\(const void \*\)a\) is valid when a != \(\(char \*\)NULL\): SUCCESS$
4+
^\[foo.assigns.\d+\] line 9 Check that __CPROVER_object_whole\(\(.* \*\)a\) is valid when a != \(\(.* \*\)NULL\): SUCCESS$
55
^\[foo.assigns.\d+\] line 19 Check that a\[\(signed long (long )?int\)i\] is assignable: SUCCESS$
66
^EXIT=0$
77
^SIGNAL=0$

regression/contracts/assigns-replace-malloc-zero/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ int foo(char *a, int size)
66
// clang-format off
77
__CPROVER_requires(0 <= size && size <= __CPROVER_max_malloc_size)
88
__CPROVER_requires(a == NULL || __CPROVER_rw_ok(a, size))
9-
__CPROVER_assigns(__CPROVER_POINTER_OBJECT(a))
9+
__CPROVER_assigns(__CPROVER_object_whole(a))
1010
__CPROVER_ensures(
1111
a && __CPROVER_return_value >= 0 ==> a[__CPROVER_return_value] == 0)
1212
// clang-format on

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)