diff --git a/src/librustc/hir/map/blocks.rs b/src/librustc/hir/map/blocks.rs index 661798a825056..1b7eb1585671e 100644 --- a/src/librustc/hir/map/blocks.rs +++ b/src/librustc/hir/map/blocks.rs @@ -192,6 +192,18 @@ impl<'a> FnLikeNode<'a> { } } + pub fn unsafety(self) -> ast::Unsafety { + match self.kind() { + FnKind::ItemFn(_, _, unsafety, ..) => { + unsafety + } + FnKind::Method(_, m, ..) => { + m.unsafety + } + _ => ast::Unsafety::Normal + } + } + pub fn kind(self) -> FnKind<'a> { let item = |p: ItemFnParts<'a>| -> FnKind<'a> { FnKind::ItemFn(p.name, p.generics, p.unsafety, p.constness, p.abi, p.vis, p.attrs) diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs index a3a133daa09c4..85d9745246f68 100644 --- a/src/librustc/hir/mod.rs +++ b/src/librustc/hir/mod.rs @@ -49,7 +49,7 @@ use rustc_data_structures::indexed_vec; use std::collections::BTreeMap; use std::fmt; -/// HIR doesn't commit to a concrete storage type and have its own alias for a vector. +/// HIR doesn't commit to a concrete storage type and has its own alias for a vector. /// It can be `Vec`, `P<[T]>` or potentially `Box<[T]>`, or some other container with similar /// behavior. Unlike AST, HIR is mostly a static structure, so we can use an owned slice instead /// of `Vec` to avoid keeping extra capacity. @@ -76,14 +76,14 @@ pub mod pat_util; pub mod print; pub mod svh; -/// A HirId uniquely identifies a node in the HIR of then current crate. It is +/// A HirId uniquely identifies a node in the HIR of the current crate. It is /// composed of the `owner`, which is the DefIndex of the directly enclosing /// hir::Item, hir::TraitItem, or hir::ImplItem (i.e. the closest "item-like"), /// and the `local_id` which is unique within the given owner. /// /// This two-level structure makes for more stable values: One can move an item /// around within the source code, or add or remove stuff before it, without -/// the local_id part of the HirId changing, which is a very useful property +/// the local_id part of the HirId changing, which is a very useful property in /// incremental compilation where we have to persist things through changes to /// the code base. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, @@ -684,6 +684,16 @@ pub enum Mutability { MutImmutable, } +impl Mutability { + /// Return MutMutable only if both arguments are mutable. + pub fn and(self, other: Self) -> Self { + match self { + MutMutable => other, + MutImmutable => MutImmutable, + } + } +} + #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] pub enum BinOp_ { /// The `+` operator (addition) diff --git a/src/librustc/ich/impls_mir.rs b/src/librustc/ich/impls_mir.rs index 6dadb702b9f24..c20864183f47a 100644 --- a/src/librustc/ich/impls_mir.rs +++ b/src/librustc/ich/impls_mir.rs @@ -226,8 +226,12 @@ for mir::StatementKind<'tcx> { mir::StatementKind::StorageDead(ref lvalue) => { lvalue.hash_stable(hcx, hasher); } - mir::StatementKind::EndRegion(ref extents) => { - extents.hash_stable(hcx, hasher); + mir::StatementKind::EndRegion(ref extent) => { + extent.hash_stable(hcx, hasher); + } + mir::StatementKind::Validate(ref op, ref lvalues) => { + op.hash_stable(hcx, hasher); + lvalues.hash_stable(hcx, hasher); } mir::StatementKind::Nop => {} mir::StatementKind::InlineAsm { ref asm, ref outputs, ref inputs } => { @@ -239,6 +243,23 @@ for mir::StatementKind<'tcx> { } } +impl<'a, 'gcx, 'tcx, T> HashStable> + for mir::ValidationOperand<'tcx, T> + where T: HashStable> +{ + fn hash_stable(&self, + hcx: &mut StableHashingContext<'a, 'gcx, 'tcx>, + hasher: &mut StableHasher) + { + self.lval.hash_stable(hcx, hasher); + self.ty.hash_stable(hcx, hasher); + self.re.hash_stable(hcx, hasher); + self.mutbl.hash_stable(hcx, hasher); + } +} + +impl_stable_hash_for!(enum mir::ValidationOp { Acquire, Release, Suspend(extent) }); + impl<'a, 'gcx, 'tcx> HashStable> for mir::Lvalue<'tcx> { fn hash_stable(&self, hcx: &mut StableHashingContext<'a, 'gcx, 'tcx>, diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 3dcd64af2ede0..1e8dda0addf4c 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -25,7 +25,7 @@ use ty::{self, AdtDef, ClosureSubsts, Region, Ty}; use ty::fold::{TypeFoldable, TypeFolder, TypeVisitor}; use util::ppaux; use rustc_back::slice; -use hir::InlineAsm; +use hir::{self, InlineAsm}; use std::ascii; use std::borrow::{Cow}; use std::cell::Ref; @@ -818,12 +818,18 @@ pub enum StatementKind<'tcx> { /// End the current live range for the storage of the local. StorageDead(Lvalue<'tcx>), + /// Execute a piece of inline Assembly. InlineAsm { asm: Box, outputs: Vec>, inputs: Vec> }, + /// Assert the given lvalues to be valid inhabitants of their type. These statements are + /// currently only interpreted by miri and only generated when "-Z mir-emit-validate" is passed. + /// See for more details. + Validate(ValidationOp, Vec>>), + /// Mark one terminating point of an extent (i.e. static region). /// (The starting point(s) arise implicitly from borrows.) EndRegion(CodeExtent), @@ -832,6 +838,57 @@ pub enum StatementKind<'tcx> { Nop, } +/// The `ValidationOp` describes what happens with each of the operands of a +/// `Validate` statement. +#[derive(Copy, Clone, RustcEncodable, RustcDecodable, PartialEq, Eq)] +pub enum ValidationOp { + /// Recursively traverse the lvalue following the type and validate that all type + /// invariants are maintained. Furthermore, acquire exclusive/read-only access to the + /// memory reachable from the lvalue. + Acquire, + /// Recursive traverse the *mutable* part of the type and relinquish all exclusive + /// access. + Release, + /// Recursive traverse the *mutable* part of the type and relinquish all exclusive + /// access *until* the given region ends. Then, access will be recovered. + Suspend(CodeExtent), +} + +impl Debug for ValidationOp { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + use self::ValidationOp::*; + match *self { + Acquire => write!(fmt, "Acquire"), + Release => write!(fmt, "Release"), + // (reuse lifetime rendering policy from ppaux.) + Suspend(ref ce) => write!(fmt, "Suspend({})", ty::ReScope(*ce)), + } + } +} + +// This is generic so that it can be reused by miri +#[derive(Clone, RustcEncodable, RustcDecodable)] +pub struct ValidationOperand<'tcx, T> { + pub lval: T, + pub ty: Ty<'tcx>, + pub re: Option, + pub mutbl: hir::Mutability, +} + +impl<'tcx, T: Debug> Debug for ValidationOperand<'tcx, T> { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "{:?}: {:?}", self.lval, self.ty)?; + if let Some(ce) = self.re { + // (reuse lifetime rendering policy from ppaux.) + write!(fmt, "/{}", ty::ReScope(ce))?; + } + if let hir::MutImmutable = self.mutbl { + write!(fmt, " (imm)")?; + } + Ok(()) + } +} + impl<'tcx> Debug for Statement<'tcx> { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { use self::StatementKind::*; @@ -839,6 +896,7 @@ impl<'tcx> Debug for Statement<'tcx> { Assign(ref lv, ref rv) => write!(fmt, "{:?} = {:?}", lv, rv), // (reuse lifetime rendering policy from ppaux.) EndRegion(ref ce) => write!(fmt, "EndRegion({})", ty::ReScope(*ce)), + Validate(ref op, ref lvalues) => write!(fmt, "Validate({:?}, {:?})", op, lvalues), StorageLive(ref lv) => write!(fmt, "StorageLive({:?})", lv), StorageDead(ref lv) => write!(fmt, "StorageDead({:?})", lv), SetDiscriminant{lvalue: ref lv, variant_index: index} => { @@ -1481,6 +1539,21 @@ impl<'tcx> TypeFoldable<'tcx> for BasicBlockData<'tcx> { } } +impl<'tcx> TypeFoldable<'tcx> for ValidationOperand<'tcx, Lvalue<'tcx>> { + fn super_fold_with<'gcx: 'tcx, F: TypeFolder<'gcx, 'tcx>>(&self, folder: &mut F) -> Self { + ValidationOperand { + lval: self.lval.fold_with(folder), + ty: self.ty.fold_with(folder), + re: self.re, + mutbl: self.mutbl, + } + } + + fn super_visit_with>(&self, visitor: &mut V) -> bool { + self.lval.visit_with(visitor) || self.ty.visit_with(visitor) + } +} + impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> { fn super_fold_with<'gcx: 'tcx, F: TypeFolder<'gcx, 'tcx>>(&self, folder: &mut F) -> Self { use mir::StatementKind::*; @@ -1505,6 +1578,10 @@ impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> { // trait with a `fn fold_extent`. EndRegion(ref extent) => EndRegion(extent.clone()), + Validate(ref op, ref lvals) => + Validate(op.clone(), + lvals.iter().map(|operand| operand.fold_with(folder)).collect()), + Nop => Nop, }; Statement { @@ -1530,6 +1607,9 @@ impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> { // trait with a `fn visit_extent`. EndRegion(ref _extent) => false, + Validate(ref _op, ref lvalues) => + lvalues.iter().any(|ty_and_lvalue| ty_and_lvalue.visit_with(visitor)), + Nop => false, } } diff --git a/src/librustc/mir/visit.rs b/src/librustc/mir/visit.rs index fd3a9f8cd2d9a..a05007503cefb 100644 --- a/src/librustc/mir/visit.rs +++ b/src/librustc/mir/visit.rs @@ -333,6 +333,13 @@ macro_rules! make_mir_visitor { self.visit_assign(block, lvalue, rvalue, location); } StatementKind::EndRegion(_) => {} + StatementKind::Validate(_, ref $($mutability)* lvalues) => { + for operand in lvalues { + self.visit_lvalue(& $($mutability)* operand.lval, + LvalueContext::Validate, location); + self.visit_ty(& $($mutability)* operand.ty, Lookup::Loc(location)); + } + } StatementKind::SetDiscriminant{ ref $($mutability)* lvalue, .. } => { self.visit_lvalue(lvalue, LvalueContext::Store, location); } @@ -784,6 +791,9 @@ pub enum LvalueContext<'tcx> { // Starting and ending a storage live range StorageLive, StorageDead, + + // Validation command + Validate, } impl<'tcx> LvalueContext<'tcx> { @@ -830,7 +840,8 @@ impl<'tcx> LvalueContext<'tcx> { LvalueContext::Borrow { kind: BorrowKind::Shared, .. } | LvalueContext::Borrow { kind: BorrowKind::Unique, .. } | LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume | - LvalueContext::StorageLive | LvalueContext::StorageDead => false, + LvalueContext::StorageLive | LvalueContext::StorageDead | + LvalueContext::Validate => false, } } @@ -842,7 +853,8 @@ impl<'tcx> LvalueContext<'tcx> { LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume => true, LvalueContext::Borrow { kind: BorrowKind::Mut, .. } | LvalueContext::Store | LvalueContext::Call | LvalueContext::Projection(Mutability::Mut) | - LvalueContext::Drop | LvalueContext::StorageLive | LvalueContext::StorageDead => false, + LvalueContext::Drop | LvalueContext::StorageLive | LvalueContext::StorageDead | + LvalueContext::Validate => false, } } diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 8b55eb4c099ae..c8b9412c5663d 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1025,6 +1025,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "the directory the MIR is dumped into"), dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], "if set, exclude the pass number when dumping MIR (used in tests)"), + mir_emit_validate: usize = (0, parse_uint, [TRACKED], + "emit Validate MIR statements, interpreted e.g. by miri (0: do not emit; 1: if function \ + contains unsafe block, only validate arguments; 2: always emit full validation)"), perf_stats: bool = (false, parse_bool, [UNTRACKED], "print some performance-related statistics"), hir_stats: bool = (false, parse_bool, [UNTRACKED], diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index c592882a1e43b..2b667d83e35e5 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -933,6 +933,8 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session, passes.push_pass(MIR_CONST, mir::transform::type_check::TypeckMir); passes.push_pass(MIR_CONST, mir::transform::rustc_peek::SanityCheck); + // We compute "constant qualifications" betwen MIR_CONST and MIR_VALIDATED. + // What we need to run borrowck etc. passes.push_pass(MIR_VALIDATED, mir::transform::qualify_consts::QualifyAndPromoteConstants); passes.push_pass(MIR_VALIDATED, @@ -940,18 +942,23 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session, passes.push_pass(MIR_VALIDATED, mir::transform::simplify::SimplifyCfg::new("qualify-consts")); passes.push_pass(MIR_VALIDATED, mir::transform::nll::NLL); - // Optimizations begin. - passes.push_pass(MIR_OPTIMIZED, mir::transform::no_landing_pads::NoLandingPads); - passes.push_pass(MIR_OPTIMIZED, mir::transform::simplify::SimplifyCfg::new("no-landing-pads")); + // borrowck runs between MIR_VALIDATED and MIR_OPTIMIZED. - // From here on out, regions are gone. - passes.push_pass(MIR_OPTIMIZED, mir::transform::erase_regions::EraseRegions); + // These next passes must be executed together + passes.push_pass(MIR_OPTIMIZED, mir::transform::no_landing_pads::NoLandingPads); passes.push_pass(MIR_OPTIMIZED, mir::transform::add_call_guards::AddCallGuards); passes.push_pass(MIR_OPTIMIZED, mir::transform::elaborate_drops::ElaborateDrops); passes.push_pass(MIR_OPTIMIZED, mir::transform::no_landing_pads::NoLandingPads); passes.push_pass(MIR_OPTIMIZED, mir::transform::simplify::SimplifyCfg::new("elaborate-drops")); - // No lifetime analysis based on borrowing can be done from here on out. + + // AddValidation needs to run after ElaborateDrops and before EraseRegions. + passes.push_pass(MIR_OPTIMIZED, mir::transform::add_validation::AddValidation); + + // From here on out, regions are gone. + passes.push_pass(MIR_OPTIMIZED, mir::transform::erase_regions::EraseRegions); + + // Optimizations begin. passes.push_pass(MIR_OPTIMIZED, mir::transform::inline::Inline); passes.push_pass(MIR_OPTIMIZED, mir::transform::instcombine::InstCombine); passes.push_pass(MIR_OPTIMIZED, mir::transform::deaggregator::Deaggregator); diff --git a/src/librustc_mir/dataflow/drop_flag_effects.rs b/src/librustc_mir/dataflow/drop_flag_effects.rs index daafbecc5dfa3..24d5aa9e46bf2 100644 --- a/src/librustc_mir/dataflow/drop_flag_effects.rs +++ b/src/librustc_mir/dataflow/drop_flag_effects.rs @@ -289,6 +289,7 @@ pub(crate) fn drop_flag_effects_for_location<'a, 'tcx, F>( mir::StatementKind::StorageDead(_) | mir::StatementKind::InlineAsm { .. } | mir::StatementKind::EndRegion(_) | + mir::StatementKind::Validate(..) | mir::StatementKind::Nop => {} }, None => { diff --git a/src/librustc_mir/dataflow/impls/mod.rs b/src/librustc_mir/dataflow/impls/mod.rs index 97c996dea68f6..d5bdc71a705c5 100644 --- a/src/librustc_mir/dataflow/impls/mod.rs +++ b/src/librustc_mir/dataflow/impls/mod.rs @@ -486,6 +486,7 @@ impl<'a, 'tcx> BitDenotation for MovingOutStatements<'a, 'tcx> { mir::StatementKind::StorageDead(_) | mir::StatementKind::InlineAsm { .. } | mir::StatementKind::EndRegion(_) | + mir::StatementKind::Validate(..) | mir::StatementKind::Nop => {} } } diff --git a/src/librustc_mir/dataflow/move_paths/mod.rs b/src/librustc_mir/dataflow/move_paths/mod.rs index fbf977b98f901..c2945d4659271 100644 --- a/src/librustc_mir/dataflow/move_paths/mod.rs +++ b/src/librustc_mir/dataflow/move_paths/mod.rs @@ -416,6 +416,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { } StatementKind::InlineAsm { .. } | StatementKind::EndRegion(_) | + StatementKind::Validate(..) | StatementKind::Nop => {} } } diff --git a/src/librustc_mir/transform/add_validation.rs b/src/librustc_mir/transform/add_validation.rs new file mode 100644 index 0000000000000..52c2eaa7cb632 --- /dev/null +++ b/src/librustc_mir/transform/add_validation.rs @@ -0,0 +1,390 @@ +// Copyright 2015 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 adds validation calls (AcquireValid, ReleaseValid) where appropriate. +//! It has to be run really early, before transformations like inlining, because +//! introducing these calls *adds* UB -- so, conceptually, this pass is actually part +//! of MIR building, and only after this pass we think of the program has having the +//! normal MIR semantics. + +use rustc::ty::{self, TyCtxt, RegionKind}; +use rustc::hir; +use rustc::mir::*; +use rustc::mir::transform::{MirPass, MirSource}; +use rustc::middle::region::CodeExtent; + +pub struct AddValidation; + +/// Determine the "context" of the lval: Mutability and region. +fn lval_context<'a, 'tcx, D>( + lval: &Lvalue<'tcx>, + local_decls: &D, + tcx: TyCtxt<'a, 'tcx, 'tcx> +) -> (Option, hir::Mutability) + where D: HasLocalDecls<'tcx> +{ + use rustc::mir::Lvalue::*; + + match *lval { + Local { .. } => (None, hir::MutMutable), + Static(_) => (None, hir::MutImmutable), + Projection(ref proj) => { + match proj.elem { + ProjectionElem::Deref => { + // Computing the inside the recursion makes this quadratic. + // We don't expect deep paths though. + let ty = proj.base.ty(local_decls, tcx).to_ty(tcx); + // A Deref projection may restrict the context, this depends on the type + // being deref'd. + let context = match ty.sty { + ty::TyRef(re, tam) => { + let re = match re { + &RegionKind::ReScope(ce) => Some(ce), + &RegionKind::ReErased => + bug!("AddValidation pass must be run before erasing lifetimes"), + _ => None + }; + (re, tam.mutbl) + } + ty::TyRawPtr(_) => + // There is no guarantee behind even a mutable raw pointer, + // no write locks are acquired there, so we also don't want to + // release any. + (None, hir::MutImmutable), + ty::TyAdt(adt, _) if adt.is_box() => (None, hir::MutMutable), + _ => bug!("Deref on a non-pointer type {:?}", ty), + }; + // "Intersect" this restriction with proj.base. + if let (Some(_), hir::MutImmutable) = context { + // This is already as restricted as it gets, no need to even recurse + context + } else { + let base_context = lval_context(&proj.base, local_decls, tcx); + // The region of the outermost Deref is always most restrictive. + let re = context.0.or(base_context.0); + let mutbl = context.1.and(base_context.1); + (re, mutbl) + } + + } + _ => lval_context(&proj.base, local_decls, tcx), + } + } + } +} + +/// Check if this function contains an unsafe block or is an unsafe function. +fn fn_contains_unsafe<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, src: MirSource) -> bool { + use rustc::hir::intravisit::{self, Visitor, FnKind}; + use rustc::hir::map::blocks::FnLikeNode; + use rustc::hir::map::Node; + + /// Decide if this is an unsafe block + fn block_is_unsafe(block: &hir::Block) -> bool { + use rustc::hir::BlockCheckMode::*; + + match block.rules { + UnsafeBlock(_) | PushUnsafeBlock(_) => true, + // For PopUnsafeBlock, we don't actually know -- but we will always also check all + // parent blocks, so we can safely declare the PopUnsafeBlock to not be unsafe. + DefaultBlock | PopUnsafeBlock(_) => false, + } + } + + /// Decide if this FnLike is a closure + fn fn_is_closure<'a>(fn_like: FnLikeNode<'a>) -> bool { + match fn_like.kind() { + FnKind::Closure(_) => true, + FnKind::Method(..) | FnKind::ItemFn(..) => false, + } + } + + let fn_like = match src { + MirSource::Fn(node_id) => { + match FnLikeNode::from_node(tcx.hir.get(node_id)) { + Some(fn_like) => fn_like, + None => return false, // e.g. struct ctor shims -- such auto-generated code cannot + // contain unsafe. + } + }, + _ => return false, // only functions can have unsafe + }; + + // Test if the function is marked unsafe. + if fn_like.unsafety() == hir::Unsafety::Unsafe { + return true; + } + + // For closures, we need to walk up the parents and see if we are inside an unsafe fn or + // unsafe block. + if fn_is_closure(fn_like) { + let mut cur = fn_like.id(); + loop { + // Go further upwards. + cur = tcx.hir.get_parent_node(cur); + let node = tcx.hir.get(cur); + // Check if this is an unsafe function + if let Some(fn_like) = FnLikeNode::from_node(node) { + if !fn_is_closure(fn_like) { + if fn_like.unsafety() == hir::Unsafety::Unsafe { + return true; + } + } + } + // Check if this is an unsafe block, or an item + match node { + Node::NodeExpr(&hir::Expr { node: hir::ExprBlock(ref block), ..}) => { + if block_is_unsafe(&*block) { + // Found an unsafe block, we can bail out here. + return true; + } + } + Node::NodeItem(..) => { + // No walking up beyond items. This makes sure the loop always terminates. + break; + } + _ => {}, + } + } + } + + // Visit the entire body of the function and check for unsafe blocks in there + struct FindUnsafe { + found_unsafe: bool, + } + let mut finder = FindUnsafe { found_unsafe: false }; + // Run the visitor on the NodeId we got. Seems like there is no uniform way to do that. + finder.visit_body(tcx.hir.body(fn_like.body())); + + impl<'tcx> Visitor<'tcx> for FindUnsafe { + fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> { + intravisit::NestedVisitorMap::None + } + + fn visit_block(&mut self, b: &'tcx hir::Block) { + if self.found_unsafe { return; } // short-circuit + + if block_is_unsafe(b) { + // We found an unsafe block. We can stop searching. + self.found_unsafe = true; + } else { + // No unsafe block here, go on searching. + intravisit::walk_block(self, b); + } + } + } + + finder.found_unsafe +} + +impl MirPass for AddValidation { + fn run_pass<'a, 'tcx>(&self, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + src: MirSource, + mir: &mut Mir<'tcx>) + { + let emit_validate = tcx.sess.opts.debugging_opts.mir_emit_validate; + if emit_validate == 0 { + return; + } + let restricted_validation = emit_validate == 1 && fn_contains_unsafe(tcx, src); + let local_decls = mir.local_decls.clone(); // FIXME: Find a way to get rid of this clone. + + // Convert an lvalue to a validation operand. + let lval_to_operand = |lval: Lvalue<'tcx>| -> ValidationOperand<'tcx, Lvalue<'tcx>> { + let (re, mutbl) = lval_context(&lval, &local_decls, tcx); + let ty = lval.ty(&local_decls, tcx).to_ty(tcx); + ValidationOperand { lval, ty, re, mutbl } + }; + + // Emit an Acquire at the beginning of the given block. If we are in restricted emission + // mode (mir_emit_validate=1), also emit a Release immediately after the Acquire. + let emit_acquire = |block: &mut BasicBlockData<'tcx>, source_info, operands: Vec<_>| { + if operands.len() == 0 { + return; // Nothing to do + } + // Emit the release first, to avoid cloning if we do not emit it + if restricted_validation { + let release_stmt = Statement { + source_info, + kind: StatementKind::Validate(ValidationOp::Release, operands.clone()), + }; + block.statements.insert(0, release_stmt); + } + // Now, the acquire + let acquire_stmt = Statement { + source_info, + kind: StatementKind::Validate(ValidationOp::Acquire, operands), + }; + block.statements.insert(0, acquire_stmt); + }; + + // PART 1 + // Add an AcquireValid at the beginning of the start block. + { + let source_info = SourceInfo { + scope: ARGUMENT_VISIBILITY_SCOPE, + span: mir.span, // FIXME: Consider using just the span covering the function + // argument declaration. + }; + // Gather all arguments, skip return value. + let operands = mir.local_decls.iter_enumerated().skip(1).take(mir.arg_count) + .map(|(local, _)| lval_to_operand(Lvalue::Local(local))).collect(); + emit_acquire(&mut mir.basic_blocks_mut()[START_BLOCK], source_info, operands); + } + + // PART 2 + // Add ReleaseValid/AcquireValid around function call terminators. We don't use a visitor + // because we need to access the block that a Call jumps to. + let mut returns : Vec<(SourceInfo, Lvalue<'tcx>, BasicBlock)> = Vec::new(); + for block_data in mir.basic_blocks_mut() { + match block_data.terminator { + Some(Terminator { kind: TerminatorKind::Call { ref args, ref destination, .. }, + source_info }) => { + // Before the call: Release all arguments *and* the return value. + // The callee may write into the return value! Note that this relies + // on "release of uninitialized" to be a NOP. + if !restricted_validation { + let release_stmt = Statement { + source_info, + kind: StatementKind::Validate(ValidationOp::Release, + destination.iter().map(|dest| lval_to_operand(dest.0.clone())) + .chain( + args.iter().filter_map(|op| { + match op { + &Operand::Consume(ref lval) => + Some(lval_to_operand(lval.clone())), + &Operand::Constant(..) => { None }, + } + }) + ).collect()) + }; + block_data.statements.push(release_stmt); + } + // Remember the return destination for later + if let &Some(ref destination) = destination { + returns.push((source_info, destination.0.clone(), destination.1)); + } + } + Some(Terminator { kind: TerminatorKind::Drop { location: ref lval, .. }, + source_info }) | + Some(Terminator { kind: TerminatorKind::DropAndReplace { location: ref lval, .. }, + source_info }) => { + // Before the call: Release all arguments + if !restricted_validation { + let release_stmt = Statement { + source_info, + kind: StatementKind::Validate(ValidationOp::Release, + vec![lval_to_operand(lval.clone())]), + }; + block_data.statements.push(release_stmt); + } + // drop doesn't return anything, so we need no acquire. + } + _ => { + // Not a block ending in a Call -> ignore. + } + } + } + // Now we go over the returns we collected to acquire the return values. + for (source_info, dest_lval, dest_block) in returns { + emit_acquire( + &mut mir.basic_blocks_mut()[dest_block], + source_info, + vec![lval_to_operand(dest_lval)] + ); + } + + if restricted_validation { + // No part 3 for us. + return; + } + + // PART 3 + // Add ReleaseValid/AcquireValid around Ref and Cast. Again an iterator does not seem very + // suited as we need to add new statements before and after each Ref. + for block_data in mir.basic_blocks_mut() { + // We want to insert statements around Ref commands as we iterate. To this end, we + // iterate backwards using indices. + for i in (0..block_data.statements.len()).rev() { + match block_data.statements[i].kind { + // When the borrow of this ref expires, we need to recover validation. + StatementKind::Assign(_, Rvalue::Ref(_, _, _)) => { + // Due to a lack of NLL; we can't capture anything directly here. + // Instead, we have to re-match and clone there. + let (dest_lval, re, src_lval) = match block_data.statements[i].kind { + StatementKind::Assign(ref dest_lval, + Rvalue::Ref(re, _, ref src_lval)) => { + (dest_lval.clone(), re, src_lval.clone()) + }, + _ => bug!("We already matched this."), + }; + // So this is a ref, and we got all the data we wanted. + // Do an acquire of the result -- but only what it points to, so add a Deref + // projection. + let dest_lval = Projection { base: dest_lval, elem: ProjectionElem::Deref }; + let dest_lval = Lvalue::Projection(Box::new(dest_lval)); + let acquire_stmt = Statement { + source_info: block_data.statements[i].source_info, + kind: StatementKind::Validate(ValidationOp::Acquire, + vec![lval_to_operand(dest_lval)]), + }; + block_data.statements.insert(i+1, acquire_stmt); + + // The source is released until the region of the borrow ends. + let op = match re { + &RegionKind::ReScope(ce) => ValidationOp::Suspend(ce), + &RegionKind::ReErased => + bug!("AddValidation pass must be run before erasing lifetimes"), + _ => ValidationOp::Release, + }; + let release_stmt = Statement { + source_info: block_data.statements[i].source_info, + kind: StatementKind::Validate(op, vec![lval_to_operand(src_lval)]), + }; + block_data.statements.insert(i, release_stmt); + } + // Casts can change what validation does (e.g. unsizing) + StatementKind::Assign(_, Rvalue::Cast(kind, Operand::Consume(_), _)) + if kind != CastKind::Misc => + { + // Due to a lack of NLL; we can't capture anything directly here. + // Instead, we have to re-match and clone there. + let (dest_lval, src_lval) = match block_data.statements[i].kind { + StatementKind::Assign(ref dest_lval, + Rvalue::Cast(_, Operand::Consume(ref src_lval), _)) => + { + (dest_lval.clone(), src_lval.clone()) + }, + _ => bug!("We already matched this."), + }; + + // Acquire of the result + let acquire_stmt = Statement { + source_info: block_data.statements[i].source_info, + kind: StatementKind::Validate(ValidationOp::Acquire, + vec![lval_to_operand(dest_lval)]), + }; + block_data.statements.insert(i+1, acquire_stmt); + + // Release of the input + let release_stmt = Statement { + source_info: block_data.statements[i].source_info, + kind: StatementKind::Validate(ValidationOp::Release, + vec![lval_to_operand(src_lval)]), + }; + block_data.statements.insert(i, release_stmt); + } + _ => {}, + } + } + } + } +} diff --git a/src/librustc_mir/transform/clean_end_regions.rs b/src/librustc_mir/transform/clean_end_regions.rs index 36125f9454365..f06b88551d11d 100644 --- a/src/librustc_mir/transform/clean_end_regions.rs +++ b/src/librustc_mir/transform/clean_end_regions.rs @@ -24,8 +24,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc::middle::region::CodeExtent; use rustc::mir::transform::{MirPass, MirSource}; use rustc::mir::{BasicBlock, Location, Mir, Rvalue, Statement, StatementKind}; -use rustc::mir::visit::{MutVisitor, Visitor}; -use rustc::ty::{RegionKind, TyCtxt}; +use rustc::mir::visit::{MutVisitor, Visitor, Lookup}; +use rustc::ty::{Ty, RegionKind, TyCtxt}; pub struct CleanEndRegions; @@ -42,7 +42,9 @@ impl MirPass for CleanEndRegions { _tcx: TyCtxt<'a, 'tcx, 'tcx>, _source: MirSource, mir: &mut Mir<'tcx>) { - let mut gather = GatherBorrowedRegions { seen_regions: FxHashSet() }; + let mut gather = GatherBorrowedRegions { + seen_regions: FxHashSet() + }; gather.visit_mir(mir); let mut delete = DeleteTrivialEndRegions { seen_regions: &mut gather.seen_regions }; @@ -54,6 +56,7 @@ impl<'tcx> Visitor<'tcx> for GatherBorrowedRegions { fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + // Gather regions that are used for borrows if let Rvalue::Ref(r, _, _) = *rvalue { if let RegionKind::ReScope(ce) = *r { self.seen_regions.insert(ce); @@ -61,6 +64,17 @@ impl<'tcx> Visitor<'tcx> for GatherBorrowedRegions { } self.super_rvalue(rvalue, location); } + + fn visit_ty(&mut self, ty: &Ty<'tcx>, _: Lookup) { + // Gather regions that occur in types + for re in ty.walk().flat_map(|t| t.regions()) { + match *re { + RegionKind::ReScope(ce) => { self.seen_regions.insert(ce); } + _ => {}, + } + } + self.super_ty(ty); + } } impl<'a, 'tcx> MutVisitor<'tcx> for DeleteTrivialEndRegions<'a> { diff --git a/src/librustc_mir/transform/erase_regions.rs b/src/librustc_mir/transform/erase_regions.rs index da9032685e03b..baf0522896c9c 100644 --- a/src/librustc_mir/transform/erase_regions.rs +++ b/src/librustc_mir/transform/erase_regions.rs @@ -11,6 +11,8 @@ //! This pass erases all early-bound regions from the types occuring in the MIR. //! We want to do this once just before trans, so trans does not have to take //! care erasing regions all over the place. +//! NOTE: We do NOT erase regions of statements that are relevant for +//! "types-as-contracts"-validation, namely, AcquireValid, ReleaseValid, and EndRegion. use rustc::ty::subst::Substs; use rustc::ty::{Ty, TyCtxt, ClosureSubsts}; @@ -20,20 +22,24 @@ use rustc::mir::transform::{MirPass, MirSource}; struct EraseRegionsVisitor<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, + in_validation_statement: bool, } impl<'a, 'tcx> EraseRegionsVisitor<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self { EraseRegionsVisitor { - tcx: tcx + tcx: tcx, + in_validation_statement: false, } } } impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> { fn visit_ty(&mut self, ty: &mut Ty<'tcx>, _: Lookup) { - let old_ty = *ty; - *ty = self.tcx.erase_regions(&old_ty); + if !self.in_validation_statement { + *ty = self.tcx.erase_regions(&{*ty}); + } + self.super_ty(ty); } fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, _: Location) { @@ -71,10 +77,20 @@ impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> { block: BasicBlock, statement: &mut Statement<'tcx>, location: Location) { - if let StatementKind::EndRegion(_) = statement.kind { - statement.kind = StatementKind::Nop; + // Do NOT delete EndRegion if validation statements are emitted. + // Validation needs EndRegion. + if self.tcx.sess.opts.debugging_opts.mir_emit_validate == 0 { + if let StatementKind::EndRegion(_) = statement.kind { + statement.kind = StatementKind::Nop; + } } + + self.in_validation_statement = match statement.kind { + StatementKind::Validate(..) => true, + _ => false, + }; self.super_statement(block, statement, location); + self.in_validation_statement = false; } } diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index c9c8ad0e0eb63..a247ce2231e76 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -24,6 +24,7 @@ use syntax::ast; use syntax_pos::{DUMMY_SP, Span}; use transform; +pub mod add_validation; pub mod clean_end_regions; pub mod simplify_branches; pub mod simplify; diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 9bb0f07aa68ac..9d01f8294e4fa 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -908,6 +908,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { StatementKind::StorageDead(_) | StatementKind::InlineAsm {..} | StatementKind::EndRegion(_) | + StatementKind::Validate(..) | StatementKind::Nop => {} } }); diff --git a/src/librustc_mir/transform/rustc_peek.rs b/src/librustc_mir/transform/rustc_peek.rs index 5918de0c68811..268e7a4c185b0 100644 --- a/src/librustc_mir/transform/rustc_peek.rs +++ b/src/librustc_mir/transform/rustc_peek.rs @@ -161,6 +161,7 @@ fn each_block<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>, mir::StatementKind::StorageDead(_) | mir::StatementKind::InlineAsm { .. } | mir::StatementKind::EndRegion(_) | + mir::StatementKind::Validate(..) | mir::StatementKind::Nop => continue, mir::StatementKind::SetDiscriminant{ .. } => span_bug!(stmt.source_info.span, diff --git a/src/librustc_mir/transform/type_check.rs b/src/librustc_mir/transform/type_check.rs index 7e6fccf30192c..1c7899a46d1d7 100644 --- a/src/librustc_mir/transform/type_check.rs +++ b/src/librustc_mir/transform/type_check.rs @@ -414,6 +414,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { } StatementKind::InlineAsm { .. } | StatementKind::EndRegion(_) | + StatementKind::Validate(..) | StatementKind::Nop => {} } } diff --git a/src/librustc_passes/mir_stats.rs b/src/librustc_passes/mir_stats.rs index 9895802700ef7..d5e477ff0c784 100644 --- a/src/librustc_passes/mir_stats.rs +++ b/src/librustc_passes/mir_stats.rs @@ -126,6 +126,7 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> { self.record(match statement.kind { StatementKind::Assign(..) => "StatementKind::Assign", StatementKind::EndRegion(..) => "StatementKind::EndRegion", + StatementKind::Validate(..) => "StatementKind::Validate", StatementKind::SetDiscriminant { .. } => "StatementKind::SetDiscriminant", StatementKind::StorageLive(..) => "StatementKind::StorageLive", StatementKind::StorageDead(..) => "StatementKind::StorageDead", diff --git a/src/librustc_trans/mir/analyze.rs b/src/librustc_trans/mir/analyze.rs index 45afcf51b5203..598af1cda91d4 100644 --- a/src/librustc_trans/mir/analyze.rs +++ b/src/librustc_trans/mir/analyze.rs @@ -158,6 +158,7 @@ impl<'mir, 'a, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'a, 'tcx> { LvalueContext::StorageLive | LvalueContext::StorageDead | + LvalueContext::Validate | LvalueContext::Inspect | LvalueContext::Consume => {} diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index 98e774a29877d..c90382673a4a2 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -285,6 +285,7 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> { } mir::StatementKind::StorageLive(_) | mir::StatementKind::StorageDead(_) | + mir::StatementKind::Validate(..) | mir::StatementKind::EndRegion(_) | mir::StatementKind::Nop => {} mir::StatementKind::InlineAsm { .. } | diff --git a/src/librustc_trans/mir/statement.rs b/src/librustc_trans/mir/statement.rs index 170a76a49497b..52dfc8dc4de5c 100644 --- a/src/librustc_trans/mir/statement.rs +++ b/src/librustc_trans/mir/statement.rs @@ -87,6 +87,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { bcx } mir::StatementKind::EndRegion(_) | + mir::StatementKind::Validate(..) | mir::StatementKind::Nop => bcx, } } diff --git a/src/test/mir-opt/README.md b/src/test/mir-opt/README.md index 28a124e3c61c8..d999ff9755160 100644 --- a/src/test/mir-opt/README.md +++ b/src/test/mir-opt/README.md @@ -57,13 +57,6 @@ the lines being too long. compiletest handles dumping the MIR before and after every pass for you. The test writer only has to specify the file names of the dumped files (not the -full path to the file) and what lines to expect. I added an option to rustc +full path to the file) and what lines to expect. There is an option to rustc that tells it to dump the mir into some directly (rather then always dumping to -the current directory). - -Lines match ignoring whitespace, and the prefix "//" is removed of course. - -It also currently strips trailing comments -- partly because the full file path -in "scope comments" is unpredictable and partly because tidy complains about -the lines being too long. - +the current directory). diff --git a/src/test/mir-opt/validate_1.rs b/src/test/mir-opt/validate_1.rs new file mode 100644 index 0000000000000..9ac76a5f4ea61 --- /dev/null +++ b/src/test/mir-opt/validate_1.rs @@ -0,0 +1,59 @@ +// Copyright 2017 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. + +// ignore-tidy-linelength +// compile-flags: -Z verbose -Z mir-emit-validate=1 + +struct Test(i32); + +impl Test { + // Make sure we run the pass on a method, not just on bare functions. + fn foo(&self, _x: &mut i32) {} +} + +fn main() { + let mut x = 0; + Test(0).foo(&mut x); + + // Also test closures + let c = |x: &mut i32| { let y = &*x; *y }; + c(&mut x); +} + +// FIXME: Also test code generated inside the closure, make sure it has validation. Unfortunately, +// the interesting lines of code also contain name of the source file, so we cannot test for it. + +// END RUST SOURCE +// START rustc.node12.EraseRegions.after.mir +// bb0: { +// Validate(Acquire, [_1: &ReFree(DefId { krate: CrateNum(0), node: DefIndex(5) => validate_1/8cd878b::{{impl}}[0]::foo[0] }, BrAnon(0)) Test, _2: &ReFree(DefId { krate: CrateNum(0), node: DefIndex(5) => validate_1/8cd878b::{{impl}}[0]::foo[0] }, BrAnon(1)) mut i32]); +// return; +// } +// END rustc.node12.EraseRegions.after.mir +// START rustc.node23.EraseRegions.after.mir +// fn main() -> () { +// bb0: { +// Validate(Suspend(ReScope(Misc(NodeId(34)))), [_1: i32]); +// _6 = &ReErased mut _1; +// Validate(Acquire, [(*_6): i32/ReScope(Misc(NodeId(34)))]); +// Validate(Suspend(ReScope(Misc(NodeId(34)))), [(*_6): i32/ReScope(Misc(NodeId(34)))]); +// _5 = &ReErased mut (*_6); +// Validate(Acquire, [(*_5): i32/ReScope(Misc(NodeId(34)))]); +// Validate(Release, [_2: (), _3: &ReScope(Misc(NodeId(34))) Test, _5: &ReScope(Misc(NodeId(34))) mut i32]); +// _2 = const Test::foo(_3, _5) -> bb1; +// } +// +// bb1: { +// Validate(Acquire, [_2: ()]); +// EndRegion(ReScope(Misc(NodeId(34)))); +// return; +// } +// } +// END rustc.node23.EraseRegions.after.mir diff --git a/src/test/mir-opt/validate_2.rs b/src/test/mir-opt/validate_2.rs new file mode 100644 index 0000000000000..37ebd720d52da --- /dev/null +++ b/src/test/mir-opt/validate_2.rs @@ -0,0 +1,27 @@ +// Copyright 2017 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. + +// ignore-tidy-linelength +// compile-flags: -Z verbose -Z mir-emit-validate=1 + +fn main() { + let _x : Box<[i32]> = Box::new([1, 2, 3]); +} + +// END RUST SOURCE +// START rustc.node4.EraseRegions.after.mir +// fn main() -> () { +// bb1: { +// Validate(Release, [_2: std::boxed::Box<[i32; 3]>]); +// _1 = _2 as std::boxed::Box<[i32]> (Unsize); +// Validate(Acquire, [_1: std::boxed::Box<[i32]>]); +// } +// } +// END rustc.node4.EraseRegions.after.mir diff --git a/src/test/mir-opt/validate_3.rs b/src/test/mir-opt/validate_3.rs new file mode 100644 index 0000000000000..9140cf5768f59 --- /dev/null +++ b/src/test/mir-opt/validate_3.rs @@ -0,0 +1,50 @@ +// Copyright 2017 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. + +// ignore-tidy-linelength +// compile-flags: -Z verbose -Z mir-emit-validate=1 + +struct Test { + x: i32 +} + +fn foo(_x: &i32) {} + +fn main() { + // These internal unsafe functions should have no effect on the code generation. + unsafe fn _unused1() {} + fn _unused2(x: *const i32) -> i32 { unsafe { *x }} + + let t = Test { x: 0 }; + let t = &t; + foo(&t.x); +} + +// END RUST SOURCE +// START rustc.node16.EraseRegions.after.mir +// fn main() -> () { +// let mut _5: &ReErased i32; +// bb0: { +// Validate(Suspend(ReScope(Misc(NodeId(46)))), [((*_2).0: i32): i32/ReScope(Remainder(BlockRemainder { block: NodeId(18), first_statement_index: 3 })) (imm)]); +// _5 = &ReErased ((*_2).0: i32); +// Validate(Acquire, [(*_5): i32/ReScope(Misc(NodeId(46))) (imm)]); +// Validate(Suspend(ReScope(Misc(NodeId(46)))), [(*_5): i32/ReScope(Misc(NodeId(46))) (imm)]); +// _4 = &ReErased (*_5); +// Validate(Acquire, [(*_4): i32/ReScope(Misc(NodeId(46))) (imm)]); +// Validate(Release, [_3: (), _4: &ReScope(Misc(NodeId(46))) i32]); +// _3 = const foo(_4) -> bb1; +// } +// bb1: { +// EndRegion(ReScope(Misc(NodeId(46)))); +// EndRegion(ReScope(Remainder(BlockRemainder { block: NodeId(18), first_statement_index: 3 }))); +// return; +// } +// } +// END rustc.node16.EraseRegions.after.mir diff --git a/src/test/mir-opt/validate_4.rs b/src/test/mir-opt/validate_4.rs new file mode 100644 index 0000000000000..591de975740f9 --- /dev/null +++ b/src/test/mir-opt/validate_4.rs @@ -0,0 +1,60 @@ +// Copyright 2017 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. + +// ignore-tidy-linelength +// compile-flags: -Z verbose -Z mir-emit-validate=1 + +// Make sure unsafe fns and fns with an unsafe block only get restricted validation. + +unsafe fn write_42(x: *mut i32) -> bool { + let test_closure = |x: *mut i32| *x = 23; + test_closure(x); + *x = 42; + true +} + +fn test(x: &mut i32) { + unsafe { write_42(x) }; +} + +fn main() { + test(&mut 0); + + let test_closure = unsafe { |x: &mut i32| write_42(x) }; + test_closure(&mut 0); +} + +// FIXME: Also test code generated inside the closure, make sure it only does restricted validation +// because it is entirely inside an unsafe block. Unfortunately, the interesting lines of code also +// contain name of the source file, so we cannot test for it. + +// END RUST SOURCE +// START rustc.node4.EraseRegions.after.mir +// fn write_42(_1: *mut i32) -> bool { +// bb0: { +// Validate(Acquire, [_1: *mut i32]); +// Validate(Release, [_1: *mut i32]); +// return; +// } +// } +// END rustc.node4.EraseRegions.after.mir +// START rustc.node31.EraseRegions.after.mir +// fn test(_1: &ReErased mut i32) -> () { +// bb0: { +// Validate(Acquire, [_1: &ReFree(DefId { krate: CrateNum(0), node: DefIndex(4) => validate_4/8cd878b::test[0] }, BrAnon(0)) mut i32]); +// Validate(Release, [_1: &ReFree(DefId { krate: CrateNum(0), node: DefIndex(4) => validate_4/8cd878b::test[0] }, BrAnon(0)) mut i32]); +// _3 = const write_42(_4) -> bb1; +// } +// bb1: { +// Validate(Acquire, [_3: bool]); +// Validate(Release, [_3: bool]); +// } +// } +// END rustc.node31.EraseRegions.after.mir diff --git a/src/test/mir-opt/validate_5.rs b/src/test/mir-opt/validate_5.rs new file mode 100644 index 0000000000000..e9919af9fd3a6 --- /dev/null +++ b/src/test/mir-opt/validate_5.rs @@ -0,0 +1,44 @@ +// Copyright 2017 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. + +// ignore-tidy-linelength +// compile-flags: -Z verbose -Z mir-emit-validate=2 + +// Make sure unsafe fns and fns with an unsafe block only get full validation. + +unsafe fn write_42(x: *mut i32) -> bool { + *x = 42; + true +} + +fn test(x: &mut i32) { + unsafe { write_42(x) }; +} + +fn main() { + test(&mut 0); + + let test_closure = unsafe { |x: &mut i32| write_42(x) }; + test_closure(&mut 0); +} + +// FIXME: Also test code generated inside the closure, make sure it has validation. Unfortunately, +// the interesting lines of code also contain name of the source file, so we cannot test for it. + +// END RUST SOURCE +// START rustc.node17.EraseRegions.after.mir +// fn test(_1: &ReErased mut i32) -> () { +// bb0: { +// Validate(Acquire, [_1: &ReFree(DefId { krate: CrateNum(0), node: DefIndex(4) => validate_5/8cd878b::test[0] }, BrAnon(0)) mut i32]); +// Validate(Release, [_3: bool, _4: *mut i32]); +// _3 = const write_42(_4) -> bb1; +// } +// } +// END rustc.node17.EraseRegions.after.mir