about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2020-06-07 02:28:54 +0200
committerGitHub <noreply@github.com>2020-06-07 02:28:54 +0200
commit63b314c367ad37ca7fdd40d7d3678ad42d93057a (patch)
tree309d91cbf129be457c8119fe54bfaf3fc985c3ee /src
parent9c1857fad849ad56d38327b9bc11377a0bdbb4cf (diff)
parentf793c0b1bf3392c19eb11331fddc8c9f561361ee (diff)
downloadrust-63b314c367ad37ca7fdd40d7d3678ad42d93057a.tar.gz
rust-63b314c367ad37ca7fdd40d7d3678ad42d93057a.zip
Rollup merge of #72810 - RalfJung:mir-terminate-sanity, r=jonas-schievink
validate basic sanity for TerminatorKind

r? @jonas-schievink

This mainly checks that all `BasicBlock` actually exist. On top of that, it checks that `Call` actually calls something of `FnPtr`/`FnDef` type, and `Assert` has to work on a `bool`. Also `SwitchInt` cannot have an empty target list.
Diffstat (limited to 'src')
-rw-r--r--src/librustc_mir/interpret/terminator.rs8
-rw-r--r--src/librustc_mir/transform/validate.rs135
2 files changed, 127 insertions, 16 deletions
diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs
index b048240ca8d..3db16a71bab 100644
--- a/src/librustc_mir/interpret/terminator.rs
+++ b/src/librustc_mir/interpret/terminator.rs
@@ -50,7 +50,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 self.go_to_block(target_block);
             }
 
-            Call { ref func, ref args, destination, ref cleanup, .. } => {
+            Call {
+                ref func,
+                ref args,
+                destination,
+                ref cleanup,
+                from_hir_call: _from_hir_call,
+            } => {
                 let old_stack = self.frame_idx();
                 let old_loc = self.frame().loc;
                 let func = self.eval_operand(func, None)?;
diff --git a/src/librustc_mir/transform/validate.rs b/src/librustc_mir/transform/validate.rs
index a25edd131ba..7d301b2f496 100644
--- a/src/librustc_mir/transform/validate.rs
+++ b/src/librustc_mir/transform/validate.rs
@@ -3,10 +3,13 @@
 use super::{MirPass, MirSource};
 use rustc_middle::mir::visit::Visitor;
 use rustc_middle::{
-    mir::{Body, Location, Operand, Rvalue, Statement, StatementKind},
-    ty::{ParamEnv, TyCtxt},
+    mir::{
+        BasicBlock, Body, Location, Operand, Rvalue, Statement, StatementKind, Terminator,
+        TerminatorKind,
+    },
+    ty::{self, ParamEnv, TyCtxt},
 };
-use rustc_span::{def_id::DefId, Span, DUMMY_SP};
+use rustc_span::def_id::DefId;
 
 pub struct Validator {
     /// Describes at which point in the pipeline this validation is happening.
@@ -30,14 +33,27 @@ struct TypeChecker<'a, 'tcx> {
 }
 
 impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
-    fn fail(&self, span: Span, msg: impl AsRef<str>) {
+    fn fail(&self, location: Location, msg: impl AsRef<str>) {
+        let span = self.body.source_info(location).span;
         // We use `delay_span_bug` as we might see broken MIR when other errors have already
         // occurred.
         self.tcx.sess.diagnostic().delay_span_bug(
             span,
-            &format!("broken MIR in {:?} ({}): {}", self.def_id, self.when, msg.as_ref()),
+            &format!(
+                "broken MIR in {:?} ({}) at {:?}:\n{}",
+                self.def_id,
+                self.when,
+                location,
+                msg.as_ref()
+            ),
         );
     }
+
+    fn check_bb(&self, location: Location, bb: BasicBlock) {
+        if self.body.basic_blocks().get(bb).is_none() {
+            self.fail(location, format!("encountered jump to invalid basic block {:?}", bb))
+        }
+    }
 }
 
 impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
@@ -45,12 +61,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
         // `Operand::Copy` is only supposed to be used with `Copy` types.
         if let Operand::Copy(place) = operand {
             let ty = place.ty(&self.body.local_decls, self.tcx).ty;
+            let span = self.body.source_info(location).span;
 
-            if !ty.is_copy_modulo_regions(self.tcx, self.param_env, DUMMY_SP) {
-                self.fail(
-                    DUMMY_SP,
-                    format!("`Operand::Copy` with non-`Copy` type {} at {:?}", ty, location),
-                );
+            if !ty.is_copy_modulo_regions(self.tcx, self.param_env, span) {
+                self.fail(location, format!("`Operand::Copy` with non-`Copy` type {}", ty));
             }
         }
 
@@ -65,11 +79,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                 Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) => {
                     if dest == src {
                         self.fail(
-                            DUMMY_SP,
-                            format!(
-                                "encountered `Assign` statement with overlapping memory at {:?}",
-                                location
-                            ),
+                            location,
+                            "encountered `Assign` statement with overlapping memory",
                         );
                     }
                 }
@@ -77,4 +88,98 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
             }
         }
     }
+
+    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
+        match &terminator.kind {
+            TerminatorKind::Goto { target } => {
+                self.check_bb(location, *target);
+            }
+            TerminatorKind::SwitchInt { targets, values, .. } => {
+                if targets.len() != values.len() + 1 {
+                    self.fail(
+                        location,
+                        format!(
+                            "encountered `SwitchInt` terminator with {} values, but {} targets (should be values+1)",
+                            values.len(),
+                            targets.len(),
+                        ),
+                    );
+                }
+                for target in targets {
+                    self.check_bb(location, *target);
+                }
+            }
+            TerminatorKind::Drop { target, unwind, .. } => {
+                self.check_bb(location, *target);
+                if let Some(unwind) = unwind {
+                    self.check_bb(location, *unwind);
+                }
+            }
+            TerminatorKind::DropAndReplace { target, unwind, .. } => {
+                self.check_bb(location, *target);
+                if let Some(unwind) = unwind {
+                    self.check_bb(location, *unwind);
+                }
+            }
+            TerminatorKind::Call { func, destination, cleanup, .. } => {
+                let func_ty = func.ty(&self.body.local_decls, self.tcx);
+                match func_ty.kind {
+                    ty::FnPtr(..) | ty::FnDef(..) => {}
+                    _ => self.fail(
+                        location,
+                        format!("encountered non-callable type {} in `Call` terminator", func_ty),
+                    ),
+                }
+                if let Some((_, target)) = destination {
+                    self.check_bb(location, *target);
+                }
+                if let Some(cleanup) = cleanup {
+                    self.check_bb(location, *cleanup);
+                }
+            }
+            TerminatorKind::Assert { cond, target, cleanup, .. } => {
+                let cond_ty = cond.ty(&self.body.local_decls, self.tcx);
+                if cond_ty != self.tcx.types.bool {
+                    self.fail(
+                        location,
+                        format!(
+                            "encountered non-boolean condition of type {} in `Assert` terminator",
+                            cond_ty
+                        ),
+                    );
+                }
+                self.check_bb(location, *target);
+                if let Some(cleanup) = cleanup {
+                    self.check_bb(location, *cleanup);
+                }
+            }
+            TerminatorKind::Yield { resume, drop, .. } => {
+                self.check_bb(location, *resume);
+                if let Some(drop) = drop {
+                    self.check_bb(location, *drop);
+                }
+            }
+            TerminatorKind::FalseEdges { real_target, imaginary_target } => {
+                self.check_bb(location, *real_target);
+                self.check_bb(location, *imaginary_target);
+            }
+            TerminatorKind::FalseUnwind { real_target, unwind } => {
+                self.check_bb(location, *real_target);
+                if let Some(unwind) = unwind {
+                    self.check_bb(location, *unwind);
+                }
+            }
+            TerminatorKind::InlineAsm { destination, .. } => {
+                if let Some(destination) = destination {
+                    self.check_bb(location, *destination);
+                }
+            }
+            // Nothing to validate for these.
+            TerminatorKind::Resume
+            | TerminatorKind::Abort
+            | TerminatorKind::Return
+            | TerminatorKind::Unreachable
+            | TerminatorKind::GeneratorDrop => {}
+        }
+    }
 }