Auto merge of #43403 - RalfJung:mir-validate, r=nikomatsakis

Add MIR Validate statement

This adds statements to MIR that express when types are to be validated (following [Types as Contracts](https://internals.rust-lang.org/t/types-as-contracts/5562)). Obviously nothing is stabilized, and in fact a `-Z` flag has to be passed for behavior to even change at all.

This is meant to make experimentation with Types as Contracts in miri possible. The design is definitely not final.

Cc @nikomatsakis @aturon
This commit is contained in:
bors 2017-08-04 07:48:07 +00:00
commit c523b3f954
27 changed files with 840 additions and 31 deletions

View File

@ -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)

View File

@ -48,7 +48,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.
@ -75,14 +75,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,
@ -701,6 +701,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)

View File

@ -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<StableHashingContext<'a, 'gcx, 'tcx>>
for mir::ValidationOperand<'tcx, T>
where T: HashStable<StableHashingContext<'a, 'gcx, 'tcx>>
{
fn hash_stable<W: StableHasherResult>(&self,
hcx: &mut StableHashingContext<'a, 'gcx, 'tcx>,
hasher: &mut StableHasher<W>)
{
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<StableHashingContext<'a, 'gcx, 'tcx>> for mir::Lvalue<'tcx> {
fn hash_stable<W: StableHasherResult>(&self,
hcx: &mut StableHashingContext<'a, 'gcx, 'tcx>,

View File

@ -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<InlineAsm>,
outputs: Vec<Lvalue<'tcx>>,
inputs: Vec<Operand<'tcx>>
},
/// 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 <https://internals.rust-lang.org/t/types-as-contracts/5562/73> for more details.
Validate(ValidationOp, Vec<ValidationOperand<'tcx, Lvalue<'tcx>>>),
/// 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<CodeExtent>,
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<V: TypeVisitor<'tcx>>(&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,
}
}

View File

@ -338,6 +338,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);
}
@ -789,6 +796,9 @@ pub enum LvalueContext<'tcx> {
// Starting and ending a storage live range
StorageLive,
StorageDead,
// Validation command
Validate,
}
impl<'tcx> LvalueContext<'tcx> {
@ -835,7 +845,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,
}
}
@ -847,7 +858,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,
}
}

View File

@ -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],

View File

@ -929,6 +929,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,
@ -936,18 +938,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);

View File

@ -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 => {

View File

@ -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 => {}
}
}

View File

@ -416,6 +416,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
}
StatementKind::InlineAsm { .. } |
StatementKind::EndRegion(_) |
StatementKind::Validate(..) |
StatementKind::Nop => {}
}
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<CodeExtent>, 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);
}
_ => {},
}
}
}
}
}

View File

@ -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> {

View File

@ -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;
}
}

View File

@ -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;

View File

@ -908,6 +908,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
StatementKind::StorageDead(_) |
StatementKind::InlineAsm {..} |
StatementKind::EndRegion(_) |
StatementKind::Validate(..) |
StatementKind::Nop => {}
}
});

View File

@ -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,

View File

@ -414,6 +414,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
StatementKind::InlineAsm { .. } |
StatementKind::EndRegion(_) |
StatementKind::Validate(..) |
StatementKind::Nop => {}
}
}

View File

@ -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",

View File

@ -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 => {}

View File

@ -293,6 +293,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 { .. } |

View File

@ -87,6 +87,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
bcx
}
mir::StatementKind::EndRegion(_) |
mir::StatementKind::Validate(..) |
mir::StatementKind::Nop => bcx,
}
}

View File

@ -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).

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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