-
Notifications
You must be signed in to change notification settings - Fork 274
Adds is_fresh predicate for function contracts #6148
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
Conversation
( At some point before the "don't review" tag gets removed, please can this PR say what the semantics of |
93cb2c7
to
0c296af
Compare
Codecov Report
@@ Coverage Diff @@
## develop #6148 +/- ##
===========================================
+ Coverage 75.04% 75.26% +0.21%
===========================================
Files 1450 1450
Lines 158276 158471 +195
===========================================
+ Hits 118784 119277 +493
+ Misses 39492 39194 -298
Continue to review full report at Codecov.
|
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 took a first pass over the regression tests. I left some small comments below, but here are some major ones:
- The regression tests all look kind of similar. Some other corner cases that we should test (feel free to add more):
- requires(is_fresh()) should pass on whole structs and individual members
- ensures(is_fresh()) should pass on functions that perform allocation directly and return a new object
- ensures(is_fresh()) should pass on functions that call another function that does the allocation and then return the object (this is to check that we propagate freshness correctly across contracts)
I will review the implementation logic in the next pass, and finally the code style in a later pass.
One high-level issue that I am confused about:
// When enforced, this contract should pass
bool sub_ptr_values(int *x, int *y)
__CPROVER_requires(
__CPROVER_is_fresh(x, sizeof(int)) && __CPROVER_is_fresh(y, sizeof(int))
)
__CPROVER_ensures(
__CPROVER_return_value == *x - *y
)
{
return *x - *y;
}
// A function that uses `sub_ptr_values`
void foo()
{
int *n = malloc(sizeof(int));
// When call is replaced by contract, this will cause an assertion violation?
int diff = sub_ptr_values(n, n);
}
The question here is, how should the contract sub_ptr_values
be written so as to allow possibly-aliased arguments? I see "is_fresh" as enforcing/checking fresh non-aliasing allocations.
Okay I guess one could make the aliasing explicit:
__CPROVER_requires(
__CPROVER_is_fresh(x, sizeof(int)) &&
(y == x || __CPROVER_is_fresh(y, sizeof(int)))
)
Can we add this conditional "is_fresh" case as a regression test?
int o = foo(n); | ||
assert(o >= 10 && o == *n + 5); | ||
return 0; | ||
} |
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.
Missing newline at the end. Please also check other files.
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.
Fixed it.
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.
Sorry, but it looks like this file is still missing a newline at the end?
I did refresh the PR page and I am looking at the current changes.
@SaswatPadhi I addressed all your comments and added more regression tests. Could you take another look at it? |
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.
Thanks for improving the regression tests! They were quite helpful for me to understand end-to-end behavior before reviewing the core logic.
I left a few more minor comments below. The contract implementation looks sounds to me. The only remaining major question I have is regarding data structures like lists. I am not sure how one would write a contract using is_fresh
for a function that takes a linked list and say counts its length. I can see how that could be written for lists that have a constant length, but it's not obvious how to write it for bounded lists, not unbounded, but that do not have a constant length.
If inductive structures like lists are beyond the scope of this first version of code contracts, let me know.
int o = foo(n); | ||
assert(o >= 10 && o == *n + 5); | ||
return 0; | ||
} |
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.
Sorry, but it looks like this file is still missing a newline at the end?
I did refresh the PR page and I am looking at the current changes.
src/ansi-c/library/contracts.c
Outdated
|
||
// | ||
// Below are templates of the functions that | ||
// will be instantiated for each contract function. |
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.
Are these templates used?
I might be wrong, but it looks like we generate concrete instances of these templates during instrumentation, but from scratch.
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.
We're not using these templates at the moment, but generating concrete instances during instrumentation as you observed. I removed this for now, so we can make use of these templates on a separate PR (as discussed offline).
For inductive data structures, one needs to invoke
This is out of the scope for this PR; however, we should talk more about it offline to discuss ideas on how we can address this in the future. |
Signed-off-by: Felipe R. Monteiro <[email protected]>
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.
Thanks! LGTM 😃
There are a bunch of TODOs but let's merge this first and then tackle the refactoring, documentation etc. in future PRs.
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.
Nothing blocking from my point of view - though like @martin-cs it would be nice to see a clear description of the intended semantics - plus a couple of small queries/comments.
|
||
// | ||
// | ||
// Code largely copied from model_argc_argv.cpp |
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.
Is there any way to factor out the common code, rather than copying?
: is_fresh_baset(_parent, _log, _fun_id) | ||
{ | ||
std::stringstream ssreq, ssensure, ssmemmap; | ||
ssreq /* << CPROVER_PREFIX */ << fun_id << "_call_requires_is_fresh"; |
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.
Are these commented out CPROVER_PREFIX's deliberate? is_fresh_enforcet::is_fresh_enforcet seems to insert the, but they are commented out here - just wondering which is correct, or whether both are?
Signed-off-by: Felipe R. Monteiro [email protected]