-
Notifications
You must be signed in to change notification settings - Fork 274
Added scalar assigns clause to code contracts #5403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
NlightNFotis
merged 5 commits into
diffblue:develop
from
azcwagner:assigns-clause-scalars
Oct 15, 2020
+1,489
−87
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f567e02
Add scalar assigns clause to code contracts.
67891bb
Extends ansi_c_declarationt to include accessors for assigns clauses
feliperodri cf8e8cb
Improves documentation for assigns clause support
feliperodri 9678a3d
Cleans-up all regression tests for assigns clauses
feliperodri 071a725
Cleans-up the implementation to support assigns clauses
feliperodri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
## CBMC Assigns Clause | ||
|
||
|
||
### Introduction | ||
The _assigns_ clause 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. 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. | ||
|
||
|
||
### Scalar Variables | ||
The initial iteration of this design focuses on specifying an assigns clause for | ||
primitive types and their pointers. Arrays, structured data, and pointers are | ||
left to future contributions. | ||
|
||
|
||
##### Syntax | ||
A special construct is introduced to specify assigns clauses. Its syntax is | ||
defined as follows. | ||
|
||
``` | ||
<assigns_clause> := __CPROVER_assigns ( <target_list> ) | ||
``` | ||
``` | ||
<target_list> := <target> | ||
| <target> , <target_list> | ||
``` | ||
``` | ||
<target> := <identifier> | ||
| * <target> | ||
``` | ||
|
||
|
||
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. | ||
|
||
|
||
##### Semantics | ||
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 to 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. | ||
|
||
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). | ||
|
||
|
||
###### Replacement | ||
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* ∈ *exprs*<sub>*c*</sub>, let there be | ||
an assignment ɸ := ∗, where ɸ is *args*<sub>*f*</sub>[i] if *e* | ||
is identical to some *params*<sub>*f*</sub>[i], and *e* otherwise. | ||
|
||
|
||
###### Enforcement | ||
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. | ||
|
||
- For each expression *e* ∈ *exprs*<sub>*c*</sub>, create a temporary | ||
variable *tmp*<sub>*e*</sub> to store \&(*e*), the address of *e*, at the | ||
start of *f*. | ||
- Before each assignment statement, *lhs* := *rhs*, add an assertion (structured | ||
as a disjunction) | ||
assert(∃ *tmp*<sub>*e*</sub>. \_\_CPROVER\_same\_object(\&(*lhs*), | ||
*tmp*<sub>*e*</sub>) | ||
- Before each function call with an assigns clause *a*, add an assertion for | ||
each *e*<sub>*a*</sub> ∈ *exprs*<sub>*a*</sub> (also formulated as a | ||
disjunction) | ||
assert(∃ *tmp*<sub>*e*</sub>. | ||
\_\_CPROVER\_same\_object(\&(*e*<sub>*a*</sub>), *tmp*<sub>*e*</sub>) | ||
|
||
Here, \_\_CPROVER\_same\_object returns true if two pointers | ||
reference the same object in the CBMC memory model. Additionally, for each | ||
function call without an assigns clause, CBMC adds 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
int foo(int *x) __CPROVER_assigns(*x) | ||
__CPROVER_ensures(__CPROVER_return_value == *x + 5) | ||
{ | ||
*x = *x + 0; | ||
return *x + 5; | ||
} | ||
|
||
int main() | ||
{ | ||
int n = 4; | ||
n = foo(&n); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
^VERIFICATION SUCCESSFUL$ | ||
-- | ||
-- | ||
This test checks that verification succeeds if only expressions inside the assigns clause are assigned within the function. | ||
|
||
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). | ||
|
||
To make such assumptions would cause verification to fail. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
int z; | ||
|
||
int foo(int *x) __CPROVER_assigns(z) | ||
__CPROVER_ensures(__CPROVER_return_value == *x + 5) | ||
{ | ||
*x = *x + 0; | ||
return *x + 5; | ||
} | ||
|
||
int main() | ||
{ | ||
int n = 4; | ||
n = foo(&n); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
^VERIFICATION FAILED$ | ||
-- | ||
-- | ||
This test checks that verification fails if an expression outside the assigns clause is assigned within the function. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) | ||
{ | ||
f2(x1, y1, z1); | ||
} | ||
|
||
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) | ||
{ | ||
f3(x2, y2, z2); | ||
} | ||
|
||
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3) | ||
{ | ||
*x3 = *x3 + 1; | ||
*y3 = *y3 + 1; | ||
*z3 = *z3 + 1; | ||
} | ||
|
||
int main() | ||
{ | ||
int p = 1; | ||
int q = 2; | ||
int r = 3; | ||
f1(&p, &q, &r); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
^VERIFICATION SUCCESSFUL$ | ||
-- | ||
-- | ||
This test checks that verification succeeds when assigns clauses are respected through multiple function calls. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) | ||
{ | ||
f2(x1, y1, z1); | ||
} | ||
|
||
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) | ||
{ | ||
f3(x2, y2, z2); | ||
} | ||
|
||
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3) | ||
{ | ||
*x3 = *x3 + 1; | ||
*y3 = *y3 + 1; | ||
*z3 = *z3 + 1; | ||
} | ||
|
||
int main() | ||
{ | ||
int p = 1; | ||
int q = 2; | ||
int r = 3; | ||
f1(&p, &q, &r); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
^VERIFICATION FAILED$ | ||
-- | ||
-- | ||
This test checks that verification fails when an assigns clause is not respected through multiple function calls. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) | ||
{ | ||
f2(x1, y1, z1); | ||
} | ||
|
||
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) | ||
{ | ||
f3(x2, y2, z2); | ||
} | ||
|
||
void f3(int *x3, int *y3, int *z3) | ||
{ | ||
} | ||
|
||
int main() | ||
{ | ||
int p = 1; | ||
int q = 2; | ||
int r = 3; | ||
f1(&p, &q, &r); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
^VERIFICATION FAILED$ | ||
-- | ||
-- | ||
This test checks that verification fails when a function with an assigns clause calls a function without an assigns clause. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) | ||
{ | ||
f2(x1, y1, z1); | ||
} | ||
|
||
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) | ||
{ | ||
f3(x2, y2, z2); | ||
} | ||
|
||
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*x3, *y3, *z3) | ||
{ | ||
*x3 = *x3 + 1; | ||
*y3 = *y3 + 1; | ||
*z3 = *z3 + 1; | ||
} | ||
|
||
int main() | ||
{ | ||
int p = 1; | ||
int q = 2; | ||
int r = 3; | ||
|
||
for(int i = 0; i < 3; ++i) | ||
{ | ||
if(i == 0) | ||
{ | ||
f1(&p, &q, &r); | ||
} | ||
if(i == 1) | ||
{ | ||
f2(&p, &q, &r); | ||
} | ||
if(i == 2) | ||
{ | ||
f3(&p, &q, &r); | ||
} | ||
} | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
^VERIFICATION SUCCESSFUL$ | ||
-- | ||
-- | ||
This test checks that verification succeeds when functions with assigns clauses are called from within a loop. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
void f1(int *x1, int *y1, int *z1) __CPROVER_assigns(*x1, *y1, *z1) | ||
{ | ||
f2(x1, y1, z1); | ||
} | ||
|
||
void f2(int *x2, int *y2, int *z2) __CPROVER_assigns(*x2, *y2, *z2) | ||
{ | ||
f3(x2, y2, z2); | ||
} | ||
|
||
void f3(int *x3, int *y3, int *z3) __CPROVER_assigns(*y3, *z3) | ||
{ | ||
*x3 = *x3 + 1; | ||
*y3 = *y3 + 1; | ||
*z3 = *z3 + 1; | ||
} | ||
|
||
int main() | ||
{ | ||
int p = 1; | ||
int q = 2; | ||
int r = 3; | ||
|
||
for(int i = 0; i < 3; ++i) | ||
{ | ||
if(i == 0) | ||
{ | ||
f1(&p, &q, &r); | ||
} | ||
if(i == 1) | ||
{ | ||
f2(&p, &q, &r); | ||
} | ||
if(i == 2) | ||
{ | ||
f3(&p, &q, &r); | ||
} | ||
} | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
^VERIFICATION FAILED$ | ||
-- | ||
-- | ||
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
void f1(int *x) __CPROVER_assigns(*x) | ||
{ | ||
int *a = x; | ||
f2(&a); | ||
} | ||
void f2(int **y) __CPROVER_assigns(**y) | ||
{ | ||
**y = 5; | ||
} | ||
|
||
int main() | ||
{ | ||
int n = 3; | ||
f1(&n); | ||
|
||
return 0; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
CORE | ||
main.c | ||
--enforce-all-contracts | ||
^EXIT=0$ | ||
^SIGNAL=0$ | ||
^VERIFICATION SUCCESSFUL$ | ||
-- | ||
-- | ||
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. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to add clauses to this test that check for which assigns clauses are being violated and which aren’t? Ditto for other tests which have failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the way the assigns clause is implemented adds an assertion before each assignment which requires that the left-hand-side which is about to be assigned actually aliases something in the list of assigns clause targets. They end up being stitched together with logical OR operations, and the whole thing ends up as one assertion per assignment. To identify a specific item from the assigns clause, we would need to restructure the assertion or augment it with some additional information. Is this what you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd read @hannes-steffenhagen-diffblue 's comment as referring to clauses, not targets. So even with the disjunction it should be possible to distinguish the failing clause?