-
Notifications
You must be signed in to change notification settings - Fork 274
enable nondeterministic pointers #6326
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
What would be the semantics for this case (slight modification of the previous test case) int *nondet_pointer();
int main()
{
int *q = nondet_pointer(); // Reordered to be 1st
int x = 123;
int *p = &x;
if(p == q)
__CPROVER_assert(*q == 123, "value of *q");
return 0;
} |
Also int *nondet_pointer();
int main()
{
int x = 123;
int *p = &x;
int *q = nondet_pointer();
__CPROVER_assume(p == q);
__CPROVER_assert(*q == 123, "value of *q");
return 0;
} |
My expectation would be that q must be NULL because there are no other pointers to choose from when q is assigned, therefore the then clause of the conditional is unreachable and the assertion vacuously true. |
int *py = &y; | ||
int *q = nondet_pointer(); | ||
|
||
if(q == px || q == py) |
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.
Why is it sufficient to test if q == px or q == py? Should we not also have to test same_object?
Why do I say that? Daniel has pointed out to me that we can construct two pointers that are equal but have different provenance and accessing one may cause an error.
Suppose y comes after x in memory. Suppose we choose q to be derived from px (&x) but with an offset large enough to point it at y. Now q == py ... but is it legal to dereference it and get 456, or should I expect failure?
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.
Nondeterministic pointers are outside of the semantics of C; we can make that go either way.
Here I am expecting q must be either NULL or a pointer derived from p since that is the only pointer around. The assumption makes q equal to p and since it must have been derived from p it is safe to dereference and so I expect *q to be 123 and the assertion to pass. |
Would that last assertion pass or fail ? int *nondet_pointer();
int main()
{
int x;
int *p = &x;
int *q = nondet_pointer();
if(p == q) {
__CPROVER_assume(*q == 123);
__CPROVER_assert(x == 123, "would that pass or fail ?");
}
return 0;
} |
We will have to put this one on hold for a bit. We discovered an unsoundness Friday when this is combined with the loop contracts feature. We will have to add some more checks to loop contracts to ensure we don't break soundness when this is added. I'll follow up with an explanation. |
Maybe no issue. Will investigate further within a couple of days. |
Looks like there is not an issue with nondet_pointer ... only perhaps with when we should use it. Let's proceed with documenting this so we can merge it. |
According to our offline discussions, the invalid pointers introduced by |
@kroening Can you also make the necessary updates to the is_invalid check for consistency with this new feature? |
5bc017d
to
b8b6272
Compare
Did I see the IS_INVALID which we need to pair with this make it in via another PR? |
Perhaps #6366 |
int index; | ||
|
||
__CPROVER_assume(index >= 0 && index < 10); | ||
__CPROVER_assume(__CPROVER_same_object(p, q)); |
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.
When I run cbmc --pointer-primitive-check nondet-pointer5.c
, I got the following result
** Results:
nondet-pointer5.c function main
[main.pointer_primitives.1] line 11 dead object in POINTER_OBJECT((const void *)p): SUCCESS
[main.pointer_primitives.2] line 11 pointer outside object bounds in POINTER_OBJECT((const void *)p): SUCCESS
[main.pointer_primitives.3] line 11 pointer invalid in POINTER_OBJECT((const void *)q): FAILURE
[main.pointer_primitives.4] line 11 deallocated dynamic object in POINTER_OBJECT((const void *)q): SUCCESS
[main.pointer_primitives.5] line 11 dead object in POINTER_OBJECT((const void *)q): SUCCESS
[main.pointer_primitives.6] line 11 pointer outside object bounds in POINTER_OBJECT((const void *)q): FAILURE
[main.pointer_primitives.7] line 12 pointer invalid in POINTER_OFFSET((const void *)q): SUCCESS
[main.pointer_primitives.8] line 12 deallocated dynamic object in POINTER_OFFSET((const void *)q): SUCCESS
[main.pointer_primitives.9] line 12 dead object in POINTER_OFFSET((const void *)q): SUCCESS
[main.pointer_primitives.10] line 12 pointer outside object bounds in POINTER_OFFSET((const void *)q): FAILURE
[main.assertion.1] line 16 value of array[index]: SUCCESS
** 3 of 11 failed (3 iterations)
The trace showed that the offset of the pointer q
exceeds the object size, which violates the pointer-primitive check
POINTER_OBJECT(NULL) == POINTER_OBJECT(q) || POINTER_OFFSET(q) >= 0l && OBJECT_SIZE(q) >= (unsigned long int)POINTER_OFFSET(q) + 1ul
.
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.
Yeah --pointer-primitive-check
shouldn't look into offsets when we are just comparing same_object
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.
Hey Daniel (@kroening). We were wondering about this regression that fails with this PR. We don't really need the offsets to be in range for the pointers for same_object to have a well defined meaning do we? Is there another assumption we could/should add to make this go away? Should same_object be relaxed to not worry about the legality of the indexes? Other thoughts? Thanks.
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.
Not an easy question. You realise that this could result in a situation whereby you've just asserted same_object, and then you see a concrete trace of the program where that very pointer points at the beginning of some other object.
I can live with that, given that the C standard has a notion of a pointer that points just beyond the end of a sequence, but this is a prime candidate for confusing users.
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.
Yes, we do realize that. We're needing to constrain a pointer in a loop invariant such that as the pointer is iterated through the array it remains pointing to the same object. We'll then further constrain it to be inbounds for the array (or one beyond). To do this without warnings we need to shut off the assertion about the index being out of range in the same_object check. We'd like to keep the other sanity checks though. We could define our own version were we shut off all the pointer-primitive checks, but just locally for that one assertion ... if we had the ability to do that with a pragma, which is a request on another thread. If we had that we could more easily make progress ourselves. Or, we could implement a version with no primitives checks as a new primitive - but that seems heavyweight.
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 have put up #6491 to adjust the behaviour of pointer-primitive-check.
Shouldn't we also
|
This commit enables the use of nondeterministic pointers, to allow declarative modeling of states that include pointers.
b8b6272
to
3789670
Compare
Codecov Report
@@ Coverage Diff @@
## develop #6326 +/- ##
========================================
Coverage 76.04% 76.04%
========================================
Files 1546 1546
Lines 165485 165494 +9
========================================
+ Hits 125836 125858 +22
+ Misses 39649 39636 -13
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.
Yes. I wonder if it should include an / the invalid object or some other kind of "this pointer points to something that is not accessible via other means" / "writes to this are effectively ignored" partially because that would be useful but also because hooks on this could be used to drive a refinement loop of "what input data structure is assumed".
{ | ||
if(expr.type().id() == ID_pointer) | ||
{ | ||
// we'll take the union of all objects we see, with unspecified offsets |
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 worry a little that the semantics of "nondet but only on objects that exist at the point it was nondet'd" might introduce some unexpected effects BUT I am not sure I have a better idea of a clearer semantics.
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 think @tautschnig has a 2nd PR in the works that addresses this concern, but it builds on this PR.
@tautschnig will progress #6442, but we'll merge this for now. |
This commit enables the use of nondeterministic pointers, to allow
declarative modeling of states that include pointers.