diff --git a/src/librustc/mir/repr.rs b/src/librustc/mir/repr.rs index 783c58469a1b..2f24a1a8962d 100644 --- a/src/librustc/mir/repr.rs +++ b/src/librustc/mir/repr.rs @@ -45,6 +45,9 @@ pub struct Mir<'tcx> { /// values in that it is possible to borrow them and mutate them /// through the resulting reference. pub temp_decls: Vec>, + + /// A span representing this MIR, for error reporting + pub span: Span, } /// where execution begins @@ -145,7 +148,7 @@ pub enum BorrowKind { /// A "variable" is a binding declared by the user as part of the fn /// decl, a let, etc. -#[derive(Clone, RustcEncodable, RustcDecodable)] +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] pub struct VarDecl<'tcx> { pub mutability: Mutability, pub name: Name, @@ -154,7 +157,7 @@ pub struct VarDecl<'tcx> { /// A "temp" is a temporary that we place on the stack. They are /// anonymous, always mutable, and have only a type. -#[derive(Clone, RustcEncodable, RustcDecodable)] +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] pub struct TempDecl<'tcx> { pub ty: Ty<'tcx>, } @@ -170,7 +173,7 @@ pub struct TempDecl<'tcx> { /// /// there is only one argument, of type `(i32, u32)`, but two bindings /// (`x` and `y`). -#[derive(Clone, RustcEncodable, RustcDecodable)] +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] pub struct ArgDecl<'tcx> { pub ty: Ty<'tcx>, } diff --git a/src/librustc/mir/tcx.rs b/src/librustc/mir/tcx.rs index 45cc0b8b413e..e8b83962b521 100644 --- a/src/librustc/mir/tcx.rs +++ b/src/librustc/mir/tcx.rs @@ -14,7 +14,8 @@ */ use mir::repr::*; -use middle::subst::Substs; +use middle::const_eval::ConstVal; +use middle::subst::{Subst, Substs}; use middle::ty::{self, AdtDef, Ty}; use rustc_front::hir; @@ -150,6 +151,73 @@ impl<'tcx> Mir<'tcx> { self.lvalue_ty(tcx, &proj.base).projection_ty(tcx, &proj.elem) } } + + pub fn rvalue_ty(&self, + tcx: &ty::ctxt<'tcx>, + rvalue: &Rvalue<'tcx>) + -> Option> + { + match *rvalue { + Rvalue::Use(ref operand) => Some(self.operand_ty(tcx, operand)), + Rvalue::Repeat(ref operand, ref count) => { + if let ConstVal::Uint(u) = count.value { + let op_ty = self.operand_ty(tcx, operand); + Some(tcx.mk_array(op_ty, u as usize)) + } else { + None + } + } + Rvalue::Ref(reg, bk, ref lv) => { + let lv_ty = self.lvalue_ty(tcx, lv).to_ty(tcx); + Some(tcx.mk_ref( + tcx.mk_region(reg), + ty::TypeAndMut { + ty: lv_ty, + mutbl: bk.to_mutbl_lossy() + } + )) + } + Rvalue::Len(..) => Some(tcx.types.usize), + Rvalue::Cast(_, _, ty) => Some(ty), + Rvalue::BinaryOp(op, ref lhs, ref rhs) => { + let lhs_ty = self.operand_ty(tcx, lhs); + let rhs_ty = self.operand_ty(tcx, rhs); + Some(self.binop_ty(tcx, op, lhs_ty, rhs_ty)) + } + Rvalue::UnaryOp(_, ref operand) => { + Some(self.operand_ty(tcx, operand)) + } + Rvalue::Box(t) => { + Some(tcx.mk_box(t)) + } + Rvalue::Aggregate(ref ak, ref ops) => { + match *ak { + AggregateKind::Vec => { + if let Some(operand) = ops.get(0) { + let ty = self.operand_ty(tcx, operand); + Some(tcx.mk_array(ty, ops.len())) + } else { + None + } + } + AggregateKind::Tuple => { + Some(tcx.mk_tup( + ops.iter().map(|op| self.operand_ty(tcx, op)).collect() + )) + } + AggregateKind::Adt(def, _, substs) => { + Some(def.type_scheme(tcx).ty.subst(tcx, substs)) + } + AggregateKind::Closure(did, substs) => { + Some(tcx.mk_closure_from_closure_substs( + did, Box::new(substs.clone()))) + } + } + } + Rvalue::Slice { .. } => None, + Rvalue::InlineAsm(..) => None + } + } } impl BorrowKind { diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs index 86b348581892..b83d0f6a9803 100644 --- a/src/librustc_mir/build/mod.rs +++ b/src/librustc_mir/build/mod.rs @@ -80,7 +80,7 @@ macro_rules! unpack { /// the main entry point for building MIR for a function pub fn construct<'a,'tcx>(hir: Cx<'a,'tcx>, - _span: Span, + span: Span, implicit_arguments: Vec>, explicit_arguments: Vec<(Ty<'tcx>, &'tcx hir::Pat)>, argument_extent: CodeExtent, @@ -97,7 +97,7 @@ pub fn construct<'a,'tcx>(hir: Cx<'a,'tcx>, temp_decls: vec![], var_decls: vec![], var_indices: FnvHashMap(), - unit_temp: None + unit_temp: None, }; assert_eq!(builder.cfg.start_new_block(), START_BLOCK); @@ -119,6 +119,7 @@ pub fn construct<'a,'tcx>(hir: Cx<'a,'tcx>, arg_decls: arg_decls, temp_decls: builder.temp_decls, return_ty: return_ty, + span: span } } diff --git a/src/librustc_mir/mir_map.rs b/src/librustc_mir/mir_map.rs index 5b887db7ec08..7a2b90fbbb31 100644 --- a/src/librustc_mir/mir_map.rs +++ b/src/librustc_mir/mir_map.rs @@ -22,7 +22,7 @@ extern crate rustc_front; use build; use graphviz; use pretty; -use transform::{simplify_cfg, no_landing_pads}; +use transform::{simplify_cfg, type_check, no_landing_pads}; use rustc::dep_graph::DepNode; use rustc::mir::repr::Mir; use hair::cx::Cx; @@ -148,8 +148,9 @@ impl<'a, 'm, 'tcx> Visitor<'tcx> for InnerDump<'a,'m,'tcx> { match build_mir(Cx::new(&infcx), implicit_arg_tys, id, span, decl, body) { Ok(mut mir) => { - no_landing_pads::NoLandingPads.run_on_mir(&mut mir, self.tcx); simplify_cfg::SimplifyCfg::new().run_on_mir(&mut mir, self.tcx); + type_check::TypeckMir::new(&infcx).run_on_mir(&mut mir, self.tcx); + no_landing_pads::NoLandingPads.run_on_mir(&mut mir, self.tcx); let meta_item_list = self.attr .iter() diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 017cf3607161..64e188a3b558 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -11,4 +11,5 @@ pub mod simplify_cfg; pub mod erase_regions; pub mod no_landing_pads; +pub mod type_check; mod util; diff --git a/src/librustc_mir/transform/type_check.rs b/src/librustc_mir/transform/type_check.rs new file mode 100644 index 000000000000..3a891543e801 --- /dev/null +++ b/src/librustc_mir/transform/type_check.rs @@ -0,0 +1,341 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This pass type-checks the MIR to ensure it is not broken. + +use rustc::middle::infer; +use rustc::middle::ty::{self, Ty}; +use rustc::middle::ty::fold::TypeFoldable; +use rustc::mir::repr::*; +use rustc::mir::transform::MirPass; +use rustc::mir::visit::{self, Visitor}; + +use syntax::codemap::{Span, DUMMY_SP}; +use std::fmt; + +macro_rules! span_mirbug { + ($context:expr, $elem:expr, $($message:tt)*) => ({ + $context.tcx().sess.span_warn( + $context.last_span, + &format!("broken MIR ({:?}): {:?}", $elem, format!($($message)*)) + ) + }) +} + +/// Verifies that MIR types are sane to not crash further +/// checks. +struct TypeVerifier<'a, 'tcx: 'a> { + infcx: &'a infer::InferCtxt<'a, 'tcx>, + mir: &'a Mir<'tcx>, + last_span: Span, + errors_reported: bool +} + +impl<'a, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'tcx> { + fn visit_span(&mut self, span: &Span) { + if *span != DUMMY_SP { + self.last_span = *span; + } + } + + fn visit_lvalue(&mut self, lvalue: &Lvalue<'tcx>, context: visit::LvalueContext) { + self.super_lvalue(lvalue, context); + let lv_ty = self.mir.lvalue_ty(self.tcx(), lvalue).to_ty(self.tcx()); + self.sanitize_type(lvalue, lv_ty); + } + + fn visit_constant(&mut self, constant: &Constant<'tcx>) { + self.super_constant(constant); + self.sanitize_type(constant, constant.ty); + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>) { + self.super_rvalue(rvalue); + if let Some(ty) = self.mir.rvalue_ty(self.tcx(), rvalue) { + self.sanitize_type(rvalue, ty); + } + } + + fn visit_mir(&mut self, mir: &Mir<'tcx>) { + if let ty::FnConverging(t) = mir.return_ty { + self.sanitize_type(&"return type", t); + } + for var_decl in &mir.var_decls { + self.sanitize_type(var_decl, var_decl.ty); + } + for (n, arg_decl) in mir.arg_decls.iter().enumerate() { + self.sanitize_type(&(n, arg_decl), arg_decl.ty); + } + for (n, tmp_decl) in mir.temp_decls.iter().enumerate() { + self.sanitize_type(&(n, tmp_decl), tmp_decl.ty); + } + self.super_mir(mir); + } +} + +impl<'a, 'tcx> TypeVerifier<'a, 'tcx> { + fn new(infcx: &'a infer::InferCtxt<'a, 'tcx>, mir: &'a Mir<'tcx>) -> Self { + TypeVerifier { + infcx: infcx, + mir: mir, + last_span: mir.span, + errors_reported: false + } + } + + fn tcx(&self) -> &'a ty::ctxt<'tcx> { + self.infcx.tcx + } + + fn sanitize_type(&mut self, parent: &fmt::Debug, ty: Ty<'tcx>) { + if !(ty.needs_infer() || ty.has_escaping_regions()) { + return; + } + span_mirbug!(self, parent, "bad type {:?}", ty); + self.errors_reported = true; + } +} + +pub struct TypeckMir<'a, 'tcx: 'a> { + infcx: &'a infer::InferCtxt<'a, 'tcx>, + last_span: Span +} + +impl<'a, 'tcx> TypeckMir<'a, 'tcx> { + pub fn new(infcx: &'a infer::InferCtxt<'a, 'tcx>) -> Self { + TypeckMir { + infcx: infcx, + last_span: DUMMY_SP + } + } + + fn tcx(&self) -> &'a ty::ctxt<'tcx> { + self.infcx.tcx + } + + fn check_stmt(&mut self, mir: &Mir<'tcx>, stmt: &Statement<'tcx>) { + debug!("check_stmt: {:?}", stmt); + let tcx = self.tcx(); + match stmt.kind { + StatementKind::Assign(ref lv, ref rv) => { + match lv { + &Lvalue::ReturnPointer if mir.return_ty == ty::FnDiverging => { + // HACK: buggy writes + return; + } + _ => {} + } + + let lv_ty = mir.lvalue_ty(tcx, lv).to_ty(tcx); + let rv_ty = mir.rvalue_ty(tcx, rv); + if let Some(rv_ty) = rv_ty { + if let Err(terr) = infer::can_mk_subty(self.infcx, rv_ty, lv_ty) { + span_mirbug!(self, stmt, "bad assignment ({:?} = {:?}): {:?}", + lv_ty, rv_ty, terr); + } + } + + // FIXME: rvalue with undeterminable type - e.g. inline + // asm. + } + } + } + + fn check_terminator(&self, + mir: &Mir<'tcx>, + term: &Terminator<'tcx>) { + debug!("check_terminator: {:?}", term); + let tcx = self.tcx(); + match *term { + Terminator::Goto { .. } | + Terminator::Resume | + Terminator::Return | + Terminator::Drop { .. } => {} + Terminator::If { ref cond, .. } => { + let cond_ty = mir.operand_ty(tcx, cond); + match cond_ty.sty { + ty::TyBool => {} + _ => { + span_mirbug!(self, term, "bad If ({:?}, not bool", cond_ty); + } + } + } + Terminator::SwitchInt { ref discr, switch_ty, .. } => { + let discr_ty = mir.lvalue_ty(tcx, discr).to_ty(tcx); + if let Err(terr) = infer::can_mk_subty(self.infcx, discr_ty, switch_ty) { + span_mirbug!(self, term, "bad SwitchInt ({:?} on {:?}): {:?}", + switch_ty, discr_ty, terr); + } + } + Terminator::Switch { ref discr, adt_def, .. } => { + let discr_ty = mir.lvalue_ty(tcx, discr).to_ty(tcx); + match discr_ty.sty { + ty::TyEnum(def, _) if def == adt_def => {}, + _ => { + span_mirbug!(self, term, "bad Switch ({:?} on {:?})", + adt_def, discr_ty); + } + } + } + Terminator::Call { ref func, ref args, ref destination, .. } => { + let func_ty = mir.operand_ty(tcx, func); + debug!("check_terminator: call, func_ty={:?}", func_ty); + let func_ty = match func_ty.sty { + ty::TyBareFn(_, func_ty) => func_ty, + _ => { + span_mirbug!(self, term, "call to non-function {:?}", func_ty); + return; + } + }; + let sig = tcx.erase_late_bound_regions(&func_ty.sig); + self.check_call_dest(mir, term, &sig, destination); + + if self.is_box_free(func) { + self.check_box_free_inputs(mir, term, &sig, args); + } else { + self.check_call_inputs(mir, term, &sig, args); + } + } + } + } + + fn check_call_dest(&self, + mir: &Mir<'tcx>, + term: &Terminator<'tcx>, + sig: &ty::FnSig<'tcx>, + destination: &Option<(Lvalue<'tcx>, BasicBlock)>) { + let tcx = self.tcx(); + match (destination, sig.output) { + (&Some(..), ty::FnDiverging) => { + span_mirbug!(self, term, "call to diverging function {:?} with dest", sig); + } + (&Some((ref dest, _)), ty::FnConverging(ty)) => { + let dest_ty = mir.lvalue_ty(tcx, dest).to_ty(tcx); + if let Err(terr) = infer::can_mk_subty(self.infcx, ty, dest_ty) { + span_mirbug!(self, term, + "call dest mismatch ({:?} <- {:?}): {:?}", + dest_ty, ty, terr); + } + } + (&None, ty::FnDiverging) => {} + (&None, ty::FnConverging(..)) => { + span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig); + } + } + } + + fn check_call_inputs(&self, + mir: &Mir<'tcx>, + term: &Terminator<'tcx>, + sig: &ty::FnSig<'tcx>, + args: &[Operand<'tcx>]) + { + debug!("check_call_inputs({:?}, {:?})", sig, args); + if sig.inputs.len() > args.len() || + (sig.inputs.len() < args.len() && !sig.variadic) { + span_mirbug!(self, term, "call to {:?} with wrong # of args", sig); + } + for (n, (fn_arg, op_arg)) in sig.inputs.iter().zip(args).enumerate() { + let op_arg_ty = mir.operand_ty(self.tcx(), op_arg); + if let Err(terr) = infer::can_mk_subty(self.infcx, op_arg_ty, fn_arg) { + span_mirbug!(self, term, "bad arg #{:?} ({:?} <- {:?}): {:?}", + n, fn_arg, op_arg_ty, terr); + } + } + } + + fn is_box_free(&self, operand: &Operand<'tcx>) -> bool { + match operand { + &Operand::Constant(Constant { + literal: Literal::Item { def_id, .. }, .. + }) => { + Some(def_id) == self.tcx().lang_items.box_free_fn() + } + _ => false, + } + } + + fn check_box_free_inputs(&self, + mir: &Mir<'tcx>, + term: &Terminator<'tcx>, + sig: &ty::FnSig<'tcx>, + args: &[Operand<'tcx>]) + { + debug!("check_box_free_inputs"); + + // box_free takes a Box as a pointer. Allow for that. + + if sig.inputs.len() != 1 { + span_mirbug!(self, term, "box_free should take 1 argument"); + return; + } + + let pointee_ty = match sig.inputs[0].sty { + ty::TyRawPtr(mt) => mt.ty, + _ => { + span_mirbug!(self, term, "box_free should take a raw ptr"); + return; + } + }; + + if args.len() != 1 { + span_mirbug!(self, term, "box_free called with wrong # of args"); + return; + } + + let arg_ty = match mir.operand_ty(self.tcx(), &args[0]).sty { + ty::TyRawPtr(mt) => mt.ty, + ty::TyBox(ty) => ty, + _ => { + span_mirbug!(self, term, "box_free called with bad arg ty"); + return; + } + }; + + if let Err(terr) = infer::can_mk_subty(self.infcx, arg_ty, pointee_ty) { + span_mirbug!(self, term, "bad box_free arg ({:?} <- {:?}): {:?}", + pointee_ty, arg_ty, terr); + } + } + + fn typeck_mir(&mut self, mir: &Mir<'tcx>) { + self.last_span = mir.span; + debug!("run_on_mir: {:?}", mir.span); + for block in &mir.basic_blocks { + for stmt in &block.statements { + if stmt.span != DUMMY_SP { + self.last_span = stmt.span; + } + self.check_stmt(mir, stmt); + } + + if let Some(ref terminator) = block.terminator { + self.check_terminator(mir, terminator); + } + } + } +} + +impl<'a, 'tcx> MirPass for TypeckMir<'a, 'tcx> { + fn run_on_mir<'tcx_>(&mut self, + mir: &mut Mir<'tcx_>, + _tcx: &ty::ctxt<'tcx_>) { + // FIXME: pass param_env to run_on_mir + let mir: &mut Mir<'tcx> = unsafe { ::std::mem::transmute(mir) }; + let mut type_verifier = TypeVerifier::new(self.infcx, mir); + type_verifier.visit_mir(mir); + + if type_verifier.errors_reported { + return; + } + + self.typeck_mir(mir); + } +}