Skip to content

Commit fb3fcaf

Browse files
author
Christopher Wagner
committed
Add scalar assigns clause to code contracts.
Expanding CBMC code contracts to include an assigns clause for scalar variables and their pointers.
1 parent eefd06f commit fb3fcaf

File tree

55 files changed

+1400
-43
lines changed

Some content is hidden

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

55 files changed

+1400
-43
lines changed

doc/cprover-manual/assigns-clause.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
## CBMC Assigns Clause
2+
3+
### Introduction
4+
Here is described the syntax and semantics of the "assigns" clause as implemented in CBMC. This construct allows the user to specify a list of memory locations which may be written within a particular scope. While an assigns clause may, in general, be interpreted with either "writes" or "modifies" semantics, this design is based on the former.
5+
This means that memory not captured by the assigns clause must not be written within the given scope, even if the value(s) therein are not modified.
6+
7+
### Scalar Variables
8+
The initial iteration of this design focuses on specifying an assigns clause for simple variable types and their pointers. Arrays, structured data, and pointers are left to future contibutions.
9+
10+
11+
##### Syntax
12+
A special construct is introduced to specify assigns clauses. Its syntax is defined as follows.
13+
14+
```
15+
<assigns_clause> := __CPROVER_assigns ( <target_list> )
16+
```
17+
```
18+
<target_list> := <target>
19+
| <target> , <target_list>
20+
```
21+
```
22+
<target> := <identifier>
23+
| * <target>
24+
```
25+
26+
27+
The `<assigns_clause>` states that only the memory identified by the dereference expressions and identifiers listed in the contained `<target_list>` may be written within the associated scope, as follows.
28+
29+
##### Semantics
30+
The semantics of an assigns clause *c* of some function *f* can be understood in two contexts. First, one may consider how the expressions in *c* are treated when a call to *f* is replaced by its contract specification, assuming the contract specification is a sound characterization of the behavior of *f*. Second, one may consider how *c* is applied the function body of *f* in order to determine whether *c* is a sound characterization of the behavior of *f*. We begin by exploring these two perspectives for assigns clauses which contain only scalar expressions.
31+
32+
Let the i<sup>th</sup> expression in some assigns clause *c* be denoted *exprs*<sub>*c*</sub>[i], the j<sup>th</sup> formal parameter of some function *f* be denoted *params*<sub>*f*</sub>[j], and the k<sup>th</sup> argument passed in a call to function *f* be denoted *args*<sub>*f*</sub>[k] (an identifying index may be added to distinguish a *particular* call to *f*, but for simplicity it is omitted here).
33+
34+
###### Replacement
35+
Assuming an assigns clause *c* is a sound characterization of the behavior of the function *f*, a call to *f* may be replaced a series of non-deterministic assignments. For each expression *e* &#8712; *exprs*<sub>*c*</sub>, let there be an assignment &#632; := &#8727;, where &#632; is *args*<sub>*f*</sub>[i] if *e* is identical to some *params*<sub>*f*</sub>[i], and *e* otherwise.
36+
37+
###### Enforcement
38+
In order to determine whether an assigns clause *c* is a sound characterization of the behavior of a function *f*, the body of the function should be instrumented with additional statements as follows.
39+
40+
- For each expression *e* &#8712; *exprs*<sub>*c*</sub>, create a temporary variable *tmp*<sub>*e*</sub> to store \&(*e*), the address of *e*, at the start of *f*.
41+
- Before each assignment statement, *lhs* := *rhs*, add an assertion (structured as a disjunction)
42+
43+
assert(&#8707; *tmp*<sub>*e*</sub>. __CPROVER_same_object(\&(*lhs*), *tmp*<sub>*e*</sub>)
44+
- Before each function call with an assigns clause *a*, add an assertion for each *e*<sub>*a*</sub> &#8712; *exprs*<sub>*a*</sub> (also formulated as a disjunction)
45+
46+
assert(&#8707; *tmp*<sub>*e*</sub>. __CPROVER_same_object(\&(*e*<sub>*a*</sub>), *tmp*<sub>*e*</sub>)
47+
48+
Here, __CPROVER_same_object is a predicate indicating that the two pointers reference the same object in the CBMC memory model. Additionally, for each function call without an assigns clause, add an assertion assert(*false*) to ensure that the assigns contract will only hold if all called functions have an assigns clause which is compatible with that of the calling function.
49+
50+
51+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <assert.h>
2+
3+
int z;
4+
5+
int foo(int *x) __CPROVER_assigns(z, *x)
6+
__CPROVER_ensures(__CPROVER_return_value == *x + 5)
7+
{
8+
*x = *x + 0;
9+
return *x + 5;
10+
}
11+
12+
int main()
13+
{
14+
int n = 4;
15+
n = foo(&n);
16+
17+
return 0;
18+
}
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <assert.h>
2+
3+
int z;
4+
5+
int foo(int *x) __CPROVER_assigns(z)
6+
__CPROVER_ensures(__CPROVER_return_value == *x + 5)
7+
{
8+
*x = *x + 0;
9+
return *x + 5;
10+
}
11+
12+
int main()
13+
{
14+
int n = 4;
15+
n = foo(&n);
16+
17+
return 0;
18+
}
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <assert.h>
2+
3+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
4+
{
5+
f2(x1, y1, z1);
6+
}
7+
8+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
9+
{
10+
f3(x2, y2, z2);
11+
}
12+
13+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3)
14+
{
15+
*x3 = *x3 + 1;
16+
*y3 = *y3 + 1;
17+
*z3 = *z3 + 1;
18+
}
19+
20+
int main()
21+
{
22+
int p = 1;
23+
int q = 2;
24+
int r = 3;
25+
f1(&p, &q, &r);
26+
27+
return 0;
28+
}
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <assert.h>
2+
3+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
4+
{
5+
f2(x1, y1, z1);
6+
}
7+
8+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
9+
{
10+
f3(x2, y2, z2);
11+
}
12+
13+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3)
14+
{
15+
*x3 = *x3 + 1;
16+
*y3 = *y3 + 1;
17+
*z3 = *z3 + 1;
18+
}
19+
20+
int main()
21+
{
22+
int p = 1;
23+
int q = 2;
24+
int r = 3;
25+
f1(&p, &q, &r);
26+
27+
return 0;
28+
}
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <assert.h>
2+
3+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
4+
{
5+
f2(x1, y1, z1);
6+
}
7+
8+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
9+
{
10+
f3(x2, y2, z2);
11+
}
12+
13+
void f3(int *x3, int *y3, int *z3)
14+
{
15+
}
16+
17+
int main()
18+
{
19+
int p = 1;
20+
int q = 2;
21+
int r = 3;
22+
f1(&p, &q, &r);
23+
24+
return 0;
25+
}
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <assert.h>
2+
3+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
4+
{
5+
f2(x1, y1, z1);
6+
}
7+
8+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
9+
{
10+
f3(x2, y2, z2);
11+
}
12+
13+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3)
14+
{
15+
*x3 = *x3 + 1;
16+
*y3 = *y3 + 1;
17+
*z3 = *z3 + 1;
18+
}
19+
20+
int main()
21+
{
22+
int p = 1;
23+
int q = 2;
24+
int r = 3;
25+
26+
for(int i = 0; i < 3; ++i)
27+
{
28+
if(i == 0)
29+
{
30+
f1(&p, &q, &r);
31+
}
32+
if(i == 1)
33+
{
34+
f2(&p, &q, &r);
35+
}
36+
if(i == 2)
37+
{
38+
f3(&p, &q, &r);
39+
}
40+
}
41+
42+
return 0;
43+
}
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <assert.h>
2+
3+
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1)
4+
{
5+
f2(x1, y1, z1);
6+
}
7+
8+
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2)
9+
{
10+
f3(x2, y2, z2);
11+
}
12+
13+
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3)
14+
{
15+
*x3 = *x3 + 1;
16+
*y3 = *y3 + 1;
17+
*z3 = *z3 + 1;
18+
}
19+
20+
int main()
21+
{
22+
int p = 1;
23+
int q = 2;
24+
int r = 3;
25+
26+
for(int i = 0; i < 3; ++i)
27+
{
28+
if(i == 0)
29+
{
30+
f1(&p, &q, &r);
31+
}
32+
if(i == 1)
33+
{
34+
f2(&p, &q, &r);
35+
}
36+
if(i == 2)
37+
{
38+
f3(&p, &q, &r);
39+
}
40+
}
41+
42+
return 0;
43+
}
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <assert.h>
2+
3+
void f1(int *x) __CPROVER_assigns(*x)
4+
{
5+
int *a = x;
6+
f2(&a);
7+
}
8+
void f2(int **y) __CPROVER_assigns(**y)
9+
{
10+
**y = 5;
11+
}
12+
13+
int main()
14+
{
15+
int n = 3;
16+
f1(&n);
17+
18+
return 0;
19+
}
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 a function with an assigns clause calls another with an additional level of indirection, and that functions respects the assigns clause of the caller.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <assert.h>
2+
3+
void f1(int **x) __CPROVER_assigns(*x)
4+
{
5+
f2(x);
6+
}
7+
void f2(int **y) __CPROVER_assigns(**y)
8+
{
9+
**y = 5;
10+
}
11+
12+
int main()
13+
{
14+
int p = 3;
15+
int *q = &p;
16+
f1(&q);
17+
18+
return 0;
19+
}
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 failes when a function with an assigns clause calls another with an assigns clause that adds one too many dereferences.

0 commit comments

Comments
 (0)