1use std::fmt::Debug;
6
7use rustc_abi::{BackendRepr, FieldIdx, HasDataLayout, Size, TargetDataLayout, VariantIdx};
8use rustc_const_eval::const_eval::DummyMachine;
9use rustc_const_eval::interpret::{
10 ImmTy, InterpCx, InterpResult, Projectable, Scalar, format_interp_error, interp_ok,
11};
12use rustc_data_structures::fx::FxHashSet;
13use rustc_hir::def::DefKind;
14use rustc_hir::{HirId, find_attr};
15use rustc_index::IndexVec;
16use rustc_index::bit_set::DenseBitSet;
17use rustc_middle::bug;
18use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
19use rustc_middle::mir::*;
20use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
21use rustc_middle::ty::{
22 self, ConstInt, GenericArgKind, GenericParamDefKind, ScalarInt, Ty, TyCtxt, TypeVisitableExt,
23 Unnormalized,
24};
25use rustc_session::lint::builtin::UNCONDITIONAL_PANIC;
26use rustc_span::Span;
27use tracing::{debug, instrument, trace};
28
29use crate::diagnostics::{AssertLint, AssertLintKind, ConstNIsZero};
30
31pub(super) struct KnownPanicsLint;
32
33impl<'tcx> crate::MirLint<'tcx> for KnownPanicsLint {
34 fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
35 if body.tainted_by_errors.is_some() {
36 return;
37 }
38
39 let def_id = body.source.def_id().expect_local();
40 let def_kind = tcx.def_kind(def_id);
41 let is_fn_like = def_kind.is_fn_like();
42 let is_assoc_const = matches!(def_kind, DefKind::AssocConst { .. });
43
44 if !is_fn_like && !is_assoc_const {
46 trace!("KnownPanicsLint skipped for {:?}", def_id);
48 return;
49 }
50
51 if tcx.is_coroutine(def_id.to_def_id()) {
54 trace!("KnownPanicsLint skipped for coroutine {:?}", def_id);
55 return;
56 }
57
58 trace!("KnownPanicsLint starting for {:?}", def_id);
59
60 let mut linter = ConstPropagator::new(body, tcx);
61 linter.visit_body(body);
62
63 trace!("KnownPanicsLint done for {:?}", def_id);
64 }
65}
66
67struct ConstPropagator<'mir, 'tcx> {
70 ecx: InterpCx<'tcx, DummyMachine>,
71 tcx: TyCtxt<'tcx>,
72 typing_env: ty::TypingEnv<'tcx>,
73 worklist: Vec<BasicBlock>,
74 visited_blocks: DenseBitSet<BasicBlock>,
75 locals: IndexVec<Local, Value<'tcx>>,
76 body: &'mir Body<'tcx>,
77 written_only_inside_own_block_locals: FxHashSet<Local>,
78 can_const_prop: IndexVec<Local, ConstPropMode>,
79}
80
81#[derive(Debug, Clone)]
82enum Value<'tcx> {
83 Immediate(ImmTy<'tcx>),
84 Aggregate { variant: VariantIdx, fields: IndexVec<FieldIdx, Value<'tcx>> },
85 Uninit,
86}
87
88impl<'tcx> From<ImmTy<'tcx>> for Value<'tcx> {
89 fn from(v: ImmTy<'tcx>) -> Self {
90 Self::Immediate(v)
91 }
92}
93
94impl<'tcx> Value<'tcx> {
95 fn project(
96 &self,
97 proj: &[PlaceElem<'tcx>],
98 prop: &ConstPropagator<'_, 'tcx>,
99 ) -> Option<&Value<'tcx>> {
100 let mut this = self;
101 for proj in proj {
102 this = match (*proj, this) {
103 (PlaceElem::Field(idx, _), Value::Aggregate { fields, .. }) => {
104 fields.get(idx).unwrap_or(&Value::Uninit)
105 }
106 (PlaceElem::Index(idx), Value::Aggregate { fields, .. }) => {
107 let idx = prop.get_const(idx.into())?.immediate()?;
108 let idx = prop.ecx.read_target_usize(idx).discard_err()?.try_into().ok()?;
109 if idx <= FieldIdx::MAX_AS_U32 {
110 fields.get(FieldIdx::from_u32(idx)).unwrap_or(&Value::Uninit)
111 } else {
112 return None;
113 }
114 }
115 (
116 PlaceElem::ConstantIndex { offset, min_length: _, from_end: false },
117 Value::Aggregate { fields, .. },
118 ) => fields
119 .get(FieldIdx::from_u32(offset.try_into().ok()?))
120 .unwrap_or(&Value::Uninit),
121 _ => return None,
122 };
123 }
124 Some(this)
125 }
126
127 fn project_mut(&mut self, proj: &[PlaceElem<'_>]) -> Option<&mut Value<'tcx>> {
128 let mut this = self;
129 for proj in proj {
130 this = match (proj, this) {
131 (PlaceElem::Field(idx, _), Value::Aggregate { fields, .. }) => {
132 fields.ensure_contains_elem(*idx, || Value::Uninit)
133 }
134 (PlaceElem::Field(..), val @ Value::Uninit) => {
135 *val =
136 Value::Aggregate { variant: VariantIdx::ZERO, fields: Default::default() };
137 val.project_mut(&[*proj])?
138 }
139 _ => return None,
140 };
141 }
142 Some(this)
143 }
144
145 fn immediate(&self) -> Option<&ImmTy<'tcx>> {
146 match self {
147 Value::Immediate(op) => Some(op),
148 _ => None,
149 }
150 }
151}
152
153impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
154 type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
155
156 #[inline]
157 fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
158 err
159 }
160}
161
162impl HasDataLayout for ConstPropagator<'_, '_> {
163 #[inline]
164 fn data_layout(&self) -> &TargetDataLayout {
165 &self.tcx.data_layout
166 }
167}
168
169impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
170 #[inline]
171 fn tcx(&self) -> TyCtxt<'tcx> {
172 self.tcx
173 }
174}
175
176impl<'tcx> ty::layout::HasTypingEnv<'tcx> for ConstPropagator<'_, 'tcx> {
177 #[inline]
178 fn typing_env(&self) -> ty::TypingEnv<'tcx> {
179 self.typing_env
180 }
181}
182
183impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
184 fn new(body: &'mir Body<'tcx>, tcx: TyCtxt<'tcx>) -> ConstPropagator<'mir, 'tcx> {
185 let def_id = body.source.def_id();
186 let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
189 let can_const_prop = CanConstProp::check(tcx, typing_env, body);
190 let ecx = InterpCx::new(tcx, tcx.def_span(def_id), typing_env, DummyMachine);
191
192 ConstPropagator {
193 ecx,
194 tcx,
195 typing_env,
196 worklist: vec![START_BLOCK],
197 visited_blocks: DenseBitSet::new_empty(body.basic_blocks.len()),
198 locals: IndexVec::from_elem_n(Value::Uninit, body.local_decls.len()),
199 body,
200 can_const_prop,
201 written_only_inside_own_block_locals: Default::default(),
202 }
203 }
204
205 fn local_decls(&self) -> &'mir LocalDecls<'tcx> {
206 &self.body.local_decls
207 }
208
209 fn get_const(&self, place: Place<'tcx>) -> Option<&Value<'tcx>> {
210 self.locals[place.local].project(&place.projection, self)
211 }
212
213 fn remove_const(&mut self, local: Local) {
216 self.locals[local] = Value::Uninit;
217 self.written_only_inside_own_block_locals.remove(&local);
218 }
219
220 fn access_mut(&mut self, place: &Place<'_>) -> Option<&mut Value<'tcx>> {
221 match self.can_const_prop[place.local] {
222 ConstPropMode::NoPropagation => return None,
223 ConstPropMode::OnlyInsideOwnBlock => {
224 self.written_only_inside_own_block_locals.insert(place.local);
225 }
226 ConstPropMode::FullConstProp => {}
227 }
228 self.locals[place.local].project_mut(place.projection)
229 }
230
231 fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
232 source_info.scope.lint_root(&self.body.source_scopes)
233 }
234
235 fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
236 where
237 F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
238 {
239 f(self)
240 .map_err_info(|err| {
241 trace!("InterpCx operation failed: {:?}", err);
242 assert!(
246 !err.kind().formatted_string(),
247 "known panics lint encountered formatting error: {}",
248 format_interp_error(err),
249 );
250 err
251 })
252 .discard_err()
253 }
254
255 fn eval_constant(&mut self, c: &ConstOperand<'tcx>) -> Option<ImmTy<'tcx>> {
257 if c.has_param() {
259 return None;
260 }
261
262 let val = self
269 .tcx
270 .try_normalize_erasing_regions(self.typing_env, Unnormalized::new_wip(c.const_))
271 .ok()?;
272
273 self.use_ecx(|this| this.ecx.eval_mir_constant(&val, c.span, None))?
274 .as_mplace_or_imm()
275 .right()
276 }
277
278 #[instrument(level = "trace", skip(self), ret)]
280 fn eval_place(&mut self, place: Place<'tcx>) -> Option<ImmTy<'tcx>> {
281 match self.get_const(place)? {
282 Value::Immediate(imm) => Some(imm.clone()),
283 Value::Aggregate { .. } => None,
284 Value::Uninit => None,
285 }
286 }
287
288 fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option<ImmTy<'tcx>> {
291 match *op {
292 Operand::RuntimeChecks(_) => None,
293 Operand::Constant(ref c) => self.eval_constant(c),
294 Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
295 }
296 }
297
298 fn report_assert_as_lint(
299 &self,
300 location: Location,
301 lint_kind: AssertLintKind,
302 assert_kind: AssertKind<impl Debug>,
303 ) {
304 let source_info = self.body.source_info(location);
305 if let Some(lint_root) = self.lint_root(*source_info) {
306 let span = source_info.span;
307 self.tcx.emit_node_span_lint(
308 lint_kind.lint(),
309 lint_root,
310 span,
311 AssertLint { span, assert_kind, lint_kind },
312 );
313 }
314 }
315
316 fn check_unary_op(&mut self, op: UnOp, arg: &Operand<'tcx>, location: Location) -> Option<()> {
317 let arg = self.eval_operand(arg)?;
318 if op == UnOp::Neg && arg.layout.ty.is_integral() {
320 let (arg, overflow) = self.use_ecx(|this| {
322 let arg = this.ecx.read_immediate(&arg)?;
323 let (_res, overflow) = this
324 .ecx
325 .binary_op(BinOp::SubWithOverflow, &ImmTy::from_int(0, arg.layout), &arg)?
326 .to_scalar_pair();
327 interp_ok((arg, overflow.to_bool()?))
328 })?;
329 if overflow {
330 self.report_assert_as_lint(
331 location,
332 AssertLintKind::ArithmeticOverflow,
333 AssertKind::OverflowNeg(arg.to_const_int()),
334 );
335 return None;
336 }
337 }
338
339 Some(())
340 }
341
342 fn check_binary_op(
343 &mut self,
344 op: BinOp,
345 left: &Operand<'tcx>,
346 right: &Operand<'tcx>,
347 location: Location,
348 ) -> Option<()> {
349 let r =
350 self.eval_operand(right).and_then(|r| self.use_ecx(|this| this.ecx.read_immediate(&r)));
351 let l =
352 self.eval_operand(left).and_then(|l| self.use_ecx(|this| this.ecx.read_immediate(&l)));
353 if matches!(op, BinOp::Shr | BinOp::Shl) {
355 let r = r.clone()?;
356 let left_ty = left.ty(self.local_decls(), self.tcx);
359 let left_size = self.ecx.layout_of(left_ty).ok()?.size;
360 let right_size = r.layout.size;
361 let r_bits = r.to_scalar().to_bits(right_size).discard_err();
362 if r_bits.is_some_and(|b| b >= left_size.bits() as u128) {
363 debug!("check_binary_op: reporting assert for {:?}", location);
364 let panic = AssertKind::Overflow(
365 op,
366 ConstInt::new(
368 ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
369 left_ty.is_signed(),
370 left_ty.is_ptr_sized_integral(),
371 ),
372 r.to_const_int(),
373 );
374 self.report_assert_as_lint(location, AssertLintKind::ArithmeticOverflow, panic);
375 return None;
376 }
377 }
378
379 let op = op.wrapping_to_overflowing().unwrap_or(op);
385 if let (Some(l), Some(r)) = (l, r)
387 && l.layout.ty.is_integral()
388 && op.is_overflowing()
389 && self.use_ecx(|this| {
390 let (_res, overflow) = this.ecx.binary_op(op, &l, &r)?.to_scalar_pair();
391 overflow.to_bool()
392 })?
393 {
394 self.report_assert_as_lint(
395 location,
396 AssertLintKind::ArithmeticOverflow,
397 AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
398 );
399 return None;
400 }
401
402 Some(())
403 }
404
405 fn check_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) -> Option<()> {
406 match rvalue {
414 Rvalue::UnaryOp(op, arg) => {
419 trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
420 self.check_unary_op(*op, arg, location)?;
421 }
422 Rvalue::BinaryOp(op, (left, right)) => {
423 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
424 self.check_binary_op(*op, left, right, location)?;
425 }
426
427 Rvalue::RawPtr(_, place) | Rvalue::Ref(_, _, place) | Rvalue::Reborrow(_, _, place) => {
429 trace!("skipping RawPtr | Ref | Reborrow for {:?}", place);
430
431 self.remove_const(place.local);
438
439 return None;
440 }
441 Rvalue::ThreadLocalRef(def_id) => {
442 trace!("skipping ThreadLocalRef({:?})", def_id);
443
444 return None;
445 }
446
447 Rvalue::Aggregate(..)
449 | Rvalue::Use(..)
450 | Rvalue::CopyForDeref(..)
451 | Rvalue::Repeat(..)
452 | Rvalue::Cast(..)
453 | Rvalue::Discriminant(..)
454 | Rvalue::WrapUnsafeBinder(..) => {}
455 }
456
457 if rvalue.has_param() {
459 return None;
460 }
461 if !rvalue.ty(self.local_decls(), self.tcx).is_sized(self.tcx, self.typing_env) {
462 return None;
465 }
466
467 Some(())
468 }
469
470 fn check_assertion(
471 &mut self,
472 expected: bool,
473 msg: &AssertKind<Operand<'tcx>>,
474 cond: &Operand<'tcx>,
475 location: Location,
476 ) {
477 let Some(value) = &self.eval_operand(cond) else { return };
478 trace!("assertion on {:?} should be {:?}", value, expected);
479
480 let expected = Scalar::from_bool(expected);
481 let Some(value_const) = self.use_ecx(|this| this.ecx.read_scalar(value)) else { return };
482
483 if expected != value_const {
484 if let Some(place) = cond.place() {
487 self.remove_const(place.local);
488 }
489
490 enum DbgVal<T> {
491 Val(T),
492 Underscore,
493 }
494 impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> {
495 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 Self::Val(val) => val.fmt(fmt),
498 Self::Underscore => fmt.write_str("_"),
499 }
500 }
501 }
502 let mut eval_to_int = |op| {
503 self.eval_operand(op)
506 .and_then(|op| self.ecx.read_immediate(&op).discard_err())
507 .map_or(DbgVal::Underscore, |op| DbgVal::Val(op.to_const_int()))
508 };
509 let msg = match msg {
510 AssertKind::DivisionByZero(op) => AssertKind::DivisionByZero(eval_to_int(op)),
511 AssertKind::RemainderByZero(op) => AssertKind::RemainderByZero(eval_to_int(op)),
512 AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => {
513 AssertKind::Overflow(*bin_op, eval_to_int(op1), eval_to_int(op2))
516 }
517 AssertKind::BoundsCheck { len, index } => {
518 let len = eval_to_int(len);
519 let index = eval_to_int(index);
520 AssertKind::BoundsCheck { len, index }
521 }
522 AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => return,
524 _ => return,
526 };
527 self.report_assert_as_lint(location, AssertLintKind::UnconditionalPanic, msg);
528 }
529 }
530
531 fn ensure_not_propagated(&self, local: Local) {
532 if cfg!(debug_assertions) {
533 let val = self.get_const(local.into());
534 assert!(
535 matches!(val, Some(Value::Uninit))
536 || self
537 .layout_of(self.local_decls()[local].ty)
538 .map_or(true, |layout| layout.is_zst()),
539 "failed to remove values for `{local:?}`, value={val:?}",
540 )
541 }
542 }
543
544 #[instrument(level = "trace", skip(self), ret)]
545 fn eval_rvalue(&mut self, rvalue: &Rvalue<'tcx>, dest: &Place<'tcx>) -> Option<()> {
546 if !dest.projection.is_empty() {
547 return None;
548 }
549 use rustc_middle::mir::Rvalue::*;
550 let layout = self.ecx.layout_of(dest.ty(self.body, self.tcx).ty).ok()?;
551 trace!(?layout);
552
553 let val: Value<'_> = match *rvalue {
554 ThreadLocalRef(_) => return None,
555
556 Use(ref operand, _) | WrapUnsafeBinder(ref operand, _) => {
557 self.eval_operand(operand)?.into()
558 }
559
560 CopyForDeref(place) | Reborrow(_, _, place) => self.eval_place(place)?.into(),
561
562 BinaryOp(bin_op, (ref left, ref right)) => {
563 let left = self.eval_operand(left)?;
564 let left = self.use_ecx(|this| this.ecx.read_immediate(&left))?;
565
566 let right = self.eval_operand(right)?;
567 let right = self.use_ecx(|this| this.ecx.read_immediate(&right))?;
568
569 let val = self.use_ecx(|this| this.ecx.binary_op(bin_op, &left, &right))?;
570 if matches!(val.layout.backend_repr, BackendRepr::ScalarPair { .. }) {
571 let (val, overflow) = val.to_pair(&self.ecx);
574 Value::Aggregate {
575 variant: VariantIdx::ZERO,
576 fields: [val.into(), overflow.into()].into_iter().collect(),
577 }
578 } else {
579 val.into()
580 }
581 }
582
583 UnaryOp(un_op, ref operand) => {
584 let operand = self.eval_operand(operand)?;
585 let val = self.use_ecx(|this| this.ecx.read_immediate(&operand))?;
586
587 let val = self.use_ecx(|this| this.ecx.unary_op(un_op, &val))?;
588 val.into()
589 }
590
591 Aggregate(ref kind, ref fields) => Value::Aggregate {
592 fields: fields
593 .iter()
594 .map(|field| self.eval_operand(field).map_or(Value::Uninit, Value::Immediate))
595 .collect(),
596 variant: match **kind {
597 AggregateKind::Adt(_, variant, _, _, _) => variant,
598 AggregateKind::Array(_)
599 | AggregateKind::Tuple
600 | AggregateKind::RawPtr(_, _)
601 | AggregateKind::Closure(_, _)
602 | AggregateKind::Coroutine(_, _)
603 | AggregateKind::CoroutineClosure(_, _) => VariantIdx::ZERO,
604 },
605 },
606
607 Repeat(ref op, n) => {
608 trace!(?op, ?n);
609 return None;
610 }
611
612 Ref(..) | RawPtr(..) => return None,
613
614 Cast(ref kind, ref value, to) => match kind {
615 CastKind::IntToInt | CastKind::IntToFloat => {
616 let value = self.eval_operand(value)?;
617 let value = self.ecx.read_immediate(&value).discard_err()?;
618 let to = self.ecx.layout_of(to).ok()?;
619 let res = self.ecx.int_to_int_or_float(&value, to).discard_err()?;
620 res.into()
621 }
622 CastKind::FloatToFloat | CastKind::FloatToInt => {
623 let value = self.eval_operand(value)?;
624 let value = self.ecx.read_immediate(&value).discard_err()?;
625 let to = self.ecx.layout_of(to).ok()?;
626 let res = self.ecx.float_to_float_or_int(&value, to).discard_err()?;
627 res.into()
628 }
629 CastKind::Transmute | CastKind::Subtype => {
630 let value = self.eval_operand(value)?;
631 let to = self.ecx.layout_of(to).ok()?;
632 match (value.layout.backend_repr, to.backend_repr) {
635 (BackendRepr::Scalar(..), BackendRepr::Scalar(..)) => {}
636 (BackendRepr::ScalarPair { .. }, BackendRepr::ScalarPair { .. }) => {}
637 _ => return None,
638 }
639
640 value.offset(Size::ZERO, to, &self.ecx).discard_err()?.into()
641 }
642 _ => return None,
643 },
644
645 Discriminant(place) => {
646 let variant = match self.get_const(place)? {
647 Value::Immediate(op) => {
648 let op = op.clone();
649 self.use_ecx(|this| this.ecx.read_discriminant(&op))?
650 }
651 Value::Aggregate { variant, .. } => *variant,
652 Value::Uninit => return None,
653 };
654 let imm = self.use_ecx(|this| {
655 this.ecx.discriminant_for_variant(
656 place.ty(this.local_decls(), this.tcx).ty,
657 variant,
658 )
659 })?;
660 imm.into()
661 }
662 };
663 trace!(?val);
664
665 *self.access_mut(dest)? = val;
666
667 Some(())
668 }
669}
670
671impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
672 fn visit_body(&mut self, body: &Body<'tcx>) {
673 while let Some(bb) = self.worklist.pop() {
674 if !self.visited_blocks.insert(bb) {
675 continue;
676 }
677
678 let data = &body.basic_blocks[bb];
679 self.visit_basic_block_data(bb, data);
680 }
681 }
682
683 fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
684 self.super_operand(operand, location);
685 }
686
687 fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, location: Location) {
688 trace!("visit_const_operand: {:?}", constant);
689 self.super_const_operand(constant, location);
690 self.eval_constant(constant);
691 }
692
693 fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
694 self.super_assign(place, rvalue, location);
695
696 let Some(()) = self.check_rvalue(rvalue, location) else { return };
697
698 match self.can_const_prop[place.local] {
699 _ if place.is_indirect() => {}
701 ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
702 ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => {
703 if self.eval_rvalue(rvalue, place).is_none() {
704 trace!(
715 "propagation into {:?} failed.
716 Nuking the entire site from orbit, it's the only way to be sure",
717 place,
718 );
719 self.remove_const(place.local);
720 }
721 }
722 }
723 }
724
725 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
726 trace!("visit_statement: {:?}", statement);
727
728 self.super_statement(statement, location);
731
732 match statement.kind {
733 StatementKind::SetDiscriminant { ref place, variant_index } => {
734 match self.can_const_prop[place.local] {
735 _ if place.is_indirect() => {}
737 ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local),
738 ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
739 match self.access_mut(place) {
740 Some(Value::Aggregate { variant, .. }) => *variant = variant_index,
741 _ => self.remove_const(place.local),
742 }
743 }
744 }
745 }
746 StatementKind::StorageLive(local) => {
747 self.remove_const(local);
748 }
749 StatementKind::StorageDead(local) => {
750 self.remove_const(local);
751 }
752 _ => {}
753 }
754 }
755
756 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
757 self.super_terminator(terminator, location);
758 match &terminator.kind {
759 TerminatorKind::Assert { expected, msg, cond, .. } => {
760 self.check_assertion(*expected, msg, cond, location);
761 }
762 TerminatorKind::SwitchInt { discr, targets } => {
763 if let Some(ref value) = self.eval_operand(discr)
764 && let Some(value_const) = self.use_ecx(|this| this.ecx.read_scalar(value))
765 && let Some(constant) = value_const.to_bits(value_const.size()).discard_err()
766 {
767 let target = targets.target_for_value(constant);
770 self.worklist.push(target);
771 return;
772 }
773 }
775 TerminatorKind::Call { func, args: _, .. } => {
776 if let Some((def_id, generic_args)) = func.const_fn_def() {
777 for (index, arg) in generic_args.iter().enumerate() {
778 if let GenericArgKind::Const(ct) = arg.kind() {
779 let generics = self.tcx.generics_of(def_id);
780 let param_def = generics.param_at(index, self.tcx);
781
782 if let GenericParamDefKind::Const { .. } = param_def.kind
783 && find_attr!(self.tcx, param_def.def_id, RustcPanicsWhenZero)
784 && let Some(0) = ct.try_to_target_usize(self.tcx)
785 {
786 let source_info = self.body.source_info(location);
791 if let Some(lint_root) = self.lint_root(*source_info) {
792 self.tcx.emit_node_span_lint(
793 UNCONDITIONAL_PANIC,
794 lint_root,
795 source_info.span,
796 ConstNIsZero {
797 const_param_span: source_info.span,
798 const_param_name: param_def.name,
799 },
800 );
801 }
802 }
803 }
804 }
805 }
806 }
807 TerminatorKind::Goto { .. }
809 | TerminatorKind::UnwindResume
810 | TerminatorKind::UnwindTerminate(_)
811 | TerminatorKind::Return
812 | TerminatorKind::TailCall { .. }
813 | TerminatorKind::Unreachable
814 | TerminatorKind::Drop { .. }
815 | TerminatorKind::Yield { .. }
816 | TerminatorKind::CoroutineDrop
817 | TerminatorKind::FalseEdge { .. }
818 | TerminatorKind::FalseUnwind { .. }
819 | TerminatorKind::InlineAsm { .. } => {}
820 }
821
822 self.worklist.extend(terminator.successors());
823 }
824
825 fn visit_basic_block_data(&mut self, block: BasicBlock, data: &BasicBlockData<'tcx>) {
826 self.super_basic_block_data(block, data);
827
828 let mut written_only_inside_own_block_locals =
832 std::mem::take(&mut self.written_only_inside_own_block_locals);
833
834 #[allow(rustc::potential_query_instability)]
839 for local in written_only_inside_own_block_locals.drain() {
840 debug_assert_eq!(self.can_const_prop[local], ConstPropMode::OnlyInsideOwnBlock);
841 self.remove_const(local);
842 }
843 self.written_only_inside_own_block_locals = written_only_inside_own_block_locals;
844
845 if cfg!(debug_assertions) {
846 for (local, &mode) in self.can_const_prop.iter_enumerated() {
847 match mode {
848 ConstPropMode::FullConstProp => {}
849 ConstPropMode::NoPropagation | ConstPropMode::OnlyInsideOwnBlock => {
850 self.ensure_not_propagated(local);
851 }
852 }
853 }
854 }
855 }
856}
857
858const MAX_ALLOC_LIMIT: u64 = 1024;
862
863#[derive(Clone, Copy, Debug, PartialEq)]
865enum ConstPropMode {
866 FullConstProp,
868 OnlyInsideOwnBlock,
870 NoPropagation,
873}
874
875struct CanConstProp {
878 can_const_prop: IndexVec<Local, ConstPropMode>,
879 found_assignment: DenseBitSet<Local>,
881}
882
883impl CanConstProp {
884 fn check<'tcx>(
886 tcx: TyCtxt<'tcx>,
887 typing_env: ty::TypingEnv<'tcx>,
888 body: &Body<'tcx>,
889 ) -> IndexVec<Local, ConstPropMode> {
890 let mut cpv = CanConstProp {
891 can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
892 found_assignment: DenseBitSet::new_empty(body.local_decls.len()),
893 };
894 for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
895 let ty = body.local_decls[local].ty;
896 if ty.is_async_drop_in_place_coroutine(tcx) {
897 *val = ConstPropMode::NoPropagation;
902 continue;
903 } else if ty.is_union() {
904 *val = ConstPropMode::NoPropagation;
908 } else {
909 match tcx.layout_of(typing_env.as_query_input(ty)) {
910 Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {}
911 _ => {
914 *val = ConstPropMode::NoPropagation;
915 continue;
916 }
917 }
918 }
919 }
920 for arg in body.args_iter() {
922 cpv.found_assignment.insert(arg);
923 }
924 cpv.visit_body(body);
925 cpv.can_const_prop
926 }
927}
928
929impl<'tcx> Visitor<'tcx> for CanConstProp {
930 fn visit_place(&mut self, place: &Place<'tcx>, mut context: PlaceContext, loc: Location) {
931 use rustc_middle::mir::visit::PlaceContext::*;
932
933 if place.projection.first() == Some(&PlaceElem::Deref) {
935 context = NonMutatingUse(NonMutatingUseContext::Copy);
936 }
937
938 self.visit_local(place.local, context, loc);
939 self.visit_projection(place.as_ref(), context, loc);
940 }
941
942 fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
943 use rustc_middle::mir::visit::PlaceContext::*;
944 match context {
945 | MutatingUse(MutatingUseContext::Call)
948 | MutatingUse(MutatingUseContext::AsmOutput)
949 | MutatingUse(MutatingUseContext::Store)
951 | MutatingUse(MutatingUseContext::SetDiscriminant) => {
952 if !self.found_assignment.insert(local) {
953 match &mut self.can_const_prop[local] {
954 ConstPropMode::OnlyInsideOwnBlock => {}
959 ConstPropMode::NoPropagation => {}
960 other @ ConstPropMode::FullConstProp => {
961 trace!(
962 "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
963 local, other,
964 );
965 *other = ConstPropMode::OnlyInsideOwnBlock;
966 }
967 }
968 }
969 }
970 NonMutatingUse(NonMutatingUseContext::Copy)
972 | NonMutatingUse(NonMutatingUseContext::Move)
973 | NonMutatingUse(NonMutatingUseContext::Inspect)
974 | NonMutatingUse(NonMutatingUseContext::PlaceMention)
975 | NonUse(_) => {}
976
977 MutatingUse(MutatingUseContext::Yield)
980 | MutatingUse(MutatingUseContext::Drop)
981 | MutatingUse(MutatingUseContext::Retag)
982 | NonMutatingUse(NonMutatingUseContext::SharedBorrow)
985 | NonMutatingUse(NonMutatingUseContext::FakeBorrow)
986 | NonMutatingUse(NonMutatingUseContext::RawBorrow)
987 | MutatingUse(MutatingUseContext::Borrow)
988 | MutatingUse(MutatingUseContext::RawBorrow) => {
989 trace!("local {:?} can't be propagated because it's used: {:?}", local, context);
990 self.can_const_prop[local] = ConstPropMode::NoPropagation;
991 }
992 MutatingUse(MutatingUseContext::Projection)
993 | NonMutatingUse(NonMutatingUseContext::Projection) => {
994 bug!("visit_place should not pass {context:?} for {local:?}")
995 }
996 }
997 }
998}