-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Add runtime validity checks inside MaybeUninit::assume_init #98073
Conversation
Some changes occured to the CTFE / Miri engine cc @rust-lang/miri Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
r? @nagisa (rust-highfive has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
(I haven't reviewed the PR, just commenting on the idea). I think that rather than being added into |
This is mainly just a safety net to try and catch UB. There is already some checks similar to this in the stdlib already, see #92686. This is just a pretty extreme runtime check, I'm not proposing to make it a safe method. One case where this can't detect UB is use of uninit memory. Without memory sanitizer, we just can't notice when someone forgot to write to a MaybeUninit. Another case that's UB that we can't detect here (even with memory sanitizer), is use of pointer bytes as an integer. Miri can detect both of these, since it has the extra runtime tracking to know what bytes are what. Also, we're not checking enums here, but that's not a strict limitation, it just complicates the code since you need to have conditionals (check byte |
Also I forgot to add them at first so I don't think highfive will ping you but hello @bjorn3 I believe rustc_codegen_cranelift is yours I'm once again messing with your intrinsics By visual inspection it looks fine to me, I did roughly the same thing as in rustc_codegen_ssa, but is there a way for me to actually test this? |
This comment has been minimized.
This comment has been minimized.
The cg_clif change looks fine. |
This comment has been minimized.
This comment has been minimized.
4935692
to
8f79b5a
Compare
This fails a test, and I'm not sure what the best way to get around it is. It's calling mem::replace and then looking to see if any other I could cfg out the whole of the assertions here so you both have to build the stdlib and pass a flag to enable the assertions (like we do for That, or let the tests ask to rebuild the stdlib, disabling the debug assertions. |
This comment has been minimized.
This comment has been minimized.
The job Click to see the possible cause of the failure (guessed by this bot)
|
pub const unsafe fn assume_init(self) -> T { | ||
// SAFETY: the caller must guarantee that `self` is initialized. | ||
// This also means that `self` must be a `value` variant. | ||
unsafe { | ||
intrinsics::assert_inhabited::<T>(); | ||
|
||
intrinsics::assert_unsafe_precondition!(intrinsics::assert_validity_of::<T>( |
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.
Implementation wise, a more appropriate way would probably be to make this entire method an intrinsic/language item/whatever, rather than just the validity assertion. Doing so would also mean that the concerns about “how was libstd built” no longer matter since the callers would be calling into an intrinsic, giving an opportunity to codegen this function into the calling crate according to that crate’s flags.
@@ -1,7 +1,7 @@ | |||
// needs-sanitizer-support | |||
// needs-sanitizer-memory | |||
// | |||
// compile-flags: -Z sanitizer=memory -Zsanitizer-memory-track-origins -O | |||
// compile-flags: -Z sanitizer=memory -Zno-validity-invariant-checks -Zsanitizer-memory-track-origins -O |
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.
Before landing – the flag should be inverted and enabling it by default would need to be considered alongside stabilization of this feature as a whole.
// run-pass | ||
// revisions: disabled normal sanitized | ||
// [disabled]compile-flags: -Zno-validity-invariant-checks | ||
// [sanitized]compile-flags: -Z sanitizer=memory |
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 will need to ignore this test based on needs-sanitizer-*
. grep for needs-sanitizer
in the test suite for examples of use.
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, I tried just requiring needs-sanitizer-memory
for just the sanitized
revision, but that doesn't seem to be allowed for a revision.
I'll split the test into two and just require the sanitizer to exist for the -Z sanitizer=memory test. Alternatively, I could try and get compiletest to allow [sanitized]needs-sanitizer-memory
, or just put the whole test as requiring it.
@@ -156,7 +156,6 @@ | |||
#![feature(const_slice_from_ref)] | |||
#![feature(const_slice_index)] | |||
#![feature(const_is_char_boundary)] | |||
// |
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.
Unrelated change, probably should undo (the extra space does give some sense of separation between blocks that isn’t really there with just a comment alone)
@@ -37,6 +37,9 @@ pub enum ConstValue<'tcx> { | |||
/// Used only for `&[u8]` and `&str` | |||
Slice { data: ConstAllocation<'tcx>, start: usize, end: usize }, | |||
|
|||
/// Like `Slice`, but for types that aren't 1 byte long. | |||
CustomSlice { data: ConstAllocation<'tcx>, length: usize }, |
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.
Unclear to me why Slice
cannot be adapted for this, but my opinion here is probably not worth much since I don’t fiddle much around mir/interpret
.
I think there is a way to ignore tests on debug builds. |
Indeed |
@rustbot author |
☔ The latest upstream changes (presumably #98957) made this pull request unmergeable. Please resolve the merge conflicts. |
Closing this since I'm unlikely to get to it any time soon, and I'll be doing it as a MIR pass instead of checks inside |
This is very much an experiment, I have no idea if this will work or is a good idea.
However, it (when combined with memory sanitizer), did find the use of uninit memory inside
http
. It introduces branches based on all fields it finds, even if those fields are of a type that's valid at any byte value. Granted, that was also found with miri, but this can work where miri doesn't.This probably absolutely kills perf, so calls to assert_validity_of might need to be configured out unless a flag is given when building the stdlib.
One thing we could do is write out a maximally invalid type inside MaybeUninit::uninit, so if you do
MaybeUninit<bool>::uninit()
, it writes out the value2
, so you don't even need memory sanitizer to detect assume_init of uninit memory, if there is a niche value we can write to mean "uninit". That would mean miri and memory sanitizer can't detect the uninit memory, which would be a problem. Still, would be neat.Still need to: