-
Notifications
You must be signed in to change notification settings - Fork 274
__CPROVER_r/w_ok does not require null or a valid pointer #6416
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
base: develop
Are you sure you want to change the base?
Conversation
7ec3a38
to
a3f383b
Compare
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.
Looks good to me, except I'd like to understand the rw_ok
removal from documentation.
Codecov Report
@@ Coverage Diff @@
## develop #6416 +/- ##
===========================================
+ Coverage 75.98% 76.77% +0.78%
===========================================
Files 1524 1546 +22
Lines 164299 171584 +7285
===========================================
+ Hits 124843 131729 +6886
- Misses 39456 39855 +399
Continue to review full report at Codecov.
|
At present, all three primitives are equivalent as all memory in CBMC is considered | ||
both readable and writeable. If `p` is the null pointer, the primitives return | ||
false. If `p` is valid, the primitives return true if the memory segment |
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.
If size
is zero, shouldn't this primitive return true
even if the pointer is NULL
or invalid
? cc. @SaswatPadhi
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.
+1. Intuitively, reading / writing "nothing" to any memory location could always (vacuously) succeed.
It's a design decision, however, and so we could choose to not do it this way and always for validity.
But currently there seems to be an issue with checking validity of a malloc(0)
result. The standard says that it's not supposed to be deref'ed, so I am not exactly sure what "valid" would mean for the returned pointer.
char *p = malloc(0);
assert(__CPROVER_r_ok(p, 0)); // fails with --pointer-primitive-check; passes otherwise
@feliperodri, correct me if I am wrong here, but this is what we observed, right?
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 don't think we should attempt to give meaning to "zero width memory accesses".
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 am perfectly happy for "__CPROVER_r_ok(p,0))" to fail if p is NULL or invalid.
But, I would want to dig a little into this example by looking at two cases (recall that an implementation of malloc is allowed to return a non-null value for malloc(0), though it may also return NULL):
char *p = malloc(0);
if (p == NULL) {
assert(__CPROVER_r_ok(p,0));
} else {
assert(__CPROVER_r_ok(p,0));
}
Consider following cases:
- First assert & pointer-primitive-checks enabled:
I'm OK with any outcome here: fail with pointer primitive check, fails because __CPROVER_r_ok returned false, or passes. I think Daniel would like this to fail with a pointer primitive check, and I can get on board with that.
- First assert & pointer-primtive-checks disabled:
I'm OK with any outcome here: fails because __CPROVER_r_ok returned false, or passes
- Second assert & pointer-primtive-checks enabled:
I'm of the opinion that this must pass
- Second assert & pointer-primtive-checks disabled:
I'm of the opinion that this must pass
If cases 3 and 4 were to fail we're going to have to write a lot more special cases in our assertions than we need to handle degenerate cases where functions are called with size 0. Those special cases shouldn't be needed.
For case 1 - I think Daniel's desire to not give meaning to 0 size regions argues for keeping the expectation that this fails the pointer-primitive-check if that is enabled.
For case 2 - I'm not sure I care, but no matter whether you return true or false you are picking a meaning for a zero sized access to a NULL pointer.
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 suppose I'm happy with undefined behavior when size is 0... because I can always defined my own version that is __jim_r_ok(p,size) = (size == 0) || __CPROVER_r_ok(p, size);
Mind you if I wanted something like __jim_r_ok that would fail if p was invalid even if size was 0 and pointer-checks were in abled I guess I would be out of luck. I can probably live with that too.
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.
You can get the version that fails with size 0 simply like this:
(size == 0) ? 0 : __CPROVER_r_ok(p, size)
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 meant when called with the -pointer-checks enabled would fail (not return false) with a pointer check error if called with __jim_r_ok(invalid-pointer,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.
Ah, apologies; I misunderstood.
878192a
to
9663df6
Compare
Ok, let's do the following: This implies that it's true when |
f10ef42
to
023e6e8
Compare
I like very much where we ended up on this. Thanks Daniel. |
This adds documentation for __CPROVER_rw_ok, in addition to r_ok and w_ok.
The definition of __CPROVER_r/w_ok (in goto_check.cpp) does include the case of an invalid pointer, and thus, there is no need to check that the pointer given to these predicates is valid.
023e6e8
to
f060b9c
Compare
Dead/deallocated objects still pose a problem.
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.
The following example might serve to demonstrate why, in my opinion, this check should not be removed:
#include <stdlib.h>
int main()
{
int *p = malloc(sizeof(int));
free(p);
__CPROVER_assume(__CPROVER_r_ok(p, sizeof(int)));
__CPROVER_assert(0, "should be unreachable");
}
CBMC wrongly reports that this assertion is reachable (but --pointer-primitive-check
calls out the problem in the assumption).
Wouldn't I want __CPROVER_r_ok(p, sizeof(int)) in this case (p has just been free-ed) to return false rather than error out? |
@tautschnig happy to review this when / if you are happy with the requested changes. |
@tautschnig as #7395 is now merged, does this need to be revisited? |
The definition of
__CPROVER_r/w_ok
(in goto_check.cpp) does include the caseof an invalid pointer, and thus, there is no need to check that the pointer
given to these predicates is valid.