Skip to content

Commit a0e4c95

Browse files
authored
Merge pull request #5403 from azcwagner/assigns-clause-scalars
Added scalar assigns clause to code contracts
2 parents 8fae794 + 071a725 commit a0e4c95

File tree

58 files changed

+1489
-87
lines changed

Some content is hidden

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

58 files changed

+1489
-87
lines changed

doc/cprover-manual/assigns-clause.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
## CBMC Assigns Clause
2+
3+
4+
### Introduction
5+
The _assigns_ clause allows the user to specify a list of memory
6+
locations which may be written within a particular scope. While an assigns
7+
clause may, in general, be interpreted with either _writes_ or _modifies_
8+
semantics, this design is based on the former. This means that memory not
9+
captured by the assigns clause must not be written within the given scope, even
10+
if the value(s) therein are not modified.
11+
12+
13+
### Scalar Variables
14+
The initial iteration of this design focuses on specifying an assigns clause for
15+
primitive types and their pointers. Arrays, structured data, and pointers are
16+
left to future contributions.
17+
18+
19+
##### Syntax
20+
A special construct is introduced to specify assigns clauses. Its syntax is
21+
defined as follows.
22+
23+
```
24+
<assigns_clause> := __CPROVER_assigns ( <target_list> )
25+
```
26+
```
27+
<target_list> := <target>
28+
| <target> , <target_list>
29+
```
30+
```
31+
<target> := <identifier>
32+
| * <target>
33+
```
34+
35+
36+
The `<assigns_clause>` states that only the memory identified by the dereference
37+
expressions and identifiers listed in the contained `<target_list>` may be
38+
written within the associated scope, as follows.
39+
40+
41+
##### Semantics
42+
The semantics of an assigns clause *c* of some function *f* can be understood in
43+
two contexts. First, one may consider how the expressions in *c* are treated
44+
when a call to *f* is replaced by its contract specification, assuming the
45+
contract specification is a sound characterization of the behavior of *f*.
46+
Second, one may consider how *c* is applied to the function body of *f* in order
47+
to determine whether *c* is a sound characterization of the behavior of *f*. We
48+
begin by exploring these two perspectives for assigns clauses which contain only
49+
scalar expressions.
50+
51+
Let the i<sup>th</sup> expression in some assigns clause *c* be denoted
52+
*exprs*<sub>*c*</sub>[i], the j<sup>th</sup> formal parameter of some function
53+
*f* be denoted *params*<sub>*f*</sub>[j], and the k<sup>th</sup> argument passed
54+
in a call to function *f* be denoted *args*<sub>*f*</sub>[k] (an identifying
55+
index may be added to distinguish a *particular* call to *f*, but for simplicity
56+
it is omitted here).
57+
58+
59+
###### Replacement
60+
Assuming an assigns clause *c* is a sound characterization of the behavior of
61+
the function *f*, a call to *f* may be replaced a series of non-deterministic
62+
assignments. For each expression *e* &#8712; *exprs*<sub>*c*</sub>, let there be
63+
an assignment &#632; := &#8727;, where &#632; is *args*<sub>*f*</sub>[i] if *e*
64+
is identical to some *params*<sub>*f*</sub>[i], and *e* otherwise.
65+
66+
67+
###### Enforcement
68+
In order to determine whether an assigns clause *c* is a sound characterization
69+
of the behavior of a function *f*, the body of the function should be
70+
instrumented with additional statements as follows.
71+
72+
- For each expression *e* &#8712; *exprs*<sub>*c*</sub>, create a temporary
73+
variable *tmp*<sub>*e*</sub> to store \&(*e*), the address of *e*, at the
74+
start of *f*.
75+
- Before each assignment statement, *lhs* := *rhs*, add an assertion (structured
76+
as a disjunction)
77+
assert(&#8707; *tmp*<sub>*e*</sub>. \_\_CPROVER\_same\_object(\&(*lhs*),
78+
*tmp*<sub>*e*</sub>)
79+
- Before each function call with an assigns clause *a*, add an assertion for
80+
each *e*<sub>*a*</sub> &#8712; *exprs*<sub>*a*</sub> (also formulated as a
81+
disjunction)
82+
assert(&#8707; *tmp*<sub>*e*</sub>.
83+
\_\_CPROVER\_same\_object(\&(*e*<sub>*a*</sub>), *tmp*<sub>*e*</sub>)
84+
85+
Here, \_\_CPROVER\_same\_object returns true if two pointers
86+
reference the same object in the CBMC memory model. Additionally, for each
87+
function call without an assigns clause, CBMC adds an assertion assert(*false*)
88+
to ensure that the assigns contract will only hold if all called functions
89+
have an assigns clause which is compatible with that of the calling function.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
int foo(int *x) __CPROVER_assigns(*x)
2+
__CPROVER_ensures(__CPROVER_return_value == *x + 5)
3+
{
4+
*x = *x + 0;
5+
return *x + 5;
6+
}
7+
8+
int main()
9+
{
10+
int n = 4;
11+
n = foo(&n);
12+
13+
return 0;
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function.
10+
11+
Note: For all 'enforce' tests, nothing can be assumed about the return value of the function (as the function call is not replaced at this point).
12+
13+
To make such assumptions would cause verification to fail.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
int z;
2+
3+
int foo(int *x) __CPROVER_assigns(z)
4+
__CPROVER_ensures(__CPROVER_return_value == *x + 5)
5+
{
6+
*x = *x + 0;
7+
return *x + 5;
8+
}
9+
10+
int main()
11+
{
12+
int n = 4;
13+
n = foo(&n);
14+
15+
return 0;
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
--
8+
--
9+
This test checks that verification fails if an expression outside the assigns clause is assigned within the function.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
2+
{
3+
f2(x1, y1, z1);
4+
}
5+
6+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
7+
{
8+
f3(x2, y2, z2);
9+
}
10+
11+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3)
12+
{
13+
*x3 = *x3 + 1;
14+
*y3 = *y3 + 1;
15+
*z3 = *z3 + 1;
16+
}
17+
18+
int main()
19+
{
20+
int p = 1;
21+
int q = 2;
22+
int r = 3;
23+
f1(&p, &q, &r);
24+
25+
return 0;
26+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that verification succeeds when assigns clauses are respected through multiple function calls.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
2+
{
3+
f2(x1, y1, z1);
4+
}
5+
6+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
7+
{
8+
f3(x2, y2, z2);
9+
}
10+
11+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3)
12+
{
13+
*x3 = *x3 + 1;
14+
*y3 = *y3 + 1;
15+
*z3 = *z3 + 1;
16+
}
17+
18+
int main()
19+
{
20+
int p = 1;
21+
int q = 2;
22+
int r = 3;
23+
f1(&p, &q, &r);
24+
25+
return 0;
26+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
--
8+
--
9+
This test checks that verification fails when an assigns clause is not respected through multiple function calls.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
2+
{
3+
f2(x1, y1, z1);
4+
}
5+
6+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
7+
{
8+
f3(x2, y2, z2);
9+
}
10+
11+
void f3(int *x3, int *y3, int *z3)
12+
{
13+
}
14+
15+
int main()
16+
{
17+
int p = 1;
18+
int q = 2;
19+
int r = 3;
20+
f1(&p, &q, &r);
21+
22+
return 0;
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
--
8+
--
9+
This test checks that verification fails when a function with an assigns clause calls a function without an assigns clause.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
2+
{
3+
f2(x1, y1, z1);
4+
}
5+
6+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
7+
{
8+
f3(x2, y2, z2);
9+
}
10+
11+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3)
12+
{
13+
*x3 = *x3 + 1;
14+
*y3 = *y3 + 1;
15+
*z3 = *z3 + 1;
16+
}
17+
18+
int main()
19+
{
20+
int p = 1;
21+
int q = 2;
22+
int r = 3;
23+
24+
for(int i = 0; i < 3; ++i)
25+
{
26+
if(i == 0)
27+
{
28+
f1(&p, &q, &r);
29+
}
30+
if(i == 1)
31+
{
32+
f2(&p, &q, &r);
33+
}
34+
if(i == 2)
35+
{
36+
f3(&p, &q, &r);
37+
}
38+
}
39+
40+
return 0;
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that verification succeeds when functions with assigns clauses are called from within a loop.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
2+
{
3+
f2(x1, y1, z1);
4+
}
5+
6+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
7+
{
8+
f3(x2, y2, z2);
9+
}
10+
11+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3)
12+
{
13+
*x3 = *x3 + 1;
14+
*y3 = *y3 + 1;
15+
*z3 = *z3 + 1;
16+
}
17+
18+
int main()
19+
{
20+
int p = 1;
21+
int q = 2;
22+
int r = 3;
23+
24+
for(int i = 0; i < 3; ++i)
25+
{
26+
if(i == 0)
27+
{
28+
f1(&p, &q, &r);
29+
}
30+
if(i == 1)
31+
{
32+
f2(&p, &q, &r);
33+
}
34+
if(i == 2)
35+
{
36+
f3(&p, &q, &r);
37+
}
38+
}
39+
40+
return 0;
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
--
8+
--
9+
This test checks that verification fails when functions with assigns clauses are called within a loop and one of them does not obey its assigns clause.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
void f1(int *x) __CPROVER_assigns(*x)
2+
{
3+
int *a = x;
4+
f2(&a);
5+
}
6+
void f2(int **y) __CPROVER_assigns(**y)
7+
{
8+
**y = 5;
9+
}
10+
11+
int main()
12+
{
13+
int n = 3;
14+
f1(&n);
15+
16+
return 0;
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
--enforce-all-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that verification succeeds when a function with an assigns
10+
clause calls another with an additional level of indirection, and that
11+
functions respects the assigns clause of the caller.

0 commit comments

Comments
 (0)