]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_mir_transform/src/const_prop_lint.rs
New upstream version 1.63.0+dfsg1
[rustc.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, EarlyBinder, 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, Pointer,
34 Scalar, 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 _destination: &PlaceTy<'tcx>,
196 _target: Option<BasicBlock>,
197 _unwind: StackPopUnwind,
198 ) -> InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> {
199 Ok(None)
200 }
201
202 fn call_intrinsic(
203 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
204 _instance: ty::Instance<'tcx>,
205 _args: &[OpTy<'tcx>],
206 _destination: &PlaceTy<'tcx>,
207 _target: Option<BasicBlock>,
208 _unwind: StackPopUnwind,
209 ) -> InterpResult<'tcx> {
210 throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
211 }
212
213 fn assert_panic(
214 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
215 _msg: &rustc_middle::mir::AssertMessage<'tcx>,
216 _unwind: Option<rustc_middle::mir::BasicBlock>,
217 ) -> InterpResult<'tcx> {
218 bug!("panics terminators are not evaluated in ConstProp")
219 }
220
221 fn binary_ptr_op(
222 _ecx: &InterpCx<'mir, 'tcx, Self>,
223 _bin_op: BinOp,
224 _left: &ImmTy<'tcx>,
225 _right: &ImmTy<'tcx>,
226 ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
227 // We can't do this because aliasing of memory can differ between const eval and llvm
228 throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
229 }
230
231 fn access_local(
232 _ecx: &InterpCx<'mir, 'tcx, Self>,
233 frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
234 local: Local,
235 ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
236 let l = &frame.locals[local];
237
238 if l.value == LocalValue::Unallocated {
239 throw_machine_stop_str!("tried to access an uninitialized local")
240 }
241
242 l.access()
243 }
244
245 fn access_local_mut<'a>(
246 ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
247 frame: usize,
248 local: Local,
249 ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
250 {
251 if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
252 throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
253 }
254 if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
255 trace!(
256 "mutating local {:?} which is restricted to its block. \
257 Will remove it from const-prop after block is finished.",
258 local
259 );
260 ecx.machine.written_only_inside_own_block_locals.insert(local);
261 }
262 ecx.machine.stack[frame].locals[local].access_mut()
263 }
264
265 fn before_access_global(
266 _tcx: TyCtxt<'tcx>,
267 _machine: &Self,
268 _alloc_id: AllocId,
269 alloc: ConstAllocation<'tcx, Self::PointerTag, Self::AllocExtra>,
270 _static_def_id: Option<DefId>,
271 is_write: bool,
272 ) -> InterpResult<'tcx> {
273 if is_write {
274 throw_machine_stop_str!("can't write to global");
275 }
276 // If the static allocation is mutable, then we can't const prop it as its content
277 // might be different at runtime.
278 if alloc.inner().mutability == Mutability::Mut {
279 throw_machine_stop_str!("can't access mutable globals in ConstProp");
280 }
281
282 Ok(())
283 }
284
285 #[inline(always)]
286 fn expose_ptr(
287 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
288 _ptr: Pointer<AllocId>,
289 ) -> InterpResult<'tcx> {
290 throw_machine_stop_str!("exposing pointers isn't supported in ConstProp")
291 }
292
293 #[inline(always)]
294 fn init_frame_extra(
295 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
296 frame: Frame<'mir, 'tcx>,
297 ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
298 Ok(frame)
299 }
300
301 #[inline(always)]
302 fn stack<'a>(
303 ecx: &'a InterpCx<'mir, 'tcx, Self>,
304 ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
305 &ecx.machine.stack
306 }
307
308 #[inline(always)]
309 fn stack_mut<'a>(
310 ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
311 ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
312 &mut ecx.machine.stack
313 }
314 }
315
316 /// Finds optimization opportunities on the MIR.
317 struct ConstPropagator<'mir, 'tcx> {
318 ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
319 tcx: TyCtxt<'tcx>,
320 param_env: ParamEnv<'tcx>,
321 source_scopes: &'mir IndexVec<SourceScope, SourceScopeData<'tcx>>,
322 local_decls: &'mir IndexVec<Local, LocalDecl<'tcx>>,
323 // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
324 // the last known `SourceInfo` here and just keep revisiting it.
325 source_info: Option<SourceInfo>,
326 }
327
328 impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
329 type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
330
331 #[inline]
332 fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
333 err
334 }
335 }
336
337 impl HasDataLayout for ConstPropagator<'_, '_> {
338 #[inline]
339 fn data_layout(&self) -> &TargetDataLayout {
340 &self.tcx.data_layout
341 }
342 }
343
344 impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
345 #[inline]
346 fn tcx(&self) -> TyCtxt<'tcx> {
347 self.tcx
348 }
349 }
350
351 impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> {
352 #[inline]
353 fn param_env(&self) -> ty::ParamEnv<'tcx> {
354 self.param_env
355 }
356 }
357
358 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
359 fn new(
360 body: &Body<'tcx>,
361 dummy_body: &'mir Body<'tcx>,
362 tcx: TyCtxt<'tcx>,
363 ) -> ConstPropagator<'mir, 'tcx> {
364 let def_id = body.source.def_id();
365 let substs = &InternalSubsts::identity_for_item(tcx, def_id);
366 let param_env = tcx.param_env_reveal_all_normalized(def_id);
367
368 let can_const_prop = CanConstProp::check(tcx, param_env, body);
369 let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
370 for (l, mode) in can_const_prop.iter_enumerated() {
371 if *mode == ConstPropMode::OnlyInsideOwnBlock {
372 only_propagate_inside_block_locals.insert(l);
373 }
374 }
375 let mut ecx = InterpCx::new(
376 tcx,
377 tcx.def_span(def_id),
378 param_env,
379 ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
380 );
381
382 let ret_layout = ecx
383 .layout_of(EarlyBinder(body.return_ty()).subst(tcx, substs))
384 .ok()
385 // Don't bother allocating memory for large values.
386 .filter(|ret_layout| ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT))
387 .unwrap_or_else(|| ecx.layout_of(tcx.types.unit).unwrap());
388
389 let ret = ecx
390 .allocate(ret_layout, MemoryKind::Stack)
391 .expect("couldn't perform small allocation")
392 .into();
393
394 ecx.push_stack_frame(
395 Instance::new(def_id, substs),
396 dummy_body,
397 &ret,
398 StackPopCleanup::Root { cleanup: false },
399 )
400 .expect("failed to push initial stack frame");
401
402 ConstPropagator {
403 ecx,
404 tcx,
405 param_env,
406 source_scopes: &dummy_body.source_scopes,
407 local_decls: &dummy_body.local_decls,
408 source_info: None,
409 }
410 }
411
412 fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
413 let op = match self.ecx.eval_place_to_op(place, None) {
414 Ok(op) => op,
415 Err(e) => {
416 trace!("get_const failed: {}", e);
417 return None;
418 }
419 };
420
421 // Try to read the local as an immediate so that if it is representable as a scalar, we can
422 // handle it as such, but otherwise, just return the value as is.
423 Some(match self.ecx.read_immediate_raw(&op, /*force*/ false) {
424 Ok(Ok(imm)) => imm.into(),
425 _ => op,
426 })
427 }
428
429 /// Remove `local` from the pool of `Locals`. Allows writing to them,
430 /// but not reading from them anymore.
431 fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
432 ecx.frame_mut().locals[local] =
433 LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
434 }
435
436 fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
437 source_info.scope.lint_root(self.source_scopes)
438 }
439
440 fn use_ecx<F, T>(&mut self, source_info: SourceInfo, f: F) -> Option<T>
441 where
442 F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
443 {
444 // Overwrite the PC -- whatever the interpreter does to it does not make any sense anyway.
445 self.ecx.frame_mut().loc = Err(source_info.span);
446 match f(self) {
447 Ok(val) => Some(val),
448 Err(error) => {
449 trace!("InterpCx operation failed: {:?}", error);
450 // Some errors shouldn't come up because creating them causes
451 // an allocation, which we should avoid. When that happens,
452 // dedicated error variants should be introduced instead.
453 assert!(
454 !error.kind().formatted_string(),
455 "const-prop encountered formatting error: {}",
456 error
457 );
458 None
459 }
460 }
461 }
462
463 /// Returns the value, if any, of evaluating `c`.
464 fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
465 // FIXME we need to revisit this for #67176
466 if c.needs_subst() {
467 return None;
468 }
469
470 match self.ecx.mir_const_to_op(&c.literal, None) {
471 Ok(op) => Some(op),
472 Err(error) => {
473 let tcx = self.ecx.tcx.at(c.span);
474 let err = ConstEvalErr::new(&self.ecx, error, Some(c.span));
475 if let Some(lint_root) = self.lint_root(source_info) {
476 let lint_only = match c.literal {
477 ConstantKind::Ty(ct) => match ct.kind() {
478 // Promoteds must lint and not error as the user didn't ask for them
479 ConstKind::Unevaluated(ty::Unevaluated {
480 def: _,
481 substs: _,
482 promoted: Some(_),
483 }) => true,
484 // Out of backwards compatibility we cannot report hard errors in unused
485 // generic functions using associated constants of the generic parameters.
486 _ => c.literal.needs_subst(),
487 },
488 ConstantKind::Val(_, ty) => ty.needs_subst(),
489 };
490 if lint_only {
491 // Out of backwards compatibility we cannot report hard errors in unused
492 // generic functions using associated constants of the generic parameters.
493 err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span));
494 } else {
495 err.report_as_error(tcx, "erroneous constant used");
496 }
497 } else {
498 err.report_as_error(tcx, "erroneous constant used");
499 }
500 None
501 }
502 }
503 }
504
505 /// Returns the value, if any, of evaluating `place`.
506 fn eval_place(&mut self, place: Place<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
507 trace!("eval_place(place={:?})", place);
508 self.use_ecx(source_info, |this| this.ecx.eval_place_to_op(place, None))
509 }
510
511 /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
512 /// or `eval_place`, depending on the variant of `Operand` used.
513 fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
514 match *op {
515 Operand::Constant(ref c) => self.eval_constant(c, source_info),
516 Operand::Move(place) | Operand::Copy(place) => self.eval_place(place, source_info),
517 }
518 }
519
520 fn report_assert_as_lint(
521 &self,
522 lint: &'static lint::Lint,
523 source_info: SourceInfo,
524 message: &'static str,
525 panic: AssertKind<impl std::fmt::Debug>,
526 ) {
527 if let Some(lint_root) = self.lint_root(source_info) {
528 self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| {
529 let mut err = lint.build(message);
530 err.span_label(source_info.span, format!("{:?}", panic));
531 err.emit();
532 });
533 }
534 }
535
536 fn check_unary_op(
537 &mut self,
538 op: UnOp,
539 arg: &Operand<'tcx>,
540 source_info: SourceInfo,
541 ) -> Option<()> {
542 if let (val, true) = self.use_ecx(source_info, |this| {
543 let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
544 let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
545 Ok((val, overflow))
546 })? {
547 // `AssertKind` only has an `OverflowNeg` variant, so make sure that is
548 // appropriate to use.
549 assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
550 self.report_assert_as_lint(
551 lint::builtin::ARITHMETIC_OVERFLOW,
552 source_info,
553 "this arithmetic operation will overflow",
554 AssertKind::OverflowNeg(val.to_const_int()),
555 );
556 return None;
557 }
558
559 Some(())
560 }
561
562 fn check_binary_op(
563 &mut self,
564 op: BinOp,
565 left: &Operand<'tcx>,
566 right: &Operand<'tcx>,
567 source_info: SourceInfo,
568 ) -> Option<()> {
569 let r = self.use_ecx(source_info, |this| {
570 this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?)
571 });
572 let l = self.use_ecx(source_info, |this| {
573 this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?)
574 });
575 // Check for exceeding shifts *even if* we cannot evaluate the LHS.
576 if op == BinOp::Shr || op == BinOp::Shl {
577 let r = r?;
578 // We need the type of the LHS. We cannot use `place_layout` as that is the type
579 // of the result, which for checked binops is not the same!
580 let left_ty = left.ty(self.local_decls, self.tcx);
581 let left_size = self.ecx.layout_of(left_ty).ok()?.size;
582 let right_size = r.layout.size;
583 let r_bits = r.to_scalar().ok();
584 let r_bits = r_bits.and_then(|r| r.to_bits(right_size).ok());
585 if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
586 debug!("check_binary_op: reporting assert for {:?}", source_info);
587 self.report_assert_as_lint(
588 lint::builtin::ARITHMETIC_OVERFLOW,
589 source_info,
590 "this arithmetic operation will overflow",
591 AssertKind::Overflow(
592 op,
593 match l {
594 Some(l) => l.to_const_int(),
595 // Invent a dummy value, the diagnostic ignores it anyway
596 None => ConstInt::new(
597 ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
598 left_ty.is_signed(),
599 left_ty.is_ptr_sized_integral(),
600 ),
601 },
602 r.to_const_int(),
603 ),
604 );
605 return None;
606 }
607 }
608
609 if let (Some(l), Some(r)) = (&l, &r) {
610 // The remaining operators are handled through `overflowing_binary_op`.
611 if self.use_ecx(source_info, |this| {
612 let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
613 Ok(overflow)
614 })? {
615 self.report_assert_as_lint(
616 lint::builtin::ARITHMETIC_OVERFLOW,
617 source_info,
618 "this arithmetic operation will overflow",
619 AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
620 );
621 return None;
622 }
623 }
624 Some(())
625 }
626
627 fn const_prop(
628 &mut self,
629 rvalue: &Rvalue<'tcx>,
630 source_info: SourceInfo,
631 place: Place<'tcx>,
632 ) -> Option<()> {
633 // Perform any special handling for specific Rvalue types.
634 // Generally, checks here fall into one of two categories:
635 // 1. Additional checking to provide useful lints to the user
636 // - In this case, we will do some validation and then fall through to the
637 // end of the function which evals the assignment.
638 // 2. Working around bugs in other parts of the compiler
639 // - In this case, we'll return `None` from this function to stop evaluation.
640 match rvalue {
641 // Additional checking: give lints to the user if an overflow would occur.
642 // We do this here and not in the `Assert` terminator as that terminator is
643 // only sometimes emitted (overflow checks can be disabled), but we want to always
644 // lint.
645 Rvalue::UnaryOp(op, arg) => {
646 trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
647 self.check_unary_op(*op, arg, source_info)?;
648 }
649 Rvalue::BinaryOp(op, box (left, right)) => {
650 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
651 self.check_binary_op(*op, left, right, source_info)?;
652 }
653 Rvalue::CheckedBinaryOp(op, box (left, right)) => {
654 trace!(
655 "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
656 op,
657 left,
658 right
659 );
660 self.check_binary_op(*op, left, right, source_info)?;
661 }
662
663 // Do not try creating references (#67862)
664 Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
665 trace!("skipping AddressOf | Ref for {:?}", place);
666
667 // This may be creating mutable references or immutable references to cells.
668 // If that happens, the pointed to value could be mutated via that reference.
669 // Since we aren't tracking references, the const propagator loses track of what
670 // value the local has right now.
671 // Thus, all locals that have their reference taken
672 // must not take part in propagation.
673 Self::remove_const(&mut self.ecx, place.local);
674
675 return None;
676 }
677 Rvalue::ThreadLocalRef(def_id) => {
678 trace!("skipping ThreadLocalRef({:?})", def_id);
679
680 return None;
681 }
682
683 // There's no other checking to do at this time.
684 Rvalue::Aggregate(..)
685 | Rvalue::Use(..)
686 | Rvalue::Repeat(..)
687 | Rvalue::Len(..)
688 | Rvalue::Cast(..)
689 | Rvalue::ShallowInitBox(..)
690 | Rvalue::Discriminant(..)
691 | Rvalue::NullaryOp(..) => {}
692 }
693
694 // FIXME we need to revisit this for #67176
695 if rvalue.needs_subst() {
696 return None;
697 }
698
699 self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place))
700 }
701 }
702
703 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
704 #[derive(Clone, Copy, Debug, PartialEq)]
705 enum ConstPropMode {
706 /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
707 FullConstProp,
708 /// The `Local` can only be propagated into and from its own block.
709 OnlyInsideOwnBlock,
710 /// The `Local` can be propagated into but reads cannot be propagated.
711 OnlyPropagateInto,
712 /// The `Local` cannot be part of propagation at all. Any statement
713 /// referencing it either for reading or writing will not get propagated.
714 NoPropagation,
715 }
716
717 struct CanConstProp {
718 can_const_prop: IndexVec<Local, ConstPropMode>,
719 // False at the beginning. Once set, no more assignments are allowed to that local.
720 found_assignment: BitSet<Local>,
721 // Cache of locals' information
722 local_kinds: IndexVec<Local, LocalKind>,
723 }
724
725 impl CanConstProp {
726 /// Returns true if `local` can be propagated
727 fn check<'tcx>(
728 tcx: TyCtxt<'tcx>,
729 param_env: ParamEnv<'tcx>,
730 body: &Body<'tcx>,
731 ) -> IndexVec<Local, ConstPropMode> {
732 let mut cpv = CanConstProp {
733 can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
734 found_assignment: BitSet::new_empty(body.local_decls.len()),
735 local_kinds: IndexVec::from_fn_n(
736 |local| body.local_kind(local),
737 body.local_decls.len(),
738 ),
739 };
740 for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
741 let ty = body.local_decls[local].ty;
742 match tcx.layout_of(param_env.and(ty)) {
743 Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {}
744 // Either the layout fails to compute, then we can't use this local anyway
745 // or the local is too large, then we don't want to.
746 _ => {
747 *val = ConstPropMode::NoPropagation;
748 continue;
749 }
750 }
751 // Cannot use args at all
752 // Cannot use locals because if x < y { y - x } else { x - y } would
753 // lint for x != y
754 // FIXME(oli-obk): lint variables until they are used in a condition
755 // FIXME(oli-obk): lint if return value is constant
756 if cpv.local_kinds[local] == LocalKind::Arg {
757 *val = ConstPropMode::OnlyPropagateInto;
758 trace!(
759 "local {:?} can't be const propagated because it's a function argument",
760 local
761 );
762 } else if cpv.local_kinds[local] == LocalKind::Var {
763 *val = ConstPropMode::OnlyInsideOwnBlock;
764 trace!(
765 "local {:?} will only be propagated inside its block, because it's a user variable",
766 local
767 );
768 }
769 }
770 cpv.visit_body(&body);
771 cpv.can_const_prop
772 }
773 }
774
775 impl Visitor<'_> for CanConstProp {
776 fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
777 use rustc_middle::mir::visit::PlaceContext::*;
778 match context {
779 // Projections are fine, because `&mut foo.x` will be caught by
780 // `MutatingUseContext::Borrow` elsewhere.
781 MutatingUse(MutatingUseContext::Projection)
782 // These are just stores, where the storing is not propagatable, but there may be later
783 // mutations of the same local via `Store`
784 | MutatingUse(MutatingUseContext::Call)
785 | MutatingUse(MutatingUseContext::AsmOutput)
786 | MutatingUse(MutatingUseContext::Deinit)
787 // Actual store that can possibly even propagate a value
788 | MutatingUse(MutatingUseContext::SetDiscriminant)
789 | MutatingUse(MutatingUseContext::Store) => {
790 if !self.found_assignment.insert(local) {
791 match &mut self.can_const_prop[local] {
792 // If the local can only get propagated in its own block, then we don't have
793 // to worry about multiple assignments, as we'll nuke the const state at the
794 // end of the block anyway, and inside the block we overwrite previous
795 // states as applicable.
796 ConstPropMode::OnlyInsideOwnBlock => {}
797 ConstPropMode::NoPropagation => {}
798 ConstPropMode::OnlyPropagateInto => {}
799 other @ ConstPropMode::FullConstProp => {
800 trace!(
801 "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
802 local, other,
803 );
804 *other = ConstPropMode::OnlyInsideOwnBlock;
805 }
806 }
807 }
808 }
809 // Reading constants is allowed an arbitrary number of times
810 NonMutatingUse(NonMutatingUseContext::Copy)
811 | NonMutatingUse(NonMutatingUseContext::Move)
812 | NonMutatingUse(NonMutatingUseContext::Inspect)
813 | NonMutatingUse(NonMutatingUseContext::Projection)
814 | NonUse(_) => {}
815
816 // These could be propagated with a smarter analysis or just some careful thinking about
817 // whether they'd be fine right now.
818 MutatingUse(MutatingUseContext::Yield)
819 | MutatingUse(MutatingUseContext::Drop)
820 | MutatingUse(MutatingUseContext::Retag)
821 // These can't ever be propagated under any scheme, as we can't reason about indirect
822 // mutation.
823 | NonMutatingUse(NonMutatingUseContext::SharedBorrow)
824 | NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
825 | NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
826 | NonMutatingUse(NonMutatingUseContext::AddressOf)
827 | MutatingUse(MutatingUseContext::Borrow)
828 | MutatingUse(MutatingUseContext::AddressOf) => {
829 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
830 self.can_const_prop[local] = ConstPropMode::NoPropagation;
831 }
832 }
833 }
834 }
835
836 impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
837 fn visit_body(&mut self, body: &Body<'tcx>) {
838 for (bb, data) in body.basic_blocks().iter_enumerated() {
839 self.visit_basic_block_data(bb, data);
840 }
841 }
842
843 fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
844 self.super_operand(operand, location);
845 }
846
847 fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) {
848 trace!("visit_constant: {:?}", constant);
849 self.super_constant(constant, location);
850 self.eval_constant(constant, self.source_info.unwrap());
851 }
852
853 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
854 trace!("visit_statement: {:?}", statement);
855 let source_info = statement.source_info;
856 self.source_info = Some(source_info);
857 if let StatementKind::Assign(box (place, ref rval)) = statement.kind {
858 let can_const_prop = self.ecx.machine.can_const_prop[place.local];
859 if let Some(()) = self.const_prop(rval, source_info, place) {
860 match can_const_prop {
861 ConstPropMode::OnlyInsideOwnBlock => {
862 trace!(
863 "found local restricted to its block. \
864 Will remove it from const-prop after block is finished. Local: {:?}",
865 place.local
866 );
867 }
868 ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
869 trace!("can't propagate into {:?}", place);
870 if place.local != RETURN_PLACE {
871 Self::remove_const(&mut self.ecx, place.local);
872 }
873 }
874 ConstPropMode::FullConstProp => {}
875 }
876 } else {
877 // Const prop failed, so erase the destination, ensuring that whatever happens
878 // from here on, does not know about the previous value.
879 // This is important in case we have
880 // ```rust
881 // let mut x = 42;
882 // x = SOME_MUTABLE_STATIC;
883 // // x must now be uninit
884 // ```
885 // FIXME: we overzealously erase the entire local, because that's easier to
886 // implement.
887 trace!(
888 "propagation into {:?} failed.
889 Nuking the entire site from orbit, it's the only way to be sure",
890 place,
891 );
892 Self::remove_const(&mut self.ecx, place.local);
893 }
894 } else {
895 match statement.kind {
896 StatementKind::SetDiscriminant { ref place, .. } => {
897 match self.ecx.machine.can_const_prop[place.local] {
898 ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
899 if self
900 .use_ecx(source_info, |this| this.ecx.statement(statement))
901 .is_some()
902 {
903 trace!("propped discriminant into {:?}", place);
904 } else {
905 Self::remove_const(&mut self.ecx, place.local);
906 }
907 }
908 ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
909 Self::remove_const(&mut self.ecx, place.local);
910 }
911 }
912 }
913 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
914 let frame = self.ecx.frame_mut();
915 frame.locals[local].value =
916 if let StatementKind::StorageLive(_) = statement.kind {
917 LocalValue::Unallocated
918 } else {
919 LocalValue::Dead
920 };
921 }
922 _ => {}
923 }
924 }
925
926 self.super_statement(statement, location);
927 }
928
929 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
930 let source_info = terminator.source_info;
931 self.source_info = Some(source_info);
932 self.super_terminator(terminator, location);
933 match &terminator.kind {
934 TerminatorKind::Assert { expected, ref msg, ref cond, .. } => {
935 if let Some(ref value) = self.eval_operand(&cond, source_info) {
936 trace!("assertion on {:?} should be {:?}", value, expected);
937 let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected));
938 let value_const = self.ecx.read_scalar(&value).unwrap();
939 if expected != value_const {
940 enum DbgVal<T> {
941 Val(T),
942 Underscore,
943 }
944 impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> {
945 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
946 match self {
947 Self::Val(val) => val.fmt(fmt),
948 Self::Underscore => fmt.write_str("_"),
949 }
950 }
951 }
952 let mut eval_to_int = |op| {
953 // This can be `None` if the lhs wasn't const propagated and we just
954 // triggered the assert on the value of the rhs.
955 self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
956 DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
957 })
958 };
959 let msg = match msg {
960 AssertKind::DivisionByZero(op) => {
961 Some(AssertKind::DivisionByZero(eval_to_int(op)))
962 }
963 AssertKind::RemainderByZero(op) => {
964 Some(AssertKind::RemainderByZero(eval_to_int(op)))
965 }
966 AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => {
967 // Division overflow is *UB* in the MIR, and different than the
968 // other overflow checks.
969 Some(AssertKind::Overflow(
970 *bin_op,
971 eval_to_int(op1),
972 eval_to_int(op2),
973 ))
974 }
975 AssertKind::BoundsCheck { ref len, ref index } => {
976 let len = eval_to_int(len);
977 let index = eval_to_int(index);
978 Some(AssertKind::BoundsCheck { len, index })
979 }
980 // Remaining overflow errors are already covered by checks on the binary operators.
981 AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None,
982 // Need proper const propagator for these.
983 _ => None,
984 };
985 // Poison all places this operand references so that further code
986 // doesn't use the invalid value
987 match cond {
988 Operand::Move(ref place) | Operand::Copy(ref place) => {
989 Self::remove_const(&mut self.ecx, place.local);
990 }
991 Operand::Constant(_) => {}
992 }
993 if let Some(msg) = msg {
994 self.report_assert_as_lint(
995 lint::builtin::UNCONDITIONAL_PANIC,
996 source_info,
997 "this operation will panic at runtime",
998 msg,
999 );
1000 }
1001 }
1002 }
1003 }
1004 // None of these have Operands to const-propagate.
1005 TerminatorKind::Goto { .. }
1006 | TerminatorKind::Resume
1007 | TerminatorKind::Abort
1008 | TerminatorKind::Return
1009 | TerminatorKind::Unreachable
1010 | TerminatorKind::Drop { .. }
1011 | TerminatorKind::DropAndReplace { .. }
1012 | TerminatorKind::Yield { .. }
1013 | TerminatorKind::GeneratorDrop
1014 | TerminatorKind::FalseEdge { .. }
1015 | TerminatorKind::FalseUnwind { .. }
1016 | TerminatorKind::SwitchInt { .. }
1017 | TerminatorKind::Call { .. }
1018 | TerminatorKind::InlineAsm { .. } => {}
1019 }
1020
1021 // We remove all Locals which are restricted in propagation to their containing blocks and
1022 // which were modified in the current block.
1023 // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
1024 let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
1025 for &local in locals.iter() {
1026 Self::remove_const(&mut self.ecx, local);
1027 }
1028 locals.clear();
1029 // Put it back so we reuse the heap of the storage
1030 self.ecx.machine.written_only_inside_own_block_locals = locals;
1031 if cfg!(debug_assertions) {
1032 // Ensure we are correctly erasing locals with the non-debug-assert logic.
1033 for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
1034 assert!(
1035 self.get_const(local.into()).is_none()
1036 || self
1037 .layout_of(self.local_decls[local].ty)
1038 .map_or(true, |layout| layout.is_zst())
1039 )
1040 }
1041 }
1042 }
1043 }