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
+ 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
8
21
9
22
``` 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)
11
37
```
12
38
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 .
17
43
18
- ### Object slice expressions
44
+ ### Lvalue targets
19
45
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 .
22
48
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:
26
52
```c
27
- __CPROVER_size_t __CPROVER_object_from(void *ptr);
53
+ __CPROVER_assignable_t __CPROVER_typed_target(expr_t expr);
28
54
```
29
55
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:
33
62
34
63
``` 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
+ }
36
81
```
37
82
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
43
84
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
+ ```
47
101
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
48
142
``` c
49
143
/* Examples */
50
144
int err_signal; // Global variable
@@ -59,17 +153,18 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
59
153
__ CPROVER_assigns(* out, err_signal)
60
154
```
61
155
62
- ### Semantics
156
+ ## Semantics
63
157
64
158
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.
66
160
67
- #### Enforcement
161
+ ### Enforcement
68
162
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
71
165
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.
73
168
74
169
For example, consider the following implementation of `sum` function.
75
170
@@ -85,8 +180,11 @@ __CPROVER_assigns(*out)
85
180
}
86
181
```
87
182
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
+
90
188
In the case of ` sum ` that is ` *out ` and ` result ` . Each assignment will be
91
189
instrumented with an assertion to check that the target of the assignment
92
190
is one of those options.
@@ -119,30 +217,40 @@ int sum(const uint32_t a, const uint32_t b, uint32_t* out)
119
217
}
120
218
```
121
219
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
129
221
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.
135
226
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
+ ```
137
236
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.
146
254
147
255
In our example, consider that a function `foo` may call `sum`.
148
256
@@ -202,7 +310,3 @@ int foo()
202
310
return rval;
203
311
}
204
312
```
205
-
206
- ## In Loop Contracts
207
-
208
- TODO: Document ` __CPROVER_assigns ` for loops.
0 commit comments