Open
Description
openedon Sep 25, 2024
I tried this code:
unsafe fn src(x: &&u8) -> bool {
let y = **x;
unknown();
**x == y
}
static mut SUSSY: *mut u8 = core::ptr::null_mut();
#[inline(never)]
unsafe fn unknown() {
*SUSSY = 1;
}
fn main() {
let mut s = 0;
unsafe {
SUSSY = core::ptr::addr_of_mut!(s);
println!("{}", src(&*core::ptr::addr_of!(SUSSY).cast::<&u8>()));
}
}
I expected to see this happen:
This should print false
, as I believe this is DB under both Stacked and Tree borrows(according to MIRI).
Instead, this happened:
It returns false
in Debug mode, and the GVN MIR pass makes src()
unconditionally return true
in Release mode.
$ cargo miri run
Compiling sus v0.1.0 (/Users/jwong3/test/sus)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `/Users/jwong3/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/sus`
false
$ cargo run -r
Compiling sus v0.1.0 (/Users/jwong3/test/sus)
Finished `release` profile [optimized] target(s) in 0.31s
Running `target/release/sus`
true
Meta
Here's the MIR before the GVN pass:
// MIR for `src` before GVN
fn src(_1: &&u8) -> bool {
debug x => _1;
let mut _0: bool;
let _2: u8;
let _3: ();
let mut _4: u8;
let mut _5: u8;
let mut _6: &u8;
let mut _7: &u8;
scope 1 {
debug y => _2;
}
bb0: {
StorageLive(_2);
_6 = deref_copy (*_1);
_2 = copy (*_6);
_3 = unknown() -> [return: bb1, unwind continue];
}
bb1: {
StorageLive(_4);
_7 = deref_copy (*_1);
_4 = copy (*_7);
StorageLive(_5);
_5 = copy _2;
_0 = Eq(move _4, move _5);
StorageDead(_5);
StorageDead(_4);
StorageDead(_2);
return;
}
}
After
// MIR for `src` after GVN
fn src(_1: &&u8) -> bool {
debug x => _1;
let mut _0: bool;
let _2: u8;
let _3: ();
let mut _4: u8;
let mut _5: u8;
let mut _6: &u8;
let mut _7: &u8;
scope 1 {
debug y => _2;
}
bb0: {
nop;
_6 = copy (*_1);
_2 = copy (*_6);
_3 = unknown() -> [return: bb1, unwind continue];
}
bb1: {
StorageLive(_4);
_7 = copy _6;
_4 = copy _2;
StorageLive(_5);
_5 = copy _2;
_0 = const true;
StorageDead(_5);
StorageDead(_4);
nop;
return;
}
}
It would be justified to make src()
return true
if _6
was dereferenced again in bb1
, however, the write in unknown()
shouldn't invalidate the actual pointer stored in _1
if my understanding of Stacked Borrows is correct.
This is present in both Stable and Nightly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Metadata
Assignees
Labels
Area: MIR optimizationsArea: MIR optimizationsIssue: Correct Rust code lowers to incorrect machine codeIssue: Correct Rust code lowers to incorrect machine codeIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessHigh priorityHigh priorityRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.