1
1
[ CPROVER Manual TOC] ( ../../ )
2
2
3
- # Assigns Clause
3
+ # Assigns Clauses
4
4
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.
6
7
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
8
34
9
35
``` 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
+ __CPROVER_assignable_t __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
+ }
11
103
```
12
104
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
17
106
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
+ __CPROVER_assignable_t __CPROVER_object_whole(void *ptr);
112
+ ```
113
+ REMARK: this also includes bytes in the object that are before the address given
114
+ by ` ptr ` ;
19
115
20
- The following functions can be used in assigns clause to specify ranges of
21
- assignable addresses.
22
116
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:
26
120
``` c
27
- __CPROVER_size_t __CPROVER_object_from(void *ptr);
121
+ __CPROVER_assignable_t __CPROVER_object_from (void * ptr);
28
122
```
29
123
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:
33
139
34
140
``` c
35
- __CPROVER_size_t __CPROVER_object_slice (void * ptr, __ CPROVER_size_t size);
141
+ __CPROVER_assignable_t __CPROVER_object_upto (void * ptr, __ CPROVER_size_t size);
36
142
```
37
143
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;
43
146
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.
47
162
163
+ ## Examples
48
164
``` c
49
165
/* Examples */
50
166
int err_signal; // Global variable
@@ -59,17 +175,18 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
59
175
__ CPROVER_assigns(* out, err_signal)
60
176
```
61
177
62
- ### Semantics
178
+ ## Semantics
63
179
64
180
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.
66
182
67
- #### Enforcement
183
+ ### Enforcement
68
184
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
71
187
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.
73
190
74
191
For example, consider the following implementation of `sum` function.
75
192
@@ -85,8 +202,11 @@ __CPROVER_assigns(*out)
85
202
}
86
203
```
87
204
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
+
90
210
In the case of ` sum ` that is ` *out ` and ` result ` . Each assignment will be
91
211
instrumented with an assertion to check that the target of the assignment
92
212
is one of those options.
@@ -119,30 +239,40 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
119
239
}
120
240
```
121
241
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:
129
250
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
+ ```
135
258
136
- #### Replacement
259
+ Function calls ` f(args) ` get replaced with a sequence of instuctions equivalent to:
137
260
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.
146
276
147
277
In our example, consider that a function `foo` may call `sum`.
148
278
@@ -202,7 +332,3 @@ int foo()
202
332
return rval;
203
333
}
204
334
```
205
-
206
- ## In Loop Contracts
207
-
208
- TODO: Document ` __CPROVER_assigns ` for loops.
0 commit comments