]>
Commit | Line | Data |
---|---|---|
5e7ed085 FG |
1 | //! Propagates constants for early reporting of statically known |
2 | //! assertion failures | |
3 | ||
064997fb FG |
4 | use crate::const_prop::CanConstProp; |
5 | use crate::const_prop::ConstPropMachine; | |
6 | use crate::const_prop::ConstPropMode; | |
7 | use crate::MirLint; | |
8 | use rustc_const_eval::const_eval::ConstEvalErr; | |
f2b60f7d | 9 | use rustc_const_eval::interpret::Immediate; |
064997fb | 10 | use rustc_const_eval::interpret::{ |
f2b60f7d | 11 | self, InterpCx, InterpResult, LocalState, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup, |
064997fb | 12 | }; |
5e7ed085 FG |
13 | use rustc_hir::def::DefKind; |
14 | use rustc_hir::HirId; | |
15 | use rustc_index::bit_set::BitSet; | |
16 | use rustc_index::vec::IndexVec; | |
064997fb | 17 | use rustc_middle::mir::visit::Visitor; |
5e7ed085 | 18 | use rustc_middle::mir::{ |
064997fb FG |
19 | AssertKind, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, Location, Operand, Place, |
20 | Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement, StatementKind, Terminator, | |
21 | TerminatorKind, UnOp, RETURN_PLACE, | |
5e7ed085 FG |
22 | }; |
23 | use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}; | |
24 | use rustc_middle::ty::subst::{InternalSubsts, Subst}; | |
f2b60f7d | 25 | use rustc_middle::ty::{self, ConstInt, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeVisitable}; |
5e7ed085 | 26 | use rustc_session::lint; |
064997fb | 27 | use rustc_span::Span; |
5e7ed085 | 28 | use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout}; |
5e7ed085 | 29 | use rustc_trait_selection::traits; |
064997fb | 30 | use std::cell::Cell; |
5e7ed085 FG |
31 | |
32 | /// The maximum number of bytes that we'll allocate space for a local or the return value. | |
33 | /// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just | |
34 | /// Severely regress performance. | |
35 | const MAX_ALLOC_LIMIT: u64 = 1024; | |
5e7ed085 FG |
36 | pub struct ConstProp; |
37 | ||
38 | impl<'tcx> MirLint<'tcx> for ConstProp { | |
39 | fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { | |
40 | // will be evaluated by miri and produce its errors there | |
41 | if body.source.promoted.is_some() { | |
42 | return; | |
43 | } | |
44 | ||
45 | let def_id = body.source.def_id().expect_local(); | |
04454e1e | 46 | let is_fn_like = tcx.def_kind(def_id).is_fn_like(); |
5e7ed085 FG |
47 | let is_assoc_const = tcx.def_kind(def_id) == DefKind::AssocConst; |
48 | ||
49 | // Only run const prop on functions, methods, closures and associated constants | |
50 | if !is_fn_like && !is_assoc_const { | |
51 | // skip anon_const/statics/consts because they'll be evaluated by miri anyway | |
52 | trace!("ConstProp skipped for {:?}", def_id); | |
53 | return; | |
54 | } | |
55 | ||
56 | let is_generator = tcx.type_of(def_id.to_def_id()).is_generator(); | |
57 | // FIXME(welseywiser) const prop doesn't work on generators because of query cycles | |
58 | // computing their layout. | |
59 | if is_generator { | |
60 | trace!("ConstProp skipped for generator {:?}", def_id); | |
61 | return; | |
62 | } | |
63 | ||
64 | // Check if it's even possible to satisfy the 'where' clauses | |
65 | // for this item. | |
66 | // This branch will never be taken for any normal function. | |
67 | // However, it's possible to `#!feature(trivial_bounds)]` to write | |
68 | // a function with impossible to satisfy clauses, e.g.: | |
69 | // `fn foo() where String: Copy {}` | |
70 | // | |
71 | // We don't usually need to worry about this kind of case, | |
72 | // since we would get a compilation error if the user tried | |
73 | // to call it. However, since we can do const propagation | |
74 | // even without any calls to the function, we need to make | |
75 | // sure that it even makes sense to try to evaluate the body. | |
76 | // If there are unsatisfiable where clauses, then all bets are | |
77 | // off, and we just give up. | |
78 | // | |
79 | // We manually filter the predicates, skipping anything that's not | |
80 | // "global". We are in a potentially generic context | |
81 | // (e.g. we are evaluating a function without substituting generic | |
82 | // parameters, so this filtering serves two purposes: | |
83 | // | |
84 | // 1. We skip evaluating any predicates that we would | |
85 | // never be able prove are unsatisfiable (e.g. `<T as Foo>` | |
86 | // 2. We avoid trying to normalize predicates involving generic | |
87 | // parameters (e.g. `<T as Foo>::MyItem`). This can confuse | |
88 | // the normalization code (leading to cycle errors), since | |
89 | // it's usually never invoked in this way. | |
90 | let predicates = tcx | |
91 | .predicates_of(def_id.to_def_id()) | |
92 | .predicates | |
93 | .iter() | |
94 | .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); | |
95 | if traits::impossible_predicates( | |
96 | tcx, | |
97 | traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(), | |
98 | ) { | |
99 | trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id); | |
100 | return; | |
101 | } | |
102 | ||
103 | trace!("ConstProp starting for {:?}", def_id); | |
104 | ||
105 | let dummy_body = &Body::new( | |
106 | body.source, | |
f2b60f7d | 107 | (*body.basic_blocks).clone(), |
5e7ed085 FG |
108 | body.source_scopes.clone(), |
109 | body.local_decls.clone(), | |
110 | Default::default(), | |
111 | body.arg_count, | |
112 | Default::default(), | |
113 | body.span, | |
114 | body.generator_kind(), | |
115 | body.tainted_by_errors, | |
116 | ); | |
117 | ||
118 | // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold | |
119 | // constants, instead of just checking for const-folding succeeding. | |
120 | // That would require a uniform one-def no-mutation analysis | |
121 | // and RPO (or recursing when needing the value of a local). | |
122 | let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx); | |
123 | optimization_finder.visit_body(body); | |
124 | ||
125 | trace!("ConstProp done for {:?}", def_id); | |
126 | } | |
127 | } | |
128 | ||
5e7ed085 FG |
129 | /// Finds optimization opportunities on the MIR. |
130 | struct ConstPropagator<'mir, 'tcx> { | |
131 | ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, | |
132 | tcx: TyCtxt<'tcx>, | |
133 | param_env: ParamEnv<'tcx>, | |
04454e1e FG |
134 | source_scopes: &'mir IndexVec<SourceScope, SourceScopeData<'tcx>>, |
135 | local_decls: &'mir IndexVec<Local, LocalDecl<'tcx>>, | |
5e7ed085 FG |
136 | // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store |
137 | // the last known `SourceInfo` here and just keep revisiting it. | |
138 | source_info: Option<SourceInfo>, | |
139 | } | |
140 | ||
141 | impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> { | |
142 | type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>; | |
143 | ||
144 | #[inline] | |
145 | fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> { | |
146 | err | |
147 | } | |
148 | } | |
149 | ||
150 | impl HasDataLayout for ConstPropagator<'_, '_> { | |
151 | #[inline] | |
152 | fn data_layout(&self) -> &TargetDataLayout { | |
153 | &self.tcx.data_layout | |
154 | } | |
155 | } | |
156 | ||
157 | impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> { | |
158 | #[inline] | |
159 | fn tcx(&self) -> TyCtxt<'tcx> { | |
160 | self.tcx | |
161 | } | |
162 | } | |
163 | ||
164 | impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> { | |
165 | #[inline] | |
166 | fn param_env(&self) -> ty::ParamEnv<'tcx> { | |
167 | self.param_env | |
168 | } | |
169 | } | |
170 | ||
171 | impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { | |
172 | fn new( | |
173 | body: &Body<'tcx>, | |
174 | dummy_body: &'mir Body<'tcx>, | |
175 | tcx: TyCtxt<'tcx>, | |
176 | ) -> ConstPropagator<'mir, 'tcx> { | |
177 | let def_id = body.source.def_id(); | |
178 | let substs = &InternalSubsts::identity_for_item(tcx, def_id); | |
179 | let param_env = tcx.param_env_reveal_all_normalized(def_id); | |
180 | ||
5e7ed085 FG |
181 | let can_const_prop = CanConstProp::check(tcx, param_env, body); |
182 | let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len()); | |
183 | for (l, mode) in can_const_prop.iter_enumerated() { | |
184 | if *mode == ConstPropMode::OnlyInsideOwnBlock { | |
185 | only_propagate_inside_block_locals.insert(l); | |
186 | } | |
187 | } | |
188 | let mut ecx = InterpCx::new( | |
189 | tcx, | |
04454e1e | 190 | tcx.def_span(def_id), |
5e7ed085 FG |
191 | param_env, |
192 | ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop), | |
5e7ed085 FG |
193 | ); |
194 | ||
923072b8 | 195 | let ret_layout = ecx |
064997fb | 196 | .layout_of(body.bound_return_ty().subst(tcx, substs)) |
5e7ed085 | 197 | .ok() |
923072b8 | 198 | // Don't bother allocating memory for large values. |
064997fb FG |
199 | // I don't know how return types can seem to be unsized but this happens in the |
200 | // `type/type-unsatisfiable.rs` test. | |
201 | .filter(|ret_layout| { | |
202 | !ret_layout.is_unsized() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) | |
203 | }) | |
923072b8 FG |
204 | .unwrap_or_else(|| ecx.layout_of(tcx.types.unit).unwrap()); |
205 | ||
206 | let ret = ecx | |
207 | .allocate(ret_layout, MemoryKind::Stack) | |
208 | .expect("couldn't perform small allocation") | |
209 | .into(); | |
5e7ed085 FG |
210 | |
211 | ecx.push_stack_frame( | |
212 | Instance::new(def_id, substs), | |
213 | dummy_body, | |
923072b8 | 214 | &ret, |
5e7ed085 FG |
215 | StackPopCleanup::Root { cleanup: false }, |
216 | ) | |
217 | .expect("failed to push initial stack frame"); | |
218 | ||
219 | ConstPropagator { | |
220 | ecx, | |
221 | tcx, | |
222 | param_env, | |
04454e1e FG |
223 | source_scopes: &dummy_body.source_scopes, |
224 | local_decls: &dummy_body.local_decls, | |
5e7ed085 FG |
225 | source_info: None, |
226 | } | |
227 | } | |
228 | ||
229 | fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> { | |
230 | let op = match self.ecx.eval_place_to_op(place, None) { | |
f2b60f7d FG |
231 | Ok(op) => { |
232 | if matches!(*op, interpret::Operand::Immediate(Immediate::Uninit)) { | |
233 | // Make sure nobody accidentally uses this value. | |
234 | return None; | |
235 | } | |
236 | op | |
237 | } | |
5e7ed085 FG |
238 | Err(e) => { |
239 | trace!("get_const failed: {}", e); | |
240 | return None; | |
241 | } | |
242 | }; | |
243 | ||
244 | // Try to read the local as an immediate so that if it is representable as a scalar, we can | |
245 | // handle it as such, but otherwise, just return the value as is. | |
f2b60f7d | 246 | Some(match self.ecx.read_immediate_raw(&op) { |
5e7ed085 FG |
247 | Ok(Ok(imm)) => imm.into(), |
248 | _ => op, | |
249 | }) | |
250 | } | |
251 | ||
252 | /// Remove `local` from the pool of `Locals`. Allows writing to them, | |
253 | /// but not reading from them anymore. | |
254 | fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) { | |
064997fb FG |
255 | ecx.frame_mut().locals[local] = LocalState { |
256 | value: LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)), | |
257 | layout: Cell::new(None), | |
258 | }; | |
5e7ed085 FG |
259 | } |
260 | ||
261 | fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> { | |
04454e1e | 262 | source_info.scope.lint_root(self.source_scopes) |
5e7ed085 FG |
263 | } |
264 | ||
923072b8 | 265 | fn use_ecx<F, T>(&mut self, source_info: SourceInfo, f: F) -> Option<T> |
5e7ed085 FG |
266 | where |
267 | F: FnOnce(&mut Self) -> InterpResult<'tcx, T>, | |
268 | { | |
923072b8 FG |
269 | // Overwrite the PC -- whatever the interpreter does to it does not make any sense anyway. |
270 | self.ecx.frame_mut().loc = Err(source_info.span); | |
5e7ed085 FG |
271 | match f(self) { |
272 | Ok(val) => Some(val), | |
273 | Err(error) => { | |
274 | trace!("InterpCx operation failed: {:?}", error); | |
275 | // Some errors shouldn't come up because creating them causes | |
276 | // an allocation, which we should avoid. When that happens, | |
277 | // dedicated error variants should be introduced instead. | |
278 | assert!( | |
279 | !error.kind().formatted_string(), | |
280 | "const-prop encountered formatting error: {}", | |
281 | error | |
282 | ); | |
283 | None | |
284 | } | |
285 | } | |
286 | } | |
287 | ||
288 | /// Returns the value, if any, of evaluating `c`. | |
289 | fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { | |
290 | // FIXME we need to revisit this for #67176 | |
291 | if c.needs_subst() { | |
292 | return None; | |
293 | } | |
294 | ||
295 | match self.ecx.mir_const_to_op(&c.literal, None) { | |
296 | Ok(op) => Some(op), | |
297 | Err(error) => { | |
298 | let tcx = self.ecx.tcx.at(c.span); | |
299 | let err = ConstEvalErr::new(&self.ecx, error, Some(c.span)); | |
300 | if let Some(lint_root) = self.lint_root(source_info) { | |
301 | let lint_only = match c.literal { | |
f2b60f7d FG |
302 | ConstantKind::Ty(ct) => ct.needs_subst(), |
303 | ConstantKind::Unevaluated( | |
304 | ty::Unevaluated { def: _, substs: _, promoted: Some(_) }, | |
305 | _, | |
306 | ) => { | |
5e7ed085 | 307 | // Promoteds must lint and not error as the user didn't ask for them |
f2b60f7d FG |
308 | true |
309 | } | |
310 | ConstantKind::Unevaluated(..) | ConstantKind::Val(..) => c.needs_subst(), | |
5e7ed085 FG |
311 | }; |
312 | if lint_only { | |
313 | // Out of backwards compatibility we cannot report hard errors in unused | |
314 | // generic functions using associated constants of the generic parameters. | |
315 | err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span)); | |
316 | } else { | |
317 | err.report_as_error(tcx, "erroneous constant used"); | |
318 | } | |
319 | } else { | |
320 | err.report_as_error(tcx, "erroneous constant used"); | |
321 | } | |
322 | None | |
323 | } | |
324 | } | |
325 | } | |
326 | ||
327 | /// Returns the value, if any, of evaluating `place`. | |
923072b8 | 328 | fn eval_place(&mut self, place: Place<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { |
5e7ed085 | 329 | trace!("eval_place(place={:?})", place); |
923072b8 | 330 | self.use_ecx(source_info, |this| this.ecx.eval_place_to_op(place, None)) |
5e7ed085 FG |
331 | } |
332 | ||
333 | /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant` | |
334 | /// or `eval_place`, depending on the variant of `Operand` used. | |
335 | fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { | |
336 | match *op { | |
337 | Operand::Constant(ref c) => self.eval_constant(c, source_info), | |
923072b8 | 338 | Operand::Move(place) | Operand::Copy(place) => self.eval_place(place, source_info), |
5e7ed085 FG |
339 | } |
340 | } | |
341 | ||
342 | fn report_assert_as_lint( | |
343 | &self, | |
344 | lint: &'static lint::Lint, | |
345 | source_info: SourceInfo, | |
346 | message: &'static str, | |
347 | panic: AssertKind<impl std::fmt::Debug>, | |
348 | ) { | |
349 | if let Some(lint_root) = self.lint_root(source_info) { | |
350 | self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| { | |
351 | let mut err = lint.build(message); | |
352 | err.span_label(source_info.span, format!("{:?}", panic)); | |
353 | err.emit(); | |
354 | }); | |
355 | } | |
356 | } | |
357 | ||
358 | fn check_unary_op( | |
359 | &mut self, | |
360 | op: UnOp, | |
361 | arg: &Operand<'tcx>, | |
362 | source_info: SourceInfo, | |
363 | ) -> Option<()> { | |
923072b8 | 364 | if let (val, true) = self.use_ecx(source_info, |this| { |
5e7ed085 FG |
365 | let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?; |
366 | let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?; | |
367 | Ok((val, overflow)) | |
368 | })? { | |
369 | // `AssertKind` only has an `OverflowNeg` variant, so make sure that is | |
370 | // appropriate to use. | |
371 | assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow"); | |
372 | self.report_assert_as_lint( | |
373 | lint::builtin::ARITHMETIC_OVERFLOW, | |
374 | source_info, | |
375 | "this arithmetic operation will overflow", | |
376 | AssertKind::OverflowNeg(val.to_const_int()), | |
377 | ); | |
378 | return None; | |
379 | } | |
380 | ||
381 | Some(()) | |
382 | } | |
383 | ||
384 | fn check_binary_op( | |
385 | &mut self, | |
386 | op: BinOp, | |
387 | left: &Operand<'tcx>, | |
388 | right: &Operand<'tcx>, | |
389 | source_info: SourceInfo, | |
390 | ) -> Option<()> { | |
923072b8 FG |
391 | let r = self.use_ecx(source_info, |this| { |
392 | this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?) | |
393 | }); | |
394 | let l = self.use_ecx(source_info, |this| { | |
395 | this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?) | |
396 | }); | |
5e7ed085 FG |
397 | // Check for exceeding shifts *even if* we cannot evaluate the LHS. |
398 | if op == BinOp::Shr || op == BinOp::Shl { | |
064997fb | 399 | let r = r.clone()?; |
5e7ed085 FG |
400 | // We need the type of the LHS. We cannot use `place_layout` as that is the type |
401 | // of the result, which for checked binops is not the same! | |
04454e1e | 402 | let left_ty = left.ty(self.local_decls, self.tcx); |
5e7ed085 FG |
403 | let left_size = self.ecx.layout_of(left_ty).ok()?.size; |
404 | let right_size = r.layout.size; | |
f2b60f7d | 405 | let r_bits = r.to_scalar().to_bits(right_size).ok(); |
5e7ed085 FG |
406 | if r_bits.map_or(false, |b| b >= left_size.bits() as u128) { |
407 | debug!("check_binary_op: reporting assert for {:?}", source_info); | |
408 | self.report_assert_as_lint( | |
409 | lint::builtin::ARITHMETIC_OVERFLOW, | |
410 | source_info, | |
411 | "this arithmetic operation will overflow", | |
412 | AssertKind::Overflow( | |
413 | op, | |
414 | match l { | |
415 | Some(l) => l.to_const_int(), | |
416 | // Invent a dummy value, the diagnostic ignores it anyway | |
417 | None => ConstInt::new( | |
418 | ScalarInt::try_from_uint(1_u8, left_size).unwrap(), | |
419 | left_ty.is_signed(), | |
420 | left_ty.is_ptr_sized_integral(), | |
421 | ), | |
422 | }, | |
423 | r.to_const_int(), | |
424 | ), | |
425 | ); | |
426 | return None; | |
427 | } | |
428 | } | |
429 | ||
064997fb | 430 | if let (Some(l), Some(r)) = (l, r) { |
5e7ed085 | 431 | // The remaining operators are handled through `overflowing_binary_op`. |
923072b8 | 432 | if self.use_ecx(source_info, |this| { |
064997fb | 433 | let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, &l, &r)?; |
5e7ed085 FG |
434 | Ok(overflow) |
435 | })? { | |
436 | self.report_assert_as_lint( | |
437 | lint::builtin::ARITHMETIC_OVERFLOW, | |
438 | source_info, | |
439 | "this arithmetic operation will overflow", | |
440 | AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()), | |
441 | ); | |
442 | return None; | |
443 | } | |
444 | } | |
445 | Some(()) | |
446 | } | |
447 | ||
448 | fn const_prop( | |
449 | &mut self, | |
450 | rvalue: &Rvalue<'tcx>, | |
451 | source_info: SourceInfo, | |
452 | place: Place<'tcx>, | |
453 | ) -> Option<()> { | |
454 | // Perform any special handling for specific Rvalue types. | |
455 | // Generally, checks here fall into one of two categories: | |
456 | // 1. Additional checking to provide useful lints to the user | |
457 | // - In this case, we will do some validation and then fall through to the | |
458 | // end of the function which evals the assignment. | |
459 | // 2. Working around bugs in other parts of the compiler | |
460 | // - In this case, we'll return `None` from this function to stop evaluation. | |
461 | match rvalue { | |
462 | // Additional checking: give lints to the user if an overflow would occur. | |
463 | // We do this here and not in the `Assert` terminator as that terminator is | |
464 | // only sometimes emitted (overflow checks can be disabled), but we want to always | |
465 | // lint. | |
466 | Rvalue::UnaryOp(op, arg) => { | |
467 | trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg); | |
468 | self.check_unary_op(*op, arg, source_info)?; | |
469 | } | |
470 | Rvalue::BinaryOp(op, box (left, right)) => { | |
471 | trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right); | |
472 | self.check_binary_op(*op, left, right, source_info)?; | |
473 | } | |
474 | Rvalue::CheckedBinaryOp(op, box (left, right)) => { | |
475 | trace!( | |
476 | "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})", | |
477 | op, | |
478 | left, | |
479 | right | |
480 | ); | |
481 | self.check_binary_op(*op, left, right, source_info)?; | |
482 | } | |
483 | ||
484 | // Do not try creating references (#67862) | |
485 | Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => { | |
486 | trace!("skipping AddressOf | Ref for {:?}", place); | |
487 | ||
488 | // This may be creating mutable references or immutable references to cells. | |
489 | // If that happens, the pointed to value could be mutated via that reference. | |
490 | // Since we aren't tracking references, the const propagator loses track of what | |
491 | // value the local has right now. | |
492 | // Thus, all locals that have their reference taken | |
493 | // must not take part in propagation. | |
494 | Self::remove_const(&mut self.ecx, place.local); | |
495 | ||
496 | return None; | |
497 | } | |
498 | Rvalue::ThreadLocalRef(def_id) => { | |
499 | trace!("skipping ThreadLocalRef({:?})", def_id); | |
500 | ||
501 | return None; | |
502 | } | |
503 | ||
504 | // There's no other checking to do at this time. | |
505 | Rvalue::Aggregate(..) | |
506 | | Rvalue::Use(..) | |
064997fb | 507 | | Rvalue::CopyForDeref(..) |
5e7ed085 FG |
508 | | Rvalue::Repeat(..) |
509 | | Rvalue::Len(..) | |
510 | | Rvalue::Cast(..) | |
511 | | Rvalue::ShallowInitBox(..) | |
512 | | Rvalue::Discriminant(..) | |
513 | | Rvalue::NullaryOp(..) => {} | |
514 | } | |
515 | ||
516 | // FIXME we need to revisit this for #67176 | |
517 | if rvalue.needs_subst() { | |
518 | return None; | |
519 | } | |
f2b60f7d FG |
520 | if !rvalue |
521 | .ty(&self.ecx.frame().body.local_decls, *self.ecx.tcx) | |
522 | .is_sized(self.ecx.tcx, self.param_env) | |
523 | { | |
524 | // the interpreter doesn't support unsized locals (only unsized arguments), | |
525 | // but rustc does (in a kinda broken way), so we have to skip them here | |
526 | return None; | |
527 | } | |
5e7ed085 | 528 | |
923072b8 | 529 | self.use_ecx(source_info, |this| this.ecx.eval_rvalue_into_place(rvalue, place)) |
5e7ed085 FG |
530 | } |
531 | } | |
532 | ||
5e7ed085 FG |
533 | impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { |
534 | fn visit_body(&mut self, body: &Body<'tcx>) { | |
f2b60f7d | 535 | for (bb, data) in body.basic_blocks.iter_enumerated() { |
5e7ed085 FG |
536 | self.visit_basic_block_data(bb, data); |
537 | } | |
538 | } | |
539 | ||
540 | fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { | |
541 | self.super_operand(operand, location); | |
542 | } | |
543 | ||
544 | fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) { | |
545 | trace!("visit_constant: {:?}", constant); | |
546 | self.super_constant(constant, location); | |
547 | self.eval_constant(constant, self.source_info.unwrap()); | |
548 | } | |
549 | ||
550 | fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { | |
551 | trace!("visit_statement: {:?}", statement); | |
552 | let source_info = statement.source_info; | |
553 | self.source_info = Some(source_info); | |
554 | if let StatementKind::Assign(box (place, ref rval)) = statement.kind { | |
555 | let can_const_prop = self.ecx.machine.can_const_prop[place.local]; | |
556 | if let Some(()) = self.const_prop(rval, source_info, place) { | |
557 | match can_const_prop { | |
558 | ConstPropMode::OnlyInsideOwnBlock => { | |
559 | trace!( | |
560 | "found local restricted to its block. \ | |
561 | Will remove it from const-prop after block is finished. Local: {:?}", | |
562 | place.local | |
563 | ); | |
564 | } | |
565 | ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => { | |
566 | trace!("can't propagate into {:?}", place); | |
567 | if place.local != RETURN_PLACE { | |
568 | Self::remove_const(&mut self.ecx, place.local); | |
569 | } | |
570 | } | |
571 | ConstPropMode::FullConstProp => {} | |
572 | } | |
573 | } else { | |
574 | // Const prop failed, so erase the destination, ensuring that whatever happens | |
575 | // from here on, does not know about the previous value. | |
576 | // This is important in case we have | |
577 | // ```rust | |
578 | // let mut x = 42; | |
579 | // x = SOME_MUTABLE_STATIC; | |
580 | // // x must now be uninit | |
581 | // ``` | |
582 | // FIXME: we overzealously erase the entire local, because that's easier to | |
583 | // implement. | |
584 | trace!( | |
585 | "propagation into {:?} failed. | |
586 | Nuking the entire site from orbit, it's the only way to be sure", | |
587 | place, | |
588 | ); | |
589 | Self::remove_const(&mut self.ecx, place.local); | |
590 | } | |
591 | } else { | |
592 | match statement.kind { | |
593 | StatementKind::SetDiscriminant { ref place, .. } => { | |
594 | match self.ecx.machine.can_const_prop[place.local] { | |
595 | ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => { | |
923072b8 FG |
596 | if self |
597 | .use_ecx(source_info, |this| this.ecx.statement(statement)) | |
598 | .is_some() | |
599 | { | |
5e7ed085 FG |
600 | trace!("propped discriminant into {:?}", place); |
601 | } else { | |
602 | Self::remove_const(&mut self.ecx, place.local); | |
603 | } | |
604 | } | |
605 | ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => { | |
606 | Self::remove_const(&mut self.ecx, place.local); | |
607 | } | |
608 | } | |
609 | } | |
610 | StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { | |
611 | let frame = self.ecx.frame_mut(); | |
612 | frame.locals[local].value = | |
613 | if let StatementKind::StorageLive(_) = statement.kind { | |
064997fb FG |
614 | LocalValue::Live(interpret::Operand::Immediate( |
615 | interpret::Immediate::Uninit, | |
616 | )) | |
5e7ed085 FG |
617 | } else { |
618 | LocalValue::Dead | |
619 | }; | |
620 | } | |
621 | _ => {} | |
622 | } | |
623 | } | |
624 | ||
625 | self.super_statement(statement, location); | |
626 | } | |
627 | ||
628 | fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { | |
629 | let source_info = terminator.source_info; | |
630 | self.source_info = Some(source_info); | |
631 | self.super_terminator(terminator, location); | |
632 | match &terminator.kind { | |
633 | TerminatorKind::Assert { expected, ref msg, ref cond, .. } => { | |
634 | if let Some(ref value) = self.eval_operand(&cond, source_info) { | |
635 | trace!("assertion on {:?} should be {:?}", value, expected); | |
f2b60f7d FG |
636 | let expected = Scalar::from_bool(*expected); |
637 | let Ok(value_const) = self.ecx.read_scalar(&value) else { | |
638 | // FIXME should be used use_ecx rather than a local match... but we have | |
639 | // quite a few of these read_scalar/read_immediate that need fixing. | |
640 | return | |
641 | }; | |
5e7ed085 FG |
642 | if expected != value_const { |
643 | enum DbgVal<T> { | |
644 | Val(T), | |
645 | Underscore, | |
646 | } | |
647 | impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> { | |
648 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
649 | match self { | |
650 | Self::Val(val) => val.fmt(fmt), | |
651 | Self::Underscore => fmt.write_str("_"), | |
652 | } | |
653 | } | |
654 | } | |
655 | let mut eval_to_int = |op| { | |
656 | // This can be `None` if the lhs wasn't const propagated and we just | |
657 | // triggered the assert on the value of the rhs. | |
f2b60f7d FG |
658 | self.eval_operand(op, source_info) |
659 | .and_then(|op| self.ecx.read_immediate(&op).ok()) | |
660 | .map_or(DbgVal::Underscore, |op| DbgVal::Val(op.to_const_int())) | |
5e7ed085 FG |
661 | }; |
662 | let msg = match msg { | |
663 | AssertKind::DivisionByZero(op) => { | |
664 | Some(AssertKind::DivisionByZero(eval_to_int(op))) | |
665 | } | |
666 | AssertKind::RemainderByZero(op) => { | |
667 | Some(AssertKind::RemainderByZero(eval_to_int(op))) | |
668 | } | |
669 | AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => { | |
670 | // Division overflow is *UB* in the MIR, and different than the | |
671 | // other overflow checks. | |
672 | Some(AssertKind::Overflow( | |
673 | *bin_op, | |
674 | eval_to_int(op1), | |
675 | eval_to_int(op2), | |
676 | )) | |
677 | } | |
678 | AssertKind::BoundsCheck { ref len, ref index } => { | |
679 | let len = eval_to_int(len); | |
680 | let index = eval_to_int(index); | |
681 | Some(AssertKind::BoundsCheck { len, index }) | |
682 | } | |
683 | // Remaining overflow errors are already covered by checks on the binary operators. | |
684 | AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None, | |
685 | // Need proper const propagator for these. | |
686 | _ => None, | |
687 | }; | |
688 | // Poison all places this operand references so that further code | |
689 | // doesn't use the invalid value | |
690 | match cond { | |
691 | Operand::Move(ref place) | Operand::Copy(ref place) => { | |
692 | Self::remove_const(&mut self.ecx, place.local); | |
693 | } | |
694 | Operand::Constant(_) => {} | |
695 | } | |
696 | if let Some(msg) = msg { | |
697 | self.report_assert_as_lint( | |
698 | lint::builtin::UNCONDITIONAL_PANIC, | |
699 | source_info, | |
700 | "this operation will panic at runtime", | |
701 | msg, | |
702 | ); | |
703 | } | |
704 | } | |
705 | } | |
706 | } | |
707 | // None of these have Operands to const-propagate. | |
708 | TerminatorKind::Goto { .. } | |
709 | | TerminatorKind::Resume | |
710 | | TerminatorKind::Abort | |
711 | | TerminatorKind::Return | |
712 | | TerminatorKind::Unreachable | |
713 | | TerminatorKind::Drop { .. } | |
714 | | TerminatorKind::DropAndReplace { .. } | |
715 | | TerminatorKind::Yield { .. } | |
716 | | TerminatorKind::GeneratorDrop | |
717 | | TerminatorKind::FalseEdge { .. } | |
718 | | TerminatorKind::FalseUnwind { .. } | |
719 | | TerminatorKind::SwitchInt { .. } | |
720 | | TerminatorKind::Call { .. } | |
721 | | TerminatorKind::InlineAsm { .. } => {} | |
722 | } | |
723 | ||
724 | // We remove all Locals which are restricted in propagation to their containing blocks and | |
725 | // which were modified in the current block. | |
726 | // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`. | |
727 | let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals); | |
728 | for &local in locals.iter() { | |
729 | Self::remove_const(&mut self.ecx, local); | |
730 | } | |
731 | locals.clear(); | |
732 | // Put it back so we reuse the heap of the storage | |
733 | self.ecx.machine.written_only_inside_own_block_locals = locals; | |
734 | if cfg!(debug_assertions) { | |
735 | // Ensure we are correctly erasing locals with the non-debug-assert logic. | |
736 | for local in self.ecx.machine.only_propagate_inside_block_locals.iter() { | |
737 | assert!( | |
738 | self.get_const(local.into()).is_none() | |
739 | || self | |
740 | .layout_of(self.local_decls[local].ty) | |
741 | .map_or(true, |layout| layout.is_zst()) | |
742 | ) | |
743 | } | |
744 | } | |
745 | } | |
746 | } |