Emit Retag statements, kill Validate statements

Also "rename" -Zmir-emit-validate to -Zmir-emit-retag, which is just a boolean (yes or no).
This commit is contained in:
Ralf Jung 2018-10-24 11:47:17 +02:00
parent 4e88b7363b
commit aafcf2c942
26 changed files with 233 additions and 543 deletions

View File

@ -254,7 +254,12 @@ fn main() {
// When running miri tests, we need to generate MIR for all libraries
if env::var("TEST_MIRI").ok().map_or(false, |val| val == "true") {
cmd.arg("-Zalways-encode-mir");
cmd.arg("-Zmir-emit-validate=1");
// These options are preferred by miri, to be able to perform better validation,
// but the bootstrap compiler might not understand them.
if stage != "0" {
cmd.arg("-Zmir-emit-retag");
cmd.arg("-Zmir-opt-level=0");
}
}
// Force all crates compiled by this compiler to (a) be unstable and (b)

View File

@ -257,9 +257,9 @@ for mir::StatementKind<'gcx> {
mir::StatementKind::EndRegion(ref region_scope) => {
region_scope.hash_stable(hcx, hasher);
}
mir::StatementKind::Validate(ref op, ref places) => {
op.hash_stable(hcx, hasher);
places.hash_stable(hcx, hasher);
mir::StatementKind::Retag { fn_entry, ref place } => {
fn_entry.hash_stable(hcx, hasher);
place.hash_stable(hcx, hasher);
}
mir::StatementKind::AscribeUserType(ref place, ref variance, ref c_ty) => {
place.hash_stable(hcx, hasher);
@ -278,23 +278,6 @@ for mir::StatementKind<'gcx> {
impl_stable_hash_for!(enum mir::FakeReadCause { ForMatchGuard, ForMatchedPlace, ForLet });
impl<'a, 'gcx, T> HashStable<StableHashingContext<'a>>
for mir::ValidationOperand<'gcx, T>
where T: HashStable<StableHashingContext<'a>>
{
fn hash_stable<W: StableHasherResult>(&self,
hcx: &mut StableHashingContext<'a>,
hasher: &mut StableHasher<W>)
{
self.place.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(region_scope) });
impl<'a, 'gcx> HashStable<StableHashingContext<'a>> for mir::Place<'gcx> {
fn hash_stable<W: StableHasherResult>(&self,
hcx: &mut StableHashingContext<'a>,

View File

@ -1754,10 +1754,13 @@ pub enum StatementKind<'tcx> {
inputs: Box<[Operand<'tcx>]>,
},
/// Assert the given places 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, Place<'tcx>>>),
/// Retag references in the given place, ensuring they got fresh tags. This is
/// part of the Stacked Borrows model. `fn_entry` indicates whether this
/// is the initial retag that happens in the function prolog. These statements are
/// currently only interpreted by miri and only generated when "-Z mir-emit-retag" is passed.
/// See <https://internals.rust-lang.org/t/stacked-borrows-an-aliasing-model-for-rust/8153/>
/// for more details.
Retag { fn_entry: bool, place: Place<'tcx> },
/// Mark one terminating point of a region scope (i.e. static region).
/// (The starting point(s) arise implicitly from borrows.)
@ -1810,57 +1813,6 @@ pub enum FakeReadCause {
ForLet,
}
/// 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 place following the type and validate that all type
/// invariants are maintained. Furthermore, acquire exclusive/read-only access to the
/// memory reachable from the place.
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(region::Scope),
}
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, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub struct ValidationOperand<'tcx, T> {
pub place: T,
pub ty: Ty<'tcx>,
pub re: Option<region::Scope>,
pub mutbl: hir::Mutability,
}
impl<'tcx, T: Debug> Debug for ValidationOperand<'tcx, T> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
write!(fmt, "{:?}: {:?}", self.place, 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::*;
@ -1869,7 +1821,8 @@ impl<'tcx> Debug for Statement<'tcx> {
FakeRead(ref cause, ref place) => write!(fmt, "FakeRead({:?}, {:?})", cause, place),
// (reuse lifetime rendering policy from ppaux.)
EndRegion(ref ce) => write!(fmt, "EndRegion({})", ty::ReScope(*ce)),
Validate(ref op, ref places) => write!(fmt, "Validate({:?}, {:?})", op, places),
Retag { fn_entry, ref place } =>
write!(fmt, "Retag({}{:?})", if fn_entry { "[fn entry]: " } else { "" }, place),
StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place),
StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place),
SetDiscriminant {
@ -2944,7 +2897,6 @@ CloneTypeFoldableAndLiftImpls! {
SourceInfo,
UpvarDecl,
FakeReadCause,
ValidationOp,
SourceScope,
SourceScopeData,
SourceScopeLocalData,
@ -2997,12 +2949,6 @@ BraceStructTypeFoldableImpl! {
}
}
BraceStructTypeFoldableImpl! {
impl<'tcx> TypeFoldable<'tcx> for ValidationOperand<'tcx, Place<'tcx>> {
place, ty, re, mutbl
}
}
BraceStructTypeFoldableImpl! {
impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> {
source_info, kind
@ -3017,7 +2963,7 @@ EnumTypeFoldableImpl! {
(StatementKind::StorageLive)(a),
(StatementKind::StorageDead)(a),
(StatementKind::InlineAsm) { asm, outputs, inputs },
(StatementKind::Validate)(a, b),
(StatementKind::Retag) { fn_entry, place },
(StatementKind::EndRegion)(a),
(StatementKind::AscribeUserType)(a, v, b),
(StatementKind::Nop),

View File

@ -371,16 +371,12 @@ macro_rules! make_mir_visitor {
);
}
StatementKind::EndRegion(_) => {}
StatementKind::Validate(_, ref $($mutability)* places) => {
for operand in places {
self.visit_place(
& $($mutability)* operand.place,
PlaceContext::NonUse(NonUseContext::Validate),
location
);
self.visit_ty(& $($mutability)* operand.ty,
TyContext::Location(location));
}
StatementKind::Retag { fn_entry: _, ref $($mutability)* place } => {
self.visit_place(
place,
PlaceContext::MutatingUse(MutatingUseContext::Retag),
location,
);
}
StatementKind::SetDiscriminant{ ref $($mutability)* place, .. } => {
self.visit_place(
@ -1010,6 +1006,8 @@ pub enum MutatingUseContext<'tcx> {
/// f(&mut x.y);
///
Projection,
/// Retagging (updating the "Stacked Borrows" tag)
Retag,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -1020,8 +1018,6 @@ pub enum NonUseContext {
StorageDead,
/// User type annotation assertions for NLL.
AscribeUserTy,
/// Validation command.
Validate,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

View File

@ -1282,9 +1282,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"in addition to `.mir` files, create graphviz `.dot` files"),
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)"),
mir_emit_retag: bool = (false, parse_bool, [TRACKED],
"emit Retagging MIR statements, interpreted e.g. by miri; implies -Zmir-opt-level=0"),
perf_stats: bool = (false, parse_bool, [UNTRACKED],
"print some performance-related statistics"),
hir_stats: bool = (false, parse_bool, [UNTRACKED],

View File

@ -1547,11 +1547,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
}
/// Should we emit EndRegion MIR statements? These are consumed by
/// MIR borrowck, but not when NLL is used. They are also consumed
/// by the validation stuff.
/// MIR borrowck, but not when NLL is used.
pub fn emit_end_regions(self) -> bool {
self.sess.opts.debugging_opts.emit_end_regions ||
self.sess.opts.debugging_opts.mir_emit_validate > 0 ||
self.use_mir_borrowck()
}

View File

@ -219,7 +219,8 @@ impl Visitor<'tcx> for LocalAnalyzer<'mir, 'a, 'll, 'tcx> {
self.assign(local, location);
}
PlaceContext::NonUse(_) => {}
PlaceContext::NonUse(_) |
PlaceContext::MutatingUse(MutatingUseContext::Retag) => {}
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {

View File

@ -109,7 +109,7 @@ impl FunctionCx<'a, 'll, 'tcx> {
}
mir::StatementKind::FakeRead(..) |
mir::StatementKind::EndRegion(_) |
mir::StatementKind::Validate(..) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => bx,
}

View File

@ -574,9 +574,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
}
StatementKind::Nop
| StatementKind::AscribeUserType(..)
| StatementKind::Validate(..)
| StatementKind::Retag { .. }
| StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Validate`, and `StorageLive` are irrelevant
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.
}
StatementKind::StorageDead(local) => {

View File

@ -136,9 +136,9 @@ impl<'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx, 'gcx> {
StatementKind::EndRegion(..) |
StatementKind::Nop |
StatementKind::AscribeUserType(..) |
StatementKind::Validate(..) |
StatementKind::Retag { .. } |
StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Validate`, and `StorageLive` are irrelevant
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.
}
StatementKind::StorageDead(local) => {

View File

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

View File

@ -338,7 +338,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Borrows<'a, 'gcx, 'tcx> {
mir::StatementKind::FakeRead(..) |
mir::StatementKind::SetDiscriminant { .. } |
mir::StatementKind::StorageLive(..) |
mir::StatementKind::Validate(..) |
mir::StatementKind::Retag { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => {}

View File

@ -302,7 +302,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
"SetDiscriminant should not exist during borrowck");
}
StatementKind::EndRegion(_) |
StatementKind::Validate(..) |
StatementKind::Retag { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {}
}

View File

@ -242,10 +242,10 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
/// Execute a validation operation
#[inline]
fn validation_op(
fn retag(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_op: ::rustc::mir::ValidationOp,
_operand: &::rustc::mir::ValidationOperand<'tcx, ::rustc::mir::Place<'tcx>>,
_fn_entry: bool,
_place: PlaceTy<'tcx, Self::PointerTag>,
) -> EvalResult<'tcx> {
Ok(())
}

View File

@ -118,11 +118,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// interpreter is solely intended for borrowck'ed code.
FakeRead(..) => {}
// Validity checks.
Validate(op, ref places) => {
for operand in places {
M::validation_op(self, op, operand)?;
}
// Retagging.
Retag { fn_entry, ref place } => {
let dest = self.eval_place(place)?;
M::retag(self, fn_entry, dest)?;
}
EndRegion(..) => {}

View File

@ -0,0 +1,169 @@
// 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, Ty, TyCtxt};
use rustc::mir::*;
use transform::{MirPass, MirSource};
pub struct AddRetag;
/// Determines whether this place is local: If it is part of a local variable.
/// We do not consider writes to pointers local, only writes that immediately assign
/// to a local variable.
/// One important property here is that evaluating the place immediately after
/// the assignment must produce the same place as what was used during the assignment.
fn is_local<'tcx>(
place: &Place<'tcx>,
) -> bool {
use rustc::mir::Place::*;
match *place {
Local { .. } => true,
Promoted(_) |
Static(_) => false,
Projection(ref proj) => {
match proj.elem {
ProjectionElem::Deref |
ProjectionElem::Index(_) =>
// Which place these point to depends on external circumstances
// (a local storing the array index, the current value of
// the projection base), so we stop tracking here.
false,
_ => is_local(&proj.base),
}
}
}
}
/// Determine whether this type has a reference in it, recursing below compound types but
/// not below references.
fn has_reference<'a, 'gcx, 'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> bool {
match ty.sty {
// Primitive types that are not references
ty::Bool | ty::Char |
ty::Float(_) | ty::Int(_) | ty::Uint(_) |
ty::RawPtr(..) | ty::FnPtr(..) |
ty::Str | ty::FnDef(..) | ty::Never =>
false,
// References
ty::Ref(..) => true,
ty::Adt(..) if ty.is_box() => true,
// Compound types
ty::Array(ty, ..) | ty::Slice(ty) =>
has_reference(ty, tcx),
ty::Tuple(tys) =>
tys.iter().any(|ty| has_reference(ty, tcx)),
ty::Adt(adt, substs) =>
adt.variants.iter().any(|v| v.fields.iter().any(|f|
has_reference(f.ty(tcx, substs), tcx)
)),
// Conservative fallback
_ => true,
}
}
impl MirPass for AddRetag {
fn run_pass<'a, 'tcx>(&self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
_src: MirSource,
mir: &mut Mir<'tcx>)
{
if !tcx.sess.opts.debugging_opts.mir_emit_retag {
return;
}
let (span, arg_count) = (mir.span, mir.arg_count);
let (basic_blocks, local_decls) = mir.basic_blocks_and_local_decls_mut();
let needs_retag = |place: &Place<'tcx>| {
is_local(place) && has_reference(place.ty(&*local_decls, tcx).to_ty(tcx), tcx)
};
// PART 1
// Retag arguments at the beginning of the start block.
{
let source_info = SourceInfo {
scope: OUTERMOST_SOURCE_SCOPE,
span: span, // FIXME: Consider using just the span covering the function
// argument declaration.
};
// Gather all arguments, skip return value.
let places = local_decls.iter_enumerated().skip(1).take(arg_count)
.map(|(local, _)| Place::Local(local))
.filter(needs_retag)
.collect::<Vec<_>>();
// Emit their retags.
basic_blocks[START_BLOCK].statements.splice(0..0,
places.into_iter().map(|place| Statement {
source_info,
kind: StatementKind::Retag { fn_entry: true, place },
})
);
}
// PART 2
// Retag return values of functions.
// We collect the return destinations because we cannot mutate while iterating.
let mut returns: Vec<(SourceInfo, Place<'tcx>, BasicBlock)> = Vec::new();
for block_data in basic_blocks.iter_mut() {
match block_data.terminator {
Some(Terminator { kind: TerminatorKind::Call { ref destination, .. },
source_info }) => {
// Remember the return destination for later
if let &Some(ref destination) = destination {
if needs_retag(&destination.0) {
returns.push((source_info, destination.0.clone(), destination.1));
}
}
}
_ => {
// Not a block ending in a Call -> ignore.
// `Drop` is also a call, but it doesn't return anything so we are good.
}
}
}
// Now we go over the returns we collected to retag the return values.
for (source_info, dest_place, dest_block) in returns {
basic_blocks[dest_block].statements.insert(0, Statement {
source_info,
kind: StatementKind::Retag { fn_entry: false, place: dest_place },
});
}
// PART 3
// Add retag after assignment.
for block_data in basic_blocks {
// We want to insert statements 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 {
// Assignments can make values obtained elsewhere "local".
// We could try to be smart here and e.g. only retag if the assignment
// loaded from memory, but that seems risky: We might miss a subtle corner
// case.
StatementKind::Assign(ref place, box Rvalue::Use(..))
if needs_retag(place) => {
// Insert a retag after the assignment.
let source_info = block_data.statements[i].source_info;
block_data.statements.insert(i+1,Statement {
source_info,
kind: StatementKind::Retag { fn_entry: false, place: place.clone() },
});
}
_ => {},
}
}
}
}
}

View File

@ -1,395 +0,0 @@
// 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::middle::region;
use transform::{MirPass, MirSource};
pub struct AddValidation;
/// Determine the "context" of the place: Mutability and region.
fn place_context<'a, 'tcx, D>(
place: &Place<'tcx>,
local_decls: &D,
tcx: TyCtxt<'a, 'tcx, 'tcx>
) -> (Option<region::Scope>, hir::Mutability)
where D: HasLocalDecls<'tcx>
{
use rustc::mir::Place::*;
match *place {
Local { .. } => (None, hir::MutMutable),
Promoted(_) |
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::Ref(re, _, mutbl) => {
let re = match re {
&RegionKind::ReScope(ce) => Some(ce),
&RegionKind::ReErased =>
bug!("AddValidation pass must be run before erasing lifetimes"),
_ => None
};
(re, mutbl)
}
ty::RawPtr(_) =>
// 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::Adt(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 = place_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)
}
}
_ => place_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::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 node_id = tcx.hir.as_local_node_id(src.def_id).unwrap();
let fn_like = match tcx.hir.body_owner_kind(node_id) {
hir::BodyOwnerKind::Fn => {
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::Expr(&hir::Expr { node: hir::ExprKind::Block(ref block, _), ..}) => {
if block_is_unsafe(&*block) {
// Found an unsafe block, we can bail out here.
return true;
}
}
Node::Item(..) => {
// 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 (span, arg_count) = (mir.span, mir.arg_count);
let (basic_blocks, local_decls) = mir.basic_blocks_and_local_decls_mut();
// Convert a place to a validation operand.
let place_to_operand = |place: Place<'tcx>| -> ValidationOperand<'tcx, Place<'tcx>> {
let (re, mutbl) = place_context(&place, local_decls, tcx);
let ty = place.ty(local_decls, tcx).to_ty(tcx);
ValidationOperand { place, 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: OUTERMOST_SOURCE_SCOPE,
span: span, // FIXME: Consider using just the span covering the function
// argument declaration.
};
// Gather all arguments, skip return value.
let operands = local_decls.iter_enumerated().skip(1).take(arg_count)
.map(|(local, _)| place_to_operand(Place::Local(local))).collect();
emit_acquire(&mut basic_blocks[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, Place<'tcx>, BasicBlock)> = Vec::new();
for block_data in basic_blocks.iter_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| place_to_operand(dest.0.clone()))
.chain(
args.iter().filter_map(|op| {
match op {
&Operand::Copy(ref place) |
&Operand::Move(ref place) =>
Some(place_to_operand(place.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 place, .. },
source_info }) |
Some(Terminator { kind: TerminatorKind::DropAndReplace { location: ref place, .. },
source_info }) => {
// Before the call: Release all arguments
if !restricted_validation {
let release_stmt = Statement {
source_info,
kind: StatementKind::Validate(ValidationOp::Release,
vec![place_to_operand(place.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_place, dest_block) in returns {
emit_acquire(
&mut basic_blocks[dest_block],
source_info,
vec![place_to_operand(dest_place)]
);
}
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 basic_blocks {
// 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(_, box 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_place, re, src_place) = match block_data.statements[i].kind {
StatementKind::Assign(ref dest_place,
box Rvalue::Ref(re, _, ref src_place)) => {
(dest_place.clone(), re, src_place.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 acquire_stmt = Statement {
source_info: block_data.statements[i].source_info,
kind: StatementKind::Validate(ValidationOp::Acquire,
vec![place_to_operand(dest_place.deref())]),
};
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![place_to_operand(src_place)]),
};
block_data.statements.insert(i, release_stmt);
}
// Casts can change what validation does (e.g. unsizing)
StatementKind::Assign(_, box Rvalue::Cast(kind, Operand::Copy(_), _)) |
StatementKind::Assign(_, box Rvalue::Cast(kind, Operand::Move(_), _))
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_place, src_place) = match block_data.statements[i].kind {
StatementKind::Assign(ref dest_place,
box Rvalue::Cast(_, Operand::Copy(ref src_place), _)) |
StatementKind::Assign(ref dest_place,
box Rvalue::Cast(_, Operand::Move(ref src_place), _)) =>
{
(dest_place.clone(), src_place.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![place_to_operand(dest_place)]),
};
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![place_to_operand(src_place)]),
};
block_data.statements.insert(i, release_stmt);
}
_ => {},
}
}
}
}
}

View File

@ -113,7 +113,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
StatementKind::StorageLive(..) |
StatementKind::StorageDead(..) |
StatementKind::EndRegion(..) |
StatementKind::Validate(..) |
StatementKind::Retag { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {
// safe (at least as emitted during MIR construction)

View File

@ -22,23 +22,19 @@ use 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,
in_validation_statement: false,
}
}
}
impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> {
fn visit_ty(&mut self, ty: &mut Ty<'tcx>, _: TyContext) {
if !self.in_validation_statement {
*ty = self.tcx.erase_regions(ty);
}
*ty = self.tcx.erase_regions(ty);
self.super_ty(ty);
}
@ -58,20 +54,11 @@ impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> {
block: BasicBlock,
statement: &mut Statement<'tcx>,
location: Location) {
// 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;
}
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

@ -23,7 +23,7 @@ use std::borrow::Cow;
use syntax::ast;
use syntax_pos::Span;
pub mod add_validation;
pub mod add_retag;
pub mod add_moves_for_packed_drops;
pub mod cleanup_post_borrowck;
pub mod check_unsafety;
@ -258,19 +258,21 @@ fn optimized_mir<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx
// Remove all `FakeRead` statements and the borrows that are only
// used for checking matches
&cleanup_post_borrowck::CleanFakeReadsAndBorrows,
&simplify::SimplifyCfg::new("early-opt"),
// These next passes must be executed together
&add_call_guards::CriticalCallEdges,
&elaborate_drops::ElaborateDrops,
&no_landing_pads::NoLandingPads,
// AddValidation needs to run after ElaborateDrops and before EraseRegions, and it needs
// an AllCallEdges pass right before it.
&add_call_guards::AllCallEdges,
&add_validation::AddValidation,
// AddMovesForPackedDrops needs to run after drop
// elaboration.
&add_moves_for_packed_drops::AddMovesForPackedDrops,
// AddRetag needs to run after ElaborateDrops, and it needs
// an AllCallEdges pass right before it. Otherwise it should
// run fairly late, but before optimizations begin.
&add_call_guards::AllCallEdges,
&add_retag::AddRetag,
&simplify::SimplifyCfg::new("elaborate-drops"),

View File

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

View File

@ -241,7 +241,7 @@ fn check_statement(
// These are all NOPs
| StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Validate(..)
| StatementKind::Retag { .. }
| StatementKind::EndRegion(_)
| StatementKind::AscribeUserType(..)
| StatementKind::Nop => Ok(()),

View File

@ -68,7 +68,7 @@ impl RemoveNoopLandingPads {
StatementKind::Assign(_, _) |
StatementKind::SetDiscriminant { .. } |
StatementKind::InlineAsm { .. } |
StatementKind::Validate { .. } => {
StatementKind::Retag { .. } => {
return false;
}
}

View File

@ -162,7 +162,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::Retag { .. } |
mir::StatementKind::AscribeUserType(..) |
mir::StatementKind::Nop => continue,
mir::StatementKind::SetDiscriminant{ .. } =>

View File

@ -204,7 +204,7 @@ pub fn categorize<'tcx>(context: PlaceContext<'tcx>) -> Option<DefUse> {
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
PlaceContext::NonUse(NonUseContext::Validate) =>
PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
Some(DefUse::Use),
///////////////////////////////////////////////////////////////////////////

View File

@ -84,7 +84,7 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> {
StatementKind::Assign(..) => "StatementKind::Assign",
StatementKind::FakeRead(..) => "StatementKind::FakeRead",
StatementKind::EndRegion(..) => "StatementKind::EndRegion",
StatementKind::Validate(..) => "StatementKind::Validate",
StatementKind::Retag { .. } => "StatementKind::Retag",
StatementKind::SetDiscriminant { .. } => "StatementKind::SetDiscriminant",
StatementKind::StorageLive(..) => "StatementKind::StorageLive",
StatementKind::StorageDead(..) => "StatementKind::StorageDead",