Skip to content

FakeRead is semantically meaningful to miri (but gets optimized away) #97092

Open
@nikomatsakis

Description

@nikomatsakis

Given this sample program:

#![feature(generators)]

fn main() {
    let x = Some(22);
    let _a = match x {
        Some(y) if foo() => (),
        _ => (),
    };
}

fn foo() -> bool { 
    true
}

the desugaring of the match intentionally adds some borrows when executing the guard:

    bb3: {
        StorageLive(_6);                 // scope 1 at foo.rs:6:14: 6:15
        _6 = &((_1 as Some).0: i32);     // scope 1 at foo.rs:6:14: 6:15
        _4 = &shallow _1;                // scope 1 at foo.rs:5:20: 5:21
        StorageLive(_7);                 // scope 1 at foo.rs:6:20: 6:25
        _7 = foo() -> [return: bb4, unwind: bb8]; // scope 1 at foo.rs:6:20: 6:25
                                         // mir::Constant
                                         // + span: foo.rs:6:20: 6:23
                                         // + literal: Const { ty: fn() -> bool {foo}, val: Value(Scalar(<ZST>)) }
    }

    bb4: {
        switchInt(move _7) -> [false: bb6, otherwise: bb5]; // scope 1 at foo.rs:6:20: 6:25
    }

    bb5: {
        StorageDead(_7);                 // scope 1 at foo.rs:6:24: 6:25
        FakeRead(ForMatchGuard, _4);     // scope 1 at foo.rs:6:24: 6:25
        FakeRead(ForGuardBinding, _6);   // scope 1 at foo.rs:6:24: 6:25

The purposeof the FakeRead of _4and _6 is to ensure that the discriminant does not change while the guard executes -- i.e., that the guard doesn't (via unsafe code, say) mutate x to be None. But after optimizations those FakeReads are removed:


    bb0: {
        Deinit(_1);                      // scope 0 at foo.rs:4:13: 4:21
        ((_1 as Some).0: i32) = const 22_i32; // scope 0 at foo.rs:4:13: 4:21
        discriminant(_1) = 1;            // scope 0 at foo.rs:4:13: 4:21
        _4 = &((_1 as Some).0: i32);     // scope 1 at foo.rs:6:14: 6:15
        _5 = foo() -> bb1;               // scope 1 at foo.rs:6:20: 6:25
                                         // mir::Constant
                                         // + span: foo.rs:6:20: 6:23
                                         // + literal: Const { ty: fn() -> bool {foo}, val: Value(Scalar(<ZST>)) }
    }

    bb1: {
        switchInt(move _5) -> [false: bb3, otherwise: bb2]; // scope 1 at foo.rs:6:20: 6:25
    }

this means that foo() could trigger writes without causing UB. This seems bad!

UPDATE: This is overstating the case. It's ok for us to optimize FakeRead away, but tools like miri or some future sanitizers would still want to see them (for the reasons give above).

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-mir-optArea: MIR optimizationsA-miriArea: The miri toolC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-opsemRelevant to the opsem team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions