-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Prepare miri engine for enforcing validity invariant during execution #54762
Conversation
|
We have an interesting test failure: This #[link_name = "check_static_recursion_foreign_helper"]
extern "C" {
#[allow(dead_code)]
static test_static: c_int;
}
static B: &'static c_int = unsafe { &test_static }; Seems like we set some rather arbitrary alignment for these? Ideally we'd pick an alignment based on the type, would that make sense? Where is the code deciding that? |
18c4e21
to
3e22673
Compare
For now, I made it skip pointers to foreign statics entirely, not even checking their alignment. I think we could do better, but this is not a regression. |
This comment has been minimized.
This comment has been minimized.
|
||
/// Make sure that `value` matches the |
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....."
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 see value
as the subject in this sentence, so it shouldn't need a "the". It's like a name: "Make sure that Berlin matches..."
trace!("validate scalar by layout: {:#?}, {:#?}, {:#?}", value, size, layout); | ||
let (lo, hi) = layout.valid_range.clone().into_inner(); | ||
if lo == u128::min_value() && hi == u128::max_value() { | ||
// Nothing to check |
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.
what about undef checks?
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.
Do we or do we not want to allow undef when the range is the entire possible range? That would mean validate_scalar_layout
would also have to take a const_mode
.
Note that we have type-based checks for that later, where we decide on a type-by-type basis whether we want undef or not. This here is only for additional restrictions that may be imposed on top of what the primitive types say.
) -> EvalResult<'tcx> { | ||
trace!("validate scalar by layout: {:#?}, {:#?}, {:#?}", value, size, layout); | ||
let (lo, hi) = layout.valid_range.clone().into_inner(); | ||
if lo == u128::min_value() && hi == u128::max_value() { |
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.
technically we should be checking whether hi.overflowing_add(1) == lo
, because there are 2^128 possible ways to encode the full range
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.
That's not correct either, we actually pick the range depending on the size of the scalar. I am now using
let max_hi = u128::max_value() >> (128 - size.bits()); // as big as the size fits
Scalar::Ptr(_) => { | ||
// Comparing a ptr with a range is not meaningfully possible. | ||
// In principle, *if* the pointer is inbonds, we could exclude NULL, but | ||
// that does not seem worth 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.
I'm fairly sure I had a test for just this case (enum Foo { E = 0 }
and then transmuting a pointer to the enum type)
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 made this more strict with the latest changes, could you have a look?
I found your test in ub-enum.rs
. Bot it kept failing as expected... maybe because this is actually not considered a primitive type, but an enum, and hence it loads the discriminant and that always fails when it is a pointer?
I finally found a way to unify handling of thin and fat pointers, so I could not resist adding that to this PR. |
Actually we do not set any alignment for these foreign statics, we get a "dangling pointer" error even when just trying to check alignment. For now, I think it's best to keep ignoring them. |
} | ||
static CRASH: () = symbol; | ||
static CRASH: u32 = symbol; |
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 had to change this test because "reading" a ()
does not actually read anything...
Might be worth doing a perf run. @bors try |
Prepare miri engine for enforcing validity invariant during execution In particular, make recursive checking of references optional, and add a `const_mode` parameter that says whether `usize` is allowed to contain a pointer. Also refactor validation a bit to be type-driven at the "leafs" (primitive types), and separately validate scalar layout to catch `NonNull` violations (which it did not properly validate before). Fixes #53826 Also fixes #54751 r? @oli-obk
☀️ Test successful - status-travis |
This comment has been minimized.
This comment has been minimized.
@rust-timer build 98f2e1b |
Success: Queued 98f2e1b with parent c67ea54, comparison URL. |
Unexpectedly things got a bit slower (because now it does that scalar check quite more often than it used to). It's 5% only for very short benchmarks though (clean incremental), and more around 1-2% for the stress tests. |
Scalar::Ptr(ptr) => { | ||
if lo == 1 && hi == max_hi { | ||
// only NULL is not allowed. | ||
// We can call `check_align` to check non-NULL-ness, but have to also look |
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 see how a pointer with an actual (dead or live) allocation could ever be null.
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 you offset a pointer enough, it can overflow to NULL.
The only way we can know it is not NULL is to make sure it is inbounds.
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 you overflow it far enough so it is inbounds again, won't we have the same 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.
Why would that be a problem? The overflow itself is okay. We only allow overflow when using wrapping_offset
.
self.memory.get_fn(ptr).is_ok(); | ||
if !non_null { | ||
// could be NULL | ||
return validation_failure!("a potentially NULL pointer", path); |
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.
needs a test if this is reachable at all, otherwise, remove
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 this is currently unreachable because we have no way in CTFE to add an offset to a pointer. It will be reachable once that is a possibility.
4935c3d
to
9491f2e
Compare
…lidation msgs on error
This does not actually regress anything. It would regress NonNull, but we didn't handle that correctly previously either.
…ling out of aggregate handling Also, make enum variant handling a bit nicer
also less verbose logging
9491f2e
to
fe96f82
Compare
The only actually surprising perf regression seems to be "clean-opt" is supposed to regress 3%, it's 1% here (and 1% I have found impossible to debug, there's just too much noise). Looking at I also tried reproducing "patched incremental: println-opt" for syn, but I am getting vastly different numbers for the instruction count (on the order of 47 billion instead of 27 billion), so I must be doing something else. On those measurements, this patch is a <0.1% slowdown. Here's the commands I used:
|
I managed to get the perf collector running locally, and used it to re-run the syn benchmarks. I am again seeing numbers around 45 billion instead of the 27 billion on the website, and I am seeing a regression of around 0.1%. I'd call that noise. I can't think of anything else I could do. |
@bors r+ |
📌 Commit fe96f82 has been approved by |
Prepare miri engine for enforcing validity invariant during execution In particular, make recursive checking of references optional, and add a `const_mode` parameter that says whether `usize` is allowed to contain a pointer. Also refactor validation a bit to be type-driven at the "leafs" (primitive types), and separately validate scalar layout to catch `NonNull` violations (which it did not properly validate before). Fixes #53826 Also fixes #54751 r? @oli-obk
☀️ Test successful - status-appveyor, status-travis |
Tested on commit rust-lang/rust@0e07c42. Direct link to PR: <rust-lang/rust#54762> 🎉 miri on windows: build-fail → test-pass. 🎉 miri on linux: build-fail → test-pass.
It looks like this may have caused a minor regression across a number of targets on perf |
These targets that regressed all have large constants. That'll likely be caused by us now checking both layout and type invariants. There is some redundancy in the checking there, which I am not sure how to avoid (while keeping the code somewhat reasonably organized). |
Maybe we could not run the layout checks on those value where we know the type checks to be sufficient? E.g. on |
You want to determine that by type? ;) I think you can add references and raw pointers to that list. (References have a layout restriction but we also check that and more in the type-based check.) In fact, for all the types which have a type-based check, that check should be sufficient. Sure, worth a try I guess. I am not convinced it will help much but there is only one way to find out. |
In particular, make recursive checking of references optional, and add a
const_mode
parameter that says whetherusize
is allowed to contain a pointer. Also refactor validation a bit to be type-driven at the "leafs" (primitive types), and separately validate scalar layout to catchNonNull
violations (which it did not properly validate before).Fixes #53826
Also fixes #54751
r? @oli-obk