1 //! Validates the MIR to ensure that invariants are upheld.
3 use super::{MirPass, MirSource};
4 use rustc_middle::mir::visit::Visitor;
7 BasicBlock, Body, Location, Operand, Rvalue, Statement, StatementKind, Terminator,
12 relate::{Relate, RelateResult, TypeRelation},
17 #[derive(Copy, Clone, Debug)]
23 pub struct Validator {
24 /// Describes at which point in the pipeline this validation is happening.
28 impl<'tcx> MirPass<'tcx> for Validator {
29 fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
30 let param_env = tcx.param_env(source.def_id());
31 TypeChecker { when: &self.when, source, body, tcx, param_env }.visit_body(body);
35 /// Returns whether the two types are equal up to lifetimes.
36 /// All lifetimes, including higher-ranked ones, get ignored for this comparison.
37 /// (This is unlike the `erasing_regions` methods, which keep higher-ranked lifetimes for soundness reasons.)
39 /// The point of this function is to approximate "equal up to subtyping". However,
40 /// the approximation is incorrect as variance is ignored.
41 pub fn equal_up_to_regions(
43 param_env: ParamEnv<'tcx>,
52 struct LifetimeIgnoreRelation<'tcx> {
54 param_env: ty::ParamEnv<'tcx>,
57 impl TypeRelation<'tcx> for LifetimeIgnoreRelation<'tcx> {
58 fn tcx(&self) -> TyCtxt<'tcx> {
62 fn param_env(&self) -> ty::ParamEnv<'tcx> {
66 fn tag(&self) -> &'static str {
67 "librustc_mir::transform::validate"
70 fn a_is_expected(&self) -> bool {
74 fn relate_with_variance<T: Relate<'tcx>>(
79 ) -> RelateResult<'tcx, T> {
80 // Ignore variance, require types to be exactly the same.
84 fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
89 ty::relate::super_relate_tys(self, a, b)
96 ) -> RelateResult<'tcx, ty::Region<'tcx>> {
103 a: &'tcx ty::Const<'tcx>,
104 b: &'tcx ty::Const<'tcx>,
105 ) -> RelateResult<'tcx, &'tcx ty::Const<'tcx>> {
106 ty::relate::super_relate_consts(self, a, b)
113 ) -> RelateResult<'tcx, ty::Binder<T>>
117 self.relate(a.skip_binder(), b.skip_binder())?;
122 // Instantiate and run relation.
123 let mut relator: LifetimeIgnoreRelation<'tcx> = LifetimeIgnoreRelation { tcx: tcx, param_env };
124 relator.relate(src, dest).is_ok()
127 struct TypeChecker<'a, 'tcx> {
129 source: MirSource<'tcx>,
130 body: &'a Body<'tcx>,
132 param_env: ParamEnv<'tcx>,
135 impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
136 fn fail(&self, location: Location, msg: impl AsRef<str>) {
137 let span = self.body.source_info(location).span;
138 // We use `delay_span_bug` as we might see broken MIR when other errors have already
140 self.tcx.sess.diagnostic().delay_span_bug(
143 "broken MIR in {:?} ({}) at {:?}:\n{}",
144 self.source.instance,
152 fn check_edge(&self, location: Location, bb: BasicBlock, edge_kind: EdgeKind) {
153 if let Some(bb) = self.body.basic_blocks().get(bb) {
154 let src = self.body.basic_blocks().get(location.block).unwrap();
155 match (src.is_cleanup, bb.is_cleanup, edge_kind) {
156 // Non-cleanup blocks can jump to non-cleanup blocks along non-unwind edges
157 (false, false, EdgeKind::Normal)
158 // Non-cleanup blocks can jump to cleanup blocks along unwind edges
159 | (false, true, EdgeKind::Unwind)
160 // Cleanup blocks can jump to cleanup blocks along non-unwind edges
161 | (true, true, EdgeKind::Normal) => {}
162 // All other jumps are invalid
167 "{:?} edge to {:?} violates unwind invariants (cleanup {:?} -> {:?})",
177 self.fail(location, format!("encountered jump to invalid basic block {:?}", bb))
181 /// Check if src can be assigned into dest.
182 /// This is not precise, it will accept some incorrect assignments.
183 fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool {
184 // Fast path before we normalize.
186 // Equal types, all is good.
189 // Normalize projections and things like that.
190 // FIXME: We need to reveal_all, as some optimizations change types in ways
191 // that require unfolding opaque types.
192 let param_env = self.param_env.with_reveal_all_normalized(self.tcx);
193 let src = self.tcx.normalize_erasing_regions(param_env, src);
194 let dest = self.tcx.normalize_erasing_regions(param_env, dest);
196 // Type-changing assignments can happen when subtyping is used. While
197 // all normal lifetimes are erased, higher-ranked types with their
198 // late-bound lifetimes are still around and can lead to type
199 // differences. So we compare ignoring lifetimes.
200 equal_up_to_regions(self.tcx, param_env, src, dest)
204 impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
205 fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
206 // `Operand::Copy` is only supposed to be used with `Copy` types.
207 if let Operand::Copy(place) = operand {
208 let ty = place.ty(&self.body.local_decls, self.tcx).ty;
209 let span = self.body.source_info(location).span;
211 if !ty.is_copy_modulo_regions(self.tcx.at(span), self.param_env) {
212 self.fail(location, format!("`Operand::Copy` with non-`Copy` type {}", ty));
216 self.super_operand(operand, location);
219 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
220 match &statement.kind {
221 StatementKind::Assign(box (dest, rvalue)) => {
222 // LHS and RHS of the assignment must have the same type.
223 let left_ty = dest.ty(&self.body.local_decls, self.tcx).ty;
224 let right_ty = rvalue.ty(&self.body.local_decls, self.tcx);
225 if !self.mir_assign_valid_types(right_ty, left_ty) {
229 "encountered `Assign` statement with incompatible types:\n\
230 left-hand side has type: {}\n\
231 right-hand side has type: {}",
236 // The sides of an assignment must not alias. Currently this just checks whether the places
239 Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) => {
243 "encountered `Assign` statement with overlapping memory",
254 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
255 match &terminator.kind {
256 TerminatorKind::Goto { target } => {
257 self.check_edge(location, *target, EdgeKind::Normal);
259 TerminatorKind::SwitchInt { targets, values, switch_ty, discr } => {
260 let ty = discr.ty(&self.body.local_decls, self.tcx);
261 if ty != *switch_ty {
265 "encountered `SwitchInt` terminator with type mismatch: {:?} != {:?}",
270 if targets.len() != values.len() + 1 {
274 "encountered `SwitchInt` terminator with {} values, but {} targets (should be values+1)",
280 for target in targets {
281 self.check_edge(location, *target, EdgeKind::Normal);
284 TerminatorKind::Drop { target, unwind, .. } => {
285 self.check_edge(location, *target, EdgeKind::Normal);
286 if let Some(unwind) = unwind {
287 self.check_edge(location, *unwind, EdgeKind::Unwind);
290 TerminatorKind::DropAndReplace { target, unwind, .. } => {
291 self.check_edge(location, *target, EdgeKind::Normal);
292 if let Some(unwind) = unwind {
293 self.check_edge(location, *unwind, EdgeKind::Unwind);
296 TerminatorKind::Call { func, destination, cleanup, .. } => {
297 let func_ty = func.ty(&self.body.local_decls, self.tcx);
299 ty::FnPtr(..) | ty::FnDef(..) => {}
302 format!("encountered non-callable type {} in `Call` terminator", func_ty),
305 if let Some((_, target)) = destination {
306 self.check_edge(location, *target, EdgeKind::Normal);
308 if let Some(cleanup) = cleanup {
309 self.check_edge(location, *cleanup, EdgeKind::Unwind);
312 TerminatorKind::Assert { cond, target, cleanup, .. } => {
313 let cond_ty = cond.ty(&self.body.local_decls, self.tcx);
314 if cond_ty != self.tcx.types.bool {
318 "encountered non-boolean condition of type {} in `Assert` terminator",
323 self.check_edge(location, *target, EdgeKind::Normal);
324 if let Some(cleanup) = cleanup {
325 self.check_edge(location, *cleanup, EdgeKind::Unwind);
328 TerminatorKind::Yield { resume, drop, .. } => {
329 self.check_edge(location, *resume, EdgeKind::Normal);
330 if let Some(drop) = drop {
331 self.check_edge(location, *drop, EdgeKind::Normal);
334 TerminatorKind::FalseEdge { real_target, imaginary_target } => {
335 self.check_edge(location, *real_target, EdgeKind::Normal);
336 self.check_edge(location, *imaginary_target, EdgeKind::Normal);
338 TerminatorKind::FalseUnwind { real_target, unwind } => {
339 self.check_edge(location, *real_target, EdgeKind::Normal);
340 if let Some(unwind) = unwind {
341 self.check_edge(location, *unwind, EdgeKind::Unwind);
344 TerminatorKind::InlineAsm { destination, .. } => {
345 if let Some(destination) = destination {
346 self.check_edge(location, *destination, EdgeKind::Normal);
349 // Nothing to validate for these.
350 TerminatorKind::Resume
351 | TerminatorKind::Abort
352 | TerminatorKind::Return
353 | TerminatorKind::Unreachable
354 | TerminatorKind::GeneratorDrop => {}