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 */
50144int 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
64158The 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
71165assertion 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
74169For 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+
90188In the case of ` sum ` that is ` *out ` and ` result ` . Each assignment will be
91189instrumented with an assertion to check that the target of the assignment
92190is 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
147255In 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.
0 commit comments