Skip to content

Miscompile in the GVN transform #130853

Closed
@jwong101

Description

@jwong101

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.

Metadata

Metadata

Assignees

Labels

A-codegenArea: Code generationA-mir-optArea: MIR optimizationsI-miscompileIssue: Correct Rust code lowers to incorrect machine codeI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-mir-optWorking group: MIR optimizations

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions