Safe function MIR reads discriminant of moved-out local · Issue #91029 · rust-lang/rust (original) (raw)

#90895 caused the Miri test suite to fail, and further investigation showed that this is due to strange MIR being generated for Option::map: when I dump the MIR for this code and look at the pre-const-prop code, it looks like:

fn <impl at map.rs:11:1: 18:2>::map(_1: Option<T>, _2: F) -> Option<U> {
    debug self => _1;                    // in scope 0 at map.rs:12:38: 12:42
    debug f => _2;                       // in scope 0 at map.rs:12:44: 12:45
    let mut _0: Option<U>;               // return place in scope 0 at map.rs:12:53: 12:62
    let mut _3: isize;                   // in scope 0 at map.rs:14:13: 14:21
    let _4: T;                           // in scope 0 at map.rs:14:19: 14:20
    let mut _5: U;                       // in scope 0 at map.rs:14:31: 14:35
    let mut _6: F;                       // in scope 0 at map.rs:14:31: 14:32
    let mut _7: (T,);                    // in scope 0 at map.rs:14:31: 14:35
    let mut _8: T;                       // in scope 0 at map.rs:14:33: 14:34
    let mut _9: bool;                    // in scope 0 at map.rs:17:5: 17:6
    let mut _10: bool;                   // in scope 0 at map.rs:17:5: 17:6
    let mut _11: isize;                  // in scope 0 at map.rs:17:5: 17:6
    let mut _12: isize;                  // in scope 0 at map.rs:17:5: 17:6
    let mut _13: isize;                  // in scope 0 at map.rs:17:5: 17:6
    scope 1 {
        debug x => _4;                   // in scope 1 at map.rs:14:19: 14:20
    }

    bb0: {
        _10 = const false;               // scope 0 at map.rs:13:15: 13:19
        _9 = const false;                // scope 0 at map.rs:13:15: 13:19
        _10 = const true;                // scope 0 at map.rs:13:15: 13:19
        _9 = const true;                 // scope 0 at map.rs:13:15: 13:19
        _3 = discriminant(_1);           // scope 0 at map.rs:13:15: 13:19
        switchInt(move _3) -> [0_isize: bb1, 1_isize: bb3, otherwise: bb2]; // scope 0 at map.rs:13:9: 13:19
    }

    bb1: {
        discriminant(_0) = 0;            // scope 0 at map.rs:15:22: 15:27
        goto -> bb7;                     // scope 0 at map.rs:15:22: 15:27
    }

    bb2: {
        unreachable;                     // scope 0 at map.rs:13:15: 13:19
    }

    bb3: {
        _4 = move ((_1 as Somex).0: T);  // scope 0 at map.rs:14:19: 14:20
        _9 = const false;                // scope 1 at map.rs:14:31: 14:32
        _6 = move _2;                    // scope 1 at map.rs:14:31: 14:32
        _8 = move _4;                    // scope 1 at map.rs:14:33: 14:34
        (_7.0: T) = move _8;             // scope 1 at map.rs:14:31: 14:35
        _5 = <F as FnOnce<(T,)>>::call_once(move _6, move _7) -> [return: bb4, unwind: bb8]; // scope 1 at map.rs:14:31: 14:35
                                         // mir::Constant
                                         // + span: map.rs:14:31: 14:32
                                         // + literal: Const { ty: extern "rust-call" fn(F, (T,)) -> <F as std::ops::FnOnce<(T,)>>::Output {<F as std::ops::FnOnce<(T,)>>::call_once}, val: Value(Scalar(<ZST>)) }
    }

    bb4: {
        ((_0 as Somex).0: U) = move _5;  // scope 1 at map.rs:14:25: 14:36
        discriminant(_0) = 1;            // scope 1 at map.rs:14:25: 14:36
        goto -> bb7;                     // scope 0 at map.rs:17:5: 17:6
    }

    bb5: {
        _11 = discriminant(_1);          // scope 0 at map.rs:17:5: 17:6
        return;                          // scope 0 at map.rs:17:6: 17:6
    }

    bb6: {
        drop(_2) -> [return: bb5, unwind: bb8]; // scope 0 at map.rs:17:5: 17:6
    }

    bb7: {
        switchInt(_9) -> [false: bb5, otherwise: bb6]; // scope 0 at map.rs:17:5: 17:6
    }

    bb8 (cleanup): {
        _13 = discriminant(_1);          // scope 0 at map.rs:17:5: 17:6
        resume;                          // scope 0 at map.rs:12:5: 17:6
    }
}

The strangeness is in bb5: this is where we end up after the closure returns, but it reads the discriminant of _1 which we have already moved out of at this point! With Miri now checking validity when the discriminant is read, it complains (in the linked-list testcase) that there is a dangling Box here (I guess the closure deallocated that Box). There are also proposals that would make a move de-initialize the memory it is moving from, which would certainly make looking at its discriminant again UB.

I am not quite sure where this comes from, but it does seem in conflict with the idea that only valid values can have their discriminant read. Cc #89764 @wesleywiser