Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 3ee62a9

Browse files
Do not optimize out SwitchInt before borrowck, or if Zmir-preserve-ub
1 parent d4f880f commit 3ee62a9

18 files changed

+197
-51
lines changed

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,8 +817,8 @@ fn test_unstable_options_tracking_hash() {
817817
tracked!(min_function_alignment, Some(Align::EIGHT));
818818
tracked!(mir_emit_retag, true);
819819
tracked!(mir_enable_passes, vec![("DestProp".to_string(), false)]);
820-
tracked!(mir_keep_place_mention, true);
821820
tracked!(mir_opt_level, Some(4));
821+
tracked!(mir_preserve_ub, true);
822822
tracked!(move_size_limit, Some(4096));
823823
tracked!(mutable_noalias, false);
824824
tracked!(next_solver, NextSolverConfig { coherence: true, globally: true });

compiler/rustc_mir_transform/src/early_otherwise_branch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
224224
// Since this optimization adds new basic blocks and invalidates others,
225225
// clean up the cfg to make it nicer for other passes
226226
if should_cleanup {
227-
simplify_cfg(body);
227+
simplify_cfg(tcx, body);
228228
}
229229
}
230230

compiler/rustc_mir_transform/src/inline.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl<'tcx> crate::MirPass<'tcx> for Inline {
6363
let _guard = span.enter();
6464
if inline::<NormalInliner<'tcx>>(tcx, body) {
6565
debug!("running simplify cfg on {:?}", body.source);
66-
simplify_cfg(body);
66+
simplify_cfg(tcx, body);
6767
deref_finder(tcx, body);
6868
}
6969
}
@@ -99,7 +99,7 @@ impl<'tcx> crate::MirPass<'tcx> for ForceInline {
9999
let _guard = span.enter();
100100
if inline::<ForceInliner<'tcx>>(tcx, body) {
101101
debug!("running simplify cfg on {:?}", body.source);
102-
simplify_cfg(body);
102+
simplify_cfg(tcx, body);
103103
deref_finder(tcx, body);
104104
}
105105
}

compiler/rustc_mir_transform/src/match_branches.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification {
4545
}
4646

4747
if should_cleanup {
48-
simplify_cfg(body);
48+
simplify_cfg(tcx, body);
4949
}
5050
}
5151

compiler/rustc_mir_transform/src/remove_place_mention.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub(super) struct RemovePlaceMention;
88

99
impl<'tcx> crate::MirPass<'tcx> for RemovePlaceMention {
1010
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
11-
!sess.opts.unstable_opts.mir_keep_place_mention
11+
!sess.opts.unstable_opts.mir_preserve_ub
1212
}
1313

1414
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {

compiler/rustc_mir_transform/src/remove_unneeded_drops.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl<'tcx> crate::MirPass<'tcx> for RemoveUnneededDrops {
3535
// if we applied optimizations, we potentially have some cfg to cleanup to
3636
// make it easier for further passes
3737
if should_simplify {
38-
simplify_cfg(body);
38+
simplify_cfg(tcx, body);
3939
}
4040
}
4141

compiler/rustc_mir_transform/src/simplify.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
//! Here the block (`{ return; }`) has the return type `char`, rather than `()`, but the MIR we
2727
//! naively generate still contains the `_a = ()` write in the unreachable block "after" the
2828
//! return.
29+
//!
30+
//! **WARNING**: This is one of the few optimizations that runs on built and analysis MIR, and
31+
//! so its effects may affect the type-checking, borrow-checking, and other analysis of MIR.
32+
//! We must be extremely careful to only apply optimizations that preserve UB and all
33+
//! non-determinism, since changes here can affect which programs compile in an insta-stable way.
34+
//! The normal logic that a program with UB can be changed to do anything does not apply to
35+
//! pre-"runtime" MIR!
2936
3037
use rustc_index::{Idx, IndexSlice, IndexVec};
3138
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
@@ -66,8 +73,8 @@ impl SimplifyCfg {
6673
}
6774
}
6875

69-
pub(super) fn simplify_cfg(body: &mut Body<'_>) {
70-
CfgSimplifier::new(body).simplify();
76+
pub(super) fn simplify_cfg<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
77+
CfgSimplifier::new(tcx, body).simplify();
7178
remove_dead_blocks(body);
7279

7380
// FIXME: Should probably be moved into some kind of pass manager
@@ -79,9 +86,9 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
7986
self.name()
8087
}
8188

82-
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
89+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
8390
debug!("SimplifyCfg({:?}) - simplifying {:?}", self.name(), body.source);
84-
simplify_cfg(body);
91+
simplify_cfg(tcx, body);
8592
}
8693

8794
fn is_required(&self) -> bool {
@@ -90,12 +97,13 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg {
9097
}
9198

9299
struct CfgSimplifier<'a, 'tcx> {
100+
preserve_switch_reads: bool,
93101
basic_blocks: &'a mut IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
94102
pred_count: IndexVec<BasicBlock, u32>,
95103
}
96104

97105
impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
98-
fn new(body: &'a mut Body<'tcx>) -> Self {
106+
fn new(tcx: TyCtxt<'tcx>, body: &'a mut Body<'tcx>) -> Self {
99107
let mut pred_count = IndexVec::from_elem(0u32, &body.basic_blocks);
100108

101109
// we can't use mir.predecessors() here because that counts
@@ -110,9 +118,12 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
110118
}
111119
}
112120

121+
// Preserve `SwitchInt` reads on built and analysis MIR, or if `-Zmir-preserve-ub`.
122+
let preserve_switch_reads = matches!(body.phase, MirPhase::Built | MirPhase::Analysis(_))
123+
|| tcx.sess.opts.unstable_opts.mir_preserve_ub;
113124
let basic_blocks = body.basic_blocks_mut();
114125

115-
CfgSimplifier { basic_blocks, pred_count }
126+
CfgSimplifier { preserve_switch_reads, basic_blocks, pred_count }
116127
}
117128

118129
fn simplify(mut self) {
@@ -253,9 +264,15 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
253264

254265
// turn a branch with all successors identical to a goto
255266
fn simplify_branch(&mut self, terminator: &mut Terminator<'tcx>) -> bool {
256-
match terminator.kind {
257-
TerminatorKind::SwitchInt { .. } => {}
258-
_ => return false,
267+
// Removing a `SwitchInt` terminator may remove reads that result in UB,
268+
// so we must not apply this optimization before borrowck or when
269+
// `-Zmir-preserve-ub` is set.
270+
if self.preserve_switch_reads {
271+
return false;
272+
}
273+
274+
let TerminatorKind::SwitchInt { .. } = terminator.kind else {
275+
return false;
259276
};
260277

261278
let first_succ = {

compiler/rustc_session/src/options.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,12 +2319,12 @@ options! {
23192319
mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED],
23202320
"include extra comments in mir pretty printing, like line numbers and statement indices, \
23212321
details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"),
2322-
mir_keep_place_mention: bool = (false, parse_bool, [TRACKED],
2323-
"keep place mention MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \
2324-
(default: no)"),
23252322
#[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")]
23262323
mir_opt_level: Option<usize> = (None, parse_opt_number, [TRACKED],
23272324
"MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"),
2325+
mir_preserve_ub: bool = (false, parse_bool, [TRACKED],
2326+
"keep place mention statements and reads in trivial SwitchInt terminators, which are interpreted \
2327+
e.g., by miri; implies -Zmir-opt-level=0 (default: no)"),
23282328
mir_strip_debuginfo: MirStripDebugInfo = (MirStripDebugInfo::None, parse_mir_strip_debuginfo, [TRACKED],
23292329
"Whether to remove some of the MIR debug info from methods. Default: None"),
23302330
move_size_limit: Option<usize> = (None, parse_opt_number, [TRACKED],

src/tools/miri/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[
169169
"-Zalways-encode-mir",
170170
"-Zextra-const-ub-checks",
171171
"-Zmir-emit-retag",
172-
"-Zmir-keep-place-mention",
172+
"-Zmir-preserve-ub",
173173
"-Zmir-opt-level=0",
174174
"-Zmir-enable-passes=-CheckAlignment,-CheckNull",
175175
// Deduplicating diagnostics means we miss events when tracking what happens during an
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Ensure that we don't optimize out `SwitchInt` reads even if that terminator
2+
// branches to the same basic block on every target, since the operand may have
3+
// side-effects that affect analysis of the MIR.
4+
//
5+
// See <https://github.com/rust-lang/miri/issues/4237>.
6+
7+
use std::mem::MaybeUninit;
8+
9+
fn main() {
10+
let uninit: MaybeUninit<i32> = MaybeUninit::uninit();
11+
let bad_ref: &i32 = unsafe { uninit.assume_init_ref() };
12+
let &(0 | _) = bad_ref;
13+
//~^ ERROR: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
14+
}

0 commit comments

Comments
 (0)