Open
Description
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 _4
and _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).