]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/const_prop_lint.rs
Rollup merge of #96372 - compiler-errors:field-method-suggest, r=oli-obk
[rust.git] / compiler / rustc_mir_transform / src / const_prop_lint.rs
1 //! Propagates constants for early reporting of statically known
2 //! assertion failures
3
4 use std::cell::Cell;
5
6 use rustc_ast::Mutability;
7 use rustc_data_structures::fx::FxHashSet;
8 use rustc_hir::def::DefKind;
9 use rustc_hir::HirId;
10 use rustc_index::bit_set::BitSet;
11 use rustc_index::vec::IndexVec;
12 use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
13 use rustc_middle::mir::{
14     AssertKind, BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind,
15     Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement,
16     StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
17 };
18 use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
19 use rustc_middle::ty::subst::{InternalSubsts, Subst};
20 use rustc_middle::ty::{
21     self, ConstInt, ConstKind, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeFoldable,
22 };
23 use rustc_session::lint;
24 use rustc_span::{def_id::DefId, Span};
25 use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
26 use rustc_target::spec::abi::Abi;
27 use rustc_trait_selection::traits;
28
29 use crate::MirLint;
30 use rustc_const_eval::const_eval::ConstEvalErr;
31 use rustc_const_eval::interpret::{
32     self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
33     LocalState, LocalValue, MemPlace, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Scalar,
34     ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
35 };
36
37 /// The maximum number of bytes that we'll allocate space for a local or the return value.
38 /// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just
39 /// Severely regress performance.
40 const MAX_ALLOC_LIMIT: u64 = 1024;
41
42 /// Macro for machine-specific `InterpError` without allocation.
43 /// (These will never be shown to the user, but they help diagnose ICEs.)
44 macro_rules! throw_machine_stop_str {
45     ($($tt:tt)*) => {{
46         // We make a new local type for it. The type itself does not carry any information,
47         // but its vtable (for the `MachineStopType` trait) does.
48         struct Zst;
49         // Printing this type shows the desired string.
50         impl std::fmt::Display for Zst {
51             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52                 write!(f, $($tt)*)
53             }
54         }
55         impl rustc_middle::mir::interpret::MachineStopType for Zst {}
56         throw_machine_stop!(Zst)
57     }};
58 }
59
60 pub struct ConstProp;
61
62 impl<'tcx> MirLint<'tcx> for ConstProp {
63     fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
64         // will be evaluated by miri and produce its errors there
65         if body.source.promoted.is_some() {
66             return;
67         }
68
69         let def_id = body.source.def_id().expect_local();
70         let is_fn_like = tcx.def_kind(def_id).is_fn_like();
71         let is_assoc_const = tcx.def_kind(def_id) == DefKind::AssocConst;
72
73         // Only run const prop on functions, methods, closures and associated constants
74         if !is_fn_like && !is_assoc_const {
75             // skip anon_const/statics/consts because they'll be evaluated by miri anyway
76             trace!("ConstProp skipped for {:?}", def_id);
77             return;
78         }
79
80         let is_generator = tcx.type_of(def_id.to_def_id()).is_generator();
81         // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
82         // computing their layout.
83         if is_generator {
84             trace!("ConstProp skipped for generator {:?}", def_id);
85             return;
86         }
87
88         // Check if it's even possible to satisfy the 'where' clauses
89         // for this item.
90         // This branch will never be taken for any normal function.
91         // However, it's possible to `#!feature(trivial_bounds)]` to write
92         // a function with impossible to satisfy clauses, e.g.:
93         // `fn foo() where String: Copy {}`
94         //
95         // We don't usually need to worry about this kind of case,
96         // since we would get a compilation error if the user tried
97         // to call it. However, since we can do const propagation
98         // even without any calls to the function, we need to make
99         // sure that it even makes sense to try to evaluate the body.
100         // If there are unsatisfiable where clauses, then all bets are
101         // off, and we just give up.
102         //
103         // We manually filter the predicates, skipping anything that's not
104         // "global". We are in a potentially generic context
105         // (e.g. we are evaluating a function without substituting generic
106         // parameters, so this filtering serves two purposes:
107         //
108         // 1. We skip evaluating any predicates that we would
109         // never be able prove are unsatisfiable (e.g. `<T as Foo>`
110         // 2. We avoid trying to normalize predicates involving generic
111         // parameters (e.g. `<T as Foo>::MyItem`). This can confuse
112         // the normalization code (leading to cycle errors), since
113         // it's usually never invoked in this way.
114         let predicates = tcx
115             .predicates_of(def_id.to_def_id())
116             .predicates
117             .iter()
118             .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
119         if traits::impossible_predicates(
120             tcx,
121             traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(),
122         ) {
123             trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id);
124             return;
125         }
126
127         trace!("ConstProp starting for {:?}", def_id);
128
129         let dummy_body = &Body::new(
130             body.source,
131             body.basic_blocks().clone(),
132             body.source_scopes.clone(),
133             body.local_decls.clone(),
134             Default::default(),
135             body.arg_count,
136             Default::default(),
137             body.span,
138             body.generator_kind(),
139             body.tainted_by_errors,
140         );
141
142         // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
143         // constants, instead of just checking for const-folding succeeding.
144         // That would require a uniform one-def no-mutation analysis
145         // and RPO (or recursing when needing the value of a local).
146         let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx);
147         optimization_finder.visit_body(body);
148
149         trace!("ConstProp done for {:?}", def_id);
150     }
151 }
152
153 struct ConstPropMachine<'mir, 'tcx> {
154     /// The virtual call stack.
155     stack: Vec<Frame<'mir, 'tcx>>,
156     /// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
157     written_only_inside_own_block_locals: FxHashSet<Local>,
158     /// Locals that need to be cleared after every block terminates.
159     only_propagate_inside_block_locals: BitSet<Local>,
160     can_const_prop: IndexVec<Local, ConstPropMode>,
161 }
162
163 impl ConstPropMachine<'_, '_> {
164     fn new(
165         only_propagate_inside_block_locals: BitSet<Local>,
166         can_const_prop: IndexVec<Local, ConstPropMode>,
167     ) -> Self {
168         Self {
169             stack: Vec::new(),
170             written_only_inside_own_block_locals: Default::default(),
171             only_propagate_inside_block_locals,
172             can_const_prop,
173         }
174     }
175 }
176
177 impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> {
178     compile_time_machine!(<'mir, 'tcx>);
179     const PANIC_ON_ALLOC_FAIL: bool = true; // all allocations are small (see `MAX_ALLOC_LIMIT`)
180
181     type MemoryKind = !;
182
183     fn load_mir(
184         _ecx: &InterpCx<'mir, 'tcx, Self>,
185         _instance: ty::InstanceDef<'tcx>,
186     ) -> InterpResult<'tcx, &'tcx Body<'tcx>> {
187         throw_machine_stop_str!("calling functions isn't supported in ConstProp")
188     }
189
190     fn find_mir_or_eval_fn(
191         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
192         _instance: ty::Instance<'tcx>,
193         _abi: Abi,
194         _args: &[OpTy<'tcx>],
195         _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
196         _unwind: StackPopUnwind,
197     ) -> InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> {
198         Ok(None)
199     }
200
201     fn call_intrinsic(
202         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
203         _instance: ty::Instance<'tcx>,
204         _args: &[OpTy<'tcx>],
205         _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
206         _unwind: StackPopUnwind,
207     ) -> InterpResult<'tcx> {
208         throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
209     }
210
211     fn assert_panic(
212         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
213         _msg: &rustc_middle::mir::AssertMessage<'tcx>,
214         _unwind: Option<rustc_middle::mir::BasicBlock>,
215     ) -> InterpResult<'tcx> {
216         bug!("panics terminators are not evaluated in ConstProp")
217     }
218
219     fn binary_ptr_op(
220         _ecx: &InterpCx<'mir, 'tcx, Self>,
221         _bin_op: BinOp,
222         _left: &ImmTy<'tcx>,
223         _right: &ImmTy<'tcx>,
224     ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
225         // We can't do this because aliasing of memory can differ between const eval and llvm
226         throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
227     }
228
229     fn access_local(
230         _ecx: &InterpCx<'mir, 'tcx, Self>,
231         frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
232         local: Local,
233     ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
234         let l = &frame.locals[local];
235
236         if l.value == LocalValue::Unallocated {
237             throw_machine_stop_str!("tried to access an uninitialized local")
238         }
239
240         l.access()
241     }
242
243     fn access_local_mut<'a>(
244         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
245         frame: usize,
246         local: Local,
247     ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
248     {
249         if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
250             throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
251         }
252         if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
253             trace!(
254                 "mutating local {:?} which is restricted to its block. \
255                 Will remove it from const-prop after block is finished.",
256                 local
257             );
258             ecx.machine.written_only_inside_own_block_locals.insert(local);
259         }
260         ecx.machine.stack[frame].locals[local].access_mut()
261     }
262
263     fn before_access_global(
264         _tcx: TyCtxt<'tcx>,
265         _machine: &Self,
266         _alloc_id: AllocId,
267         alloc: ConstAllocation<'tcx, Self::PointerTag, Self::AllocExtra>,
268         _static_def_id: Option<DefId>,
269         is_write: bool,
270     ) -> InterpResult<'tcx> {
271         if is_write {
272             throw_machine_stop_str!("can't write to global");
273         }
274         // If the static allocation is mutable, then we can't const prop it as its content
275         // might be different at runtime.
276         if alloc.inner().mutability == Mutability::Mut {
277             throw_machine_stop_str!("can't access mutable globals in ConstProp");
278         }
279
280         Ok(())
281     }
282
283     #[inline(always)]
284     fn init_frame_extra(
285         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
286         frame: Frame<'mir, 'tcx>,
287     ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
288         Ok(frame)
289     }
290
291     #[inline(always)]
292     fn stack<'a>(
293         ecx: &'a InterpCx<'mir, 'tcx, Self>,
294     ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
295         &ecx.machine.stack
296     }
297
298     #[inline(always)]
299     fn stack_mut<'a>(
300         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
301     ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
302         &mut ecx.machine.stack
303     }
304 }
305
306 /// Finds optimization opportunities on the MIR.
307 struct ConstPropagator<'mir, 'tcx> {
308     ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
309     tcx: TyCtxt<'tcx>,
310     param_env: ParamEnv<'tcx>,
311     source_scopes: &'mir IndexVec<SourceScope, SourceScopeData<'tcx>>,
312     local_decls: &'mir IndexVec<Local, LocalDecl<'tcx>>,
313     // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
314     // the last known `SourceInfo` here and just keep revisiting it.
315     source_info: Option<SourceInfo>,
316 }
317
318 impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
319     type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
320
321     #[inline]
322     fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
323         err
324     }
325 }
326
327 impl HasDataLayout for ConstPropagator<'_, '_> {
328     #[inline]
329     fn data_layout(&self) -> &TargetDataLayout {
330         &self.tcx.data_layout
331     }
332 }
333
334 impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
335     #[inline]
336     fn tcx(&self) -> TyCtxt<'tcx> {
337         self.tcx
338     }
339 }
340
341 impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> {
342     #[inline]
343     fn param_env(&self) -> ty::ParamEnv<'tcx> {
344         self.param_env
345     }
346 }
347
348 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
349     fn new(
350         body: &Body<'tcx>,
351         dummy_body: &'mir Body<'tcx>,
352         tcx: TyCtxt<'tcx>,
353     ) -> ConstPropagator<'mir, 'tcx> {
354         let def_id = body.source.def_id();
355         let substs = &InternalSubsts::identity_for_item(tcx, def_id);
356         let param_env = tcx.param_env_reveal_all_normalized(def_id);
357
358         let can_const_prop = CanConstProp::check(tcx, param_env, body);
359         let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
360         for (l, mode) in can_const_prop.iter_enumerated() {
361             if *mode == ConstPropMode::OnlyInsideOwnBlock {
362                 only_propagate_inside_block_locals.insert(l);
363             }
364         }
365         let mut ecx = InterpCx::new(
366             tcx,
367             tcx.def_span(def_id),
368             param_env,
369             ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
370         );
371
372         let ret = ecx
373             .layout_of(body.return_ty().subst(tcx, substs))
374             .ok()
375             // Don't bother allocating memory for ZST types which have no values
376             // or for large values.
377             .filter(|ret_layout| {
378                 !ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
379             })
380             .map(|ret_layout| {
381                 ecx.allocate(ret_layout, MemoryKind::Stack)
382                     .expect("couldn't perform small allocation")
383                     .into()
384             });
385
386         ecx.push_stack_frame(
387             Instance::new(def_id, substs),
388             dummy_body,
389             ret.as_ref(),
390             StackPopCleanup::Root { cleanup: false },
391         )
392         .expect("failed to push initial stack frame");
393
394         ConstPropagator {
395             ecx,
396             tcx,
397             param_env,
398             source_scopes: &dummy_body.source_scopes,
399             local_decls: &dummy_body.local_decls,
400             source_info: None,
401         }
402     }
403
404     fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
405         let op = match self.ecx.eval_place_to_op(place, None) {
406             Ok(op) => op,
407             Err(e) => {
408                 trace!("get_const failed: {}", e);
409                 return None;
410             }
411         };
412
413         // Try to read the local as an immediate so that if it is representable as a scalar, we can
414         // handle it as such, but otherwise, just return the value as is.
415         Some(match self.ecx.try_read_immediate(&op) {
416             Ok(Ok(imm)) => imm.into(),
417             _ => op,
418         })
419     }
420
421     /// Remove `local` from the pool of `Locals`. Allows writing to them,
422     /// but not reading from them anymore.
423     fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
424         ecx.frame_mut().locals[local] =
425             LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
426     }
427
428     fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
429         source_info.scope.lint_root(self.source_scopes)
430     }
431
432     fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
433     where
434         F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
435     {
436         match f(self) {
437             Ok(val) => Some(val),
438             Err(error) => {
439                 trace!("InterpCx operation failed: {:?}", error);
440                 // Some errors shouldn't come up because creating them causes
441                 // an allocation, which we should avoid. When that happens,
442                 // dedicated error variants should be introduced instead.
443                 assert!(
444                     !error.kind().formatted_string(),
445                     "const-prop encountered formatting error: {}",
446                     error
447                 );
448                 None
449             }
450         }
451     }
452
453     /// Returns the value, if any, of evaluating `c`.
454     fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
455         // FIXME we need to revisit this for #67176
456         if c.needs_subst() {
457             return None;
458         }
459
460         match self.ecx.mir_const_to_op(&c.literal, None) {
461             Ok(op) => Some(op),
462             Err(error) => {
463                 let tcx = self.ecx.tcx.at(c.span);
464                 let err = ConstEvalErr::new(&self.ecx, error, Some(c.span));
465                 if let Some(lint_root) = self.lint_root(source_info) {
466                     let lint_only = match c.literal {
467                         ConstantKind::Ty(ct) => match ct.val() {
468                             // Promoteds must lint and not error as the user didn't ask for them
469                             ConstKind::Unevaluated(ty::Unevaluated {
470                                 def: _,
471                                 substs: _,
472                                 promoted: Some(_),
473                             }) => true,
474                             // Out of backwards compatibility we cannot report hard errors in unused
475                             // generic functions using associated constants of the generic parameters.
476                             _ => c.literal.needs_subst(),
477                         },
478                         ConstantKind::Val(_, ty) => ty.needs_subst(),
479                     };
480                     if lint_only {
481                         // Out of backwards compatibility we cannot report hard errors in unused
482                         // generic functions using associated constants of the generic parameters.
483                         err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span));
484                     } else {
485                         err.report_as_error(tcx, "erroneous constant used");
486                     }
487                 } else {
488                     err.report_as_error(tcx, "erroneous constant used");
489                 }
490                 None
491             }
492         }
493     }
494
495     /// Returns the value, if any, of evaluating `place`.
496     fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
497         trace!("eval_place(place={:?})", place);
498         self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
499     }
500
501     /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
502     /// or `eval_place`, depending on the variant of `Operand` used.
503     fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
504         match *op {
505             Operand::Constant(ref c) => self.eval_constant(c, source_info),
506             Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
507         }
508     }
509
510     fn report_assert_as_lint(
511         &self,
512         lint: &'static lint::Lint,
513         source_info: SourceInfo,
514         message: &'static str,
515         panic: AssertKind<impl std::fmt::Debug>,
516     ) {
517         if let Some(lint_root) = self.lint_root(source_info) {
518             self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| {
519                 let mut err = lint.build(message);
520                 err.span_label(source_info.span, format!("{:?}", panic));
521                 err.emit();
522             });
523         }
524     }
525
526     fn check_unary_op(
527         &mut self,
528         op: UnOp,
529         arg: &Operand<'tcx>,
530         source_info: SourceInfo,
531     ) -> Option<()> {
532         if let (val, true) = self.use_ecx(|this| {
533             let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
534             let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
535             Ok((val, overflow))
536         })? {
537             // `AssertKind` only has an `OverflowNeg` variant, so make sure that is
538             // appropriate to use.
539             assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
540             self.report_assert_as_lint(
541                 lint::builtin::ARITHMETIC_OVERFLOW,
542                 source_info,
543                 "this arithmetic operation will overflow",
544                 AssertKind::OverflowNeg(val.to_const_int()),
545             );
546             return None;
547         }
548
549         Some(())
550     }
551
552     fn check_binary_op(
553         &mut self,
554         op: BinOp,
555         left: &Operand<'tcx>,
556         right: &Operand<'tcx>,
557         source_info: SourceInfo,
558     ) -> Option<()> {
559         let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
560         let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
561         // Check for exceeding shifts *even if* we cannot evaluate the LHS.
562         if op == BinOp::Shr || op == BinOp::Shl {
563             let r = r?;
564             // We need the type of the LHS. We cannot use `place_layout` as that is the type
565             // of the result, which for checked binops is not the same!
566             let left_ty = left.ty(self.local_decls, self.tcx);
567             let left_size = self.ecx.layout_of(left_ty).ok()?.size;
568             let right_size = r.layout.size;
569             let r_bits = r.to_scalar().ok();
570             let r_bits = r_bits.and_then(|r| r.to_bits(right_size).ok());
571             if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
572                 debug!("check_binary_op: reporting assert for {:?}", source_info);
573                 self.report_assert_as_lint(
574                     lint::builtin::ARITHMETIC_OVERFLOW,
575                     source_info,
576                     "this arithmetic operation will overflow",
577                     AssertKind::Overflow(
578                         op,
579                         match l {
580                             Some(l) => l.to_const_int(),
581                             // Invent a dummy value, the diagnostic ignores it anyway
582                             None => ConstInt::new(
583                                 ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
584                                 left_ty.is_signed(),
585                                 left_ty.is_ptr_sized_integral(),
586                             ),
587                         },
588                         r.to_const_int(),
589                     ),
590                 );
591                 return None;
592             }
593         }
594
595         if let (Some(l), Some(r)) = (&l, &r) {
596             // The remaining operators are handled through `overflowing_binary_op`.
597             if self.use_ecx(|this| {
598                 let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
599                 Ok(overflow)
600             })? {
601                 self.report_assert_as_lint(
602                     lint::builtin::ARITHMETIC_OVERFLOW,
603                     source_info,
604                     "this arithmetic operation will overflow",
605                     AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
606                 );
607                 return None;
608             }
609         }
610         Some(())
611     }
612
613     fn const_prop(
614         &mut self,
615         rvalue: &Rvalue<'tcx>,
616         source_info: SourceInfo,
617         place: Place<'tcx>,
618     ) -> Option<()> {
619         // Perform any special handling for specific Rvalue types.
620         // Generally, checks here fall into one of two categories:
621         //   1. Additional checking to provide useful lints to the user
622         //        - In this case, we will do some validation and then fall through to the
623         //          end of the function which evals the assignment.
624         //   2. Working around bugs in other parts of the compiler
625         //        - In this case, we'll return `None` from this function to stop evaluation.
626         match rvalue {
627             // Additional checking: give lints to the user if an overflow would occur.
628             // We do this here and not in the `Assert` terminator as that terminator is
629             // only sometimes emitted (overflow checks can be disabled), but we want to always
630             // lint.
631             Rvalue::UnaryOp(op, arg) => {
632                 trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
633                 self.check_unary_op(*op, arg, source_info)?;
634             }
635             Rvalue::BinaryOp(op, box (left, right)) => {
636                 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
637                 self.check_binary_op(*op, left, right, source_info)?;
638             }
639             Rvalue::CheckedBinaryOp(op, box (left, right)) => {
640                 trace!(
641                     "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
642                     op,
643                     left,
644                     right
645                 );
646                 self.check_binary_op(*op, left, right, source_info)?;
647             }
648
649             // Do not try creating references (#67862)
650             Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
651                 trace!("skipping AddressOf | Ref for {:?}", place);
652
653                 // This may be creating mutable references or immutable references to cells.
654                 // If that happens, the pointed to value could be mutated via that reference.
655                 // Since we aren't tracking references, the const propagator loses track of what
656                 // value the local has right now.
657                 // Thus, all locals that have their reference taken
658                 // must not take part in propagation.
659                 Self::remove_const(&mut self.ecx, place.local);
660
661                 return None;
662             }
663             Rvalue::ThreadLocalRef(def_id) => {
664                 trace!("skipping ThreadLocalRef({:?})", def_id);
665
666                 return None;
667             }
668
669             // There's no other checking to do at this time.
670             Rvalue::Aggregate(..)
671             | Rvalue::Use(..)
672             | Rvalue::Repeat(..)
673             | Rvalue::Len(..)
674             | Rvalue::Cast(..)
675             | Rvalue::ShallowInitBox(..)
676             | Rvalue::Discriminant(..)
677             | Rvalue::NullaryOp(..) => {}
678         }
679
680         // FIXME we need to revisit this for #67176
681         if rvalue.needs_subst() {
682             return None;
683         }
684
685         self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place))
686     }
687 }
688
689 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
690 #[derive(Clone, Copy, Debug, PartialEq)]
691 enum ConstPropMode {
692     /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
693     FullConstProp,
694     /// The `Local` can only be propagated into and from its own block.
695     OnlyInsideOwnBlock,
696     /// The `Local` can be propagated into but reads cannot be propagated.
697     OnlyPropagateInto,
698     /// The `Local` cannot be part of propagation at all. Any statement
699     /// referencing it either for reading or writing will not get propagated.
700     NoPropagation,
701 }
702
703 struct CanConstProp {
704     can_const_prop: IndexVec<Local, ConstPropMode>,
705     // False at the beginning. Once set, no more assignments are allowed to that local.
706     found_assignment: BitSet<Local>,
707     // Cache of locals' information
708     local_kinds: IndexVec<Local, LocalKind>,
709 }
710
711 impl CanConstProp {
712     /// Returns true if `local` can be propagated
713     fn check<'tcx>(
714         tcx: TyCtxt<'tcx>,
715         param_env: ParamEnv<'tcx>,
716         body: &Body<'tcx>,
717     ) -> IndexVec<Local, ConstPropMode> {
718         let mut cpv = CanConstProp {
719             can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
720             found_assignment: BitSet::new_empty(body.local_decls.len()),
721             local_kinds: IndexVec::from_fn_n(
722                 |local| body.local_kind(local),
723                 body.local_decls.len(),
724             ),
725         };
726         for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
727             let ty = body.local_decls[local].ty;
728             match tcx.layout_of(param_env.and(ty)) {
729                 Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {}
730                 // Either the layout fails to compute, then we can't use this local anyway
731                 // or the local is too large, then we don't want to.
732                 _ => {
733                     *val = ConstPropMode::NoPropagation;
734                     continue;
735                 }
736             }
737             // Cannot use args at all
738             // Cannot use locals because if x < y { y - x } else { x - y } would
739             //        lint for x != y
740             // FIXME(oli-obk): lint variables until they are used in a condition
741             // FIXME(oli-obk): lint if return value is constant
742             if cpv.local_kinds[local] == LocalKind::Arg {
743                 *val = ConstPropMode::OnlyPropagateInto;
744                 trace!(
745                     "local {:?} can't be const propagated because it's a function argument",
746                     local
747                 );
748             } else if cpv.local_kinds[local] == LocalKind::Var {
749                 *val = ConstPropMode::OnlyInsideOwnBlock;
750                 trace!(
751                     "local {:?} will only be propagated inside its block, because it's a user variable",
752                     local
753                 );
754             }
755         }
756         cpv.visit_body(&body);
757         cpv.can_const_prop
758     }
759 }
760
761 impl Visitor<'_> for CanConstProp {
762     fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
763         use rustc_middle::mir::visit::PlaceContext::*;
764         match context {
765             // Projections are fine, because `&mut foo.x` will be caught by
766             // `MutatingUseContext::Borrow` elsewhere.
767             MutatingUse(MutatingUseContext::Projection)
768             // These are just stores, where the storing is not propagatable, but there may be later
769             // mutations of the same local via `Store`
770             | MutatingUse(MutatingUseContext::Call)
771             | MutatingUse(MutatingUseContext::AsmOutput)
772             | MutatingUse(MutatingUseContext::Deinit)
773             // Actual store that can possibly even propagate a value
774             | MutatingUse(MutatingUseContext::SetDiscriminant)
775             | MutatingUse(MutatingUseContext::Store) => {
776                 if !self.found_assignment.insert(local) {
777                     match &mut self.can_const_prop[local] {
778                         // If the local can only get propagated in its own block, then we don't have
779                         // to worry about multiple assignments, as we'll nuke the const state at the
780                         // end of the block anyway, and inside the block we overwrite previous
781                         // states as applicable.
782                         ConstPropMode::OnlyInsideOwnBlock => {}
783                         ConstPropMode::NoPropagation => {}
784                         ConstPropMode::OnlyPropagateInto => {}
785                         other @ ConstPropMode::FullConstProp => {
786                             trace!(
787                                 "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
788                                 local, other,
789                             );
790                             *other = ConstPropMode::OnlyInsideOwnBlock;
791                         }
792                     }
793                 }
794             }
795             // Reading constants is allowed an arbitrary number of times
796             NonMutatingUse(NonMutatingUseContext::Copy)
797             | NonMutatingUse(NonMutatingUseContext::Move)
798             | NonMutatingUse(NonMutatingUseContext::Inspect)
799             | NonMutatingUse(NonMutatingUseContext::Projection)
800             | NonUse(_) => {}
801
802             // These could be propagated with a smarter analysis or just some careful thinking about
803             // whether they'd be fine right now.
804             MutatingUse(MutatingUseContext::Yield)
805             | MutatingUse(MutatingUseContext::Drop)
806             | MutatingUse(MutatingUseContext::Retag)
807             // These can't ever be propagated under any scheme, as we can't reason about indirect
808             // mutation.
809             | NonMutatingUse(NonMutatingUseContext::SharedBorrow)
810             | NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
811             | NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
812             | NonMutatingUse(NonMutatingUseContext::AddressOf)
813             | MutatingUse(MutatingUseContext::Borrow)
814             | MutatingUse(MutatingUseContext::AddressOf) => {
815                 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
816                 self.can_const_prop[local] = ConstPropMode::NoPropagation;
817             }
818         }
819     }
820 }
821
822 impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
823     fn visit_body(&mut self, body: &Body<'tcx>) {
824         for (bb, data) in body.basic_blocks().iter_enumerated() {
825             self.visit_basic_block_data(bb, data);
826         }
827     }
828
829     fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
830         self.super_operand(operand, location);
831     }
832
833     fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) {
834         trace!("visit_constant: {:?}", constant);
835         self.super_constant(constant, location);
836         self.eval_constant(constant, self.source_info.unwrap());
837     }
838
839     fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
840         trace!("visit_statement: {:?}", statement);
841         let source_info = statement.source_info;
842         self.source_info = Some(source_info);
843         if let StatementKind::Assign(box (place, ref rval)) = statement.kind {
844             let can_const_prop = self.ecx.machine.can_const_prop[place.local];
845             if let Some(()) = self.const_prop(rval, source_info, place) {
846                 match can_const_prop {
847                     ConstPropMode::OnlyInsideOwnBlock => {
848                         trace!(
849                             "found local restricted to its block. \
850                                 Will remove it from const-prop after block is finished. Local: {:?}",
851                             place.local
852                         );
853                     }
854                     ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
855                         trace!("can't propagate into {:?}", place);
856                         if place.local != RETURN_PLACE {
857                             Self::remove_const(&mut self.ecx, place.local);
858                         }
859                     }
860                     ConstPropMode::FullConstProp => {}
861                 }
862             } else {
863                 // Const prop failed, so erase the destination, ensuring that whatever happens
864                 // from here on, does not know about the previous value.
865                 // This is important in case we have
866                 // ```rust
867                 // let mut x = 42;
868                 // x = SOME_MUTABLE_STATIC;
869                 // // x must now be uninit
870                 // ```
871                 // FIXME: we overzealously erase the entire local, because that's easier to
872                 // implement.
873                 trace!(
874                     "propagation into {:?} failed.
875                         Nuking the entire site from orbit, it's the only way to be sure",
876                     place,
877                 );
878                 Self::remove_const(&mut self.ecx, place.local);
879             }
880         } else {
881             match statement.kind {
882                 StatementKind::SetDiscriminant { ref place, .. } => {
883                     match self.ecx.machine.can_const_prop[place.local] {
884                         ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
885                             if self.use_ecx(|this| this.ecx.statement(statement)).is_some() {
886                                 trace!("propped discriminant into {:?}", place);
887                             } else {
888                                 Self::remove_const(&mut self.ecx, place.local);
889                             }
890                         }
891                         ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
892                             Self::remove_const(&mut self.ecx, place.local);
893                         }
894                     }
895                 }
896                 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
897                     let frame = self.ecx.frame_mut();
898                     frame.locals[local].value =
899                         if let StatementKind::StorageLive(_) = statement.kind {
900                             LocalValue::Unallocated
901                         } else {
902                             LocalValue::Dead
903                         };
904                 }
905                 _ => {}
906             }
907         }
908
909         self.super_statement(statement, location);
910     }
911
912     fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
913         let source_info = terminator.source_info;
914         self.source_info = Some(source_info);
915         self.super_terminator(terminator, location);
916         match &terminator.kind {
917             TerminatorKind::Assert { expected, ref msg, ref cond, .. } => {
918                 if let Some(ref value) = self.eval_operand(&cond, source_info) {
919                     trace!("assertion on {:?} should be {:?}", value, expected);
920                     let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected));
921                     let value_const = self.ecx.read_scalar(&value).unwrap();
922                     if expected != value_const {
923                         enum DbgVal<T> {
924                             Val(T),
925                             Underscore,
926                         }
927                         impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> {
928                             fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
929                                 match self {
930                                     Self::Val(val) => val.fmt(fmt),
931                                     Self::Underscore => fmt.write_str("_"),
932                                 }
933                             }
934                         }
935                         let mut eval_to_int = |op| {
936                             // This can be `None` if the lhs wasn't const propagated and we just
937                             // triggered the assert on the value of the rhs.
938                             self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
939                                 DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
940                             })
941                         };
942                         let msg = match msg {
943                             AssertKind::DivisionByZero(op) => {
944                                 Some(AssertKind::DivisionByZero(eval_to_int(op)))
945                             }
946                             AssertKind::RemainderByZero(op) => {
947                                 Some(AssertKind::RemainderByZero(eval_to_int(op)))
948                             }
949                             AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => {
950                                 // Division overflow is *UB* in the MIR, and different than the
951                                 // other overflow checks.
952                                 Some(AssertKind::Overflow(
953                                     *bin_op,
954                                     eval_to_int(op1),
955                                     eval_to_int(op2),
956                                 ))
957                             }
958                             AssertKind::BoundsCheck { ref len, ref index } => {
959                                 let len = eval_to_int(len);
960                                 let index = eval_to_int(index);
961                                 Some(AssertKind::BoundsCheck { len, index })
962                             }
963                             // Remaining overflow errors are already covered by checks on the binary operators.
964                             AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None,
965                             // Need proper const propagator for these.
966                             _ => None,
967                         };
968                         // Poison all places this operand references so that further code
969                         // doesn't use the invalid value
970                         match cond {
971                             Operand::Move(ref place) | Operand::Copy(ref place) => {
972                                 Self::remove_const(&mut self.ecx, place.local);
973                             }
974                             Operand::Constant(_) => {}
975                         }
976                         if let Some(msg) = msg {
977                             self.report_assert_as_lint(
978                                 lint::builtin::UNCONDITIONAL_PANIC,
979                                 source_info,
980                                 "this operation will panic at runtime",
981                                 msg,
982                             );
983                         }
984                     }
985                 }
986             }
987             // None of these have Operands to const-propagate.
988             TerminatorKind::Goto { .. }
989             | TerminatorKind::Resume
990             | TerminatorKind::Abort
991             | TerminatorKind::Return
992             | TerminatorKind::Unreachable
993             | TerminatorKind::Drop { .. }
994             | TerminatorKind::DropAndReplace { .. }
995             | TerminatorKind::Yield { .. }
996             | TerminatorKind::GeneratorDrop
997             | TerminatorKind::FalseEdge { .. }
998             | TerminatorKind::FalseUnwind { .. }
999             | TerminatorKind::SwitchInt { .. }
1000             | TerminatorKind::Call { .. }
1001             | TerminatorKind::InlineAsm { .. } => {}
1002         }
1003
1004         // We remove all Locals which are restricted in propagation to their containing blocks and
1005         // which were modified in the current block.
1006         // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
1007         let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
1008         for &local in locals.iter() {
1009             Self::remove_const(&mut self.ecx, local);
1010         }
1011         locals.clear();
1012         // Put it back so we reuse the heap of the storage
1013         self.ecx.machine.written_only_inside_own_block_locals = locals;
1014         if cfg!(debug_assertions) {
1015             // Ensure we are correctly erasing locals with the non-debug-assert logic.
1016             for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
1017                 assert!(
1018                     self.get_const(local.into()).is_none()
1019                         || self
1020                             .layout_of(self.local_decls[local].ty)
1021                             .map_or(true, |layout| layout.is_zst())
1022                 )
1023             }
1024         }
1025     }
1026 }