Auto merge of #55316 - RalfJung:retagging, r=oli-obk

Add Retagging statements

This adds a `Retag` statement kind to MIR, used to perform the retagging operation from [Stacked Borrows](https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html). It also kills the old `Validate` statements that I added last year.

NOTE: This includes https://github.com/rust-lang/rust/pull/55270. Only [these commits are new](https://github.com/RalfJung/rust/compare/stacked-borrows-ng...RalfJung:retagging).
This commit is contained in:
bors 2018-11-02 12:45:03 +00:00
commit 87a3c1ee70
37 changed files with 450 additions and 922 deletions

View File

@ -253,8 +253,15 @@ 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") {
// The flags here should be kept in sync with `add_miri_default_args`
// in miri's `src/lib.rs`.
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,17 @@ 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. 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` indicates whether this is the initial retag that happens in the
/// function prolog.
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 +1817,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 +1825,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 +2901,6 @@ CloneTypeFoldableAndLiftImpls! {
SourceInfo,
UpvarDecl,
FakeReadCause,
ValidationOp,
SourceScope,
SourceScopeData,
SourceScopeLocalData,
@ -2997,12 +2953,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 +2967,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

@ -152,6 +152,13 @@ macro_rules! make_mir_visitor {
self.super_ascribe_user_ty(place, variance, user_ty, location);
}
fn visit_retag(&mut self,
fn_entry: & $($mutability)* bool,
place: & $($mutability)* Place<'tcx>,
location: Location) {
self.super_retag(fn_entry, place, location);
}
fn visit_place(&mut self,
place: & $($mutability)* Place<'tcx>,
context: PlaceContext<'tcx>,
@ -371,17 +378,6 @@ 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::SetDiscriminant{ ref $($mutability)* place, .. } => {
self.visit_place(
place,
@ -417,6 +413,10 @@ macro_rules! make_mir_visitor {
self.visit_operand(input, location);
}
}
StatementKind::Retag { ref $($mutability)* fn_entry,
ref $($mutability)* place } => {
self.visit_retag(fn_entry, place, location);
}
StatementKind::AscribeUserType(
ref $($mutability)* place,
ref $($mutability)* variance,
@ -719,6 +719,17 @@ macro_rules! make_mir_visitor {
self.visit_user_type_projection(user_ty);
}
fn super_retag(&mut self,
_fn_entry: & $($mutability)* bool,
place: & $($mutability)* Place<'tcx>,
location: Location) {
self.visit_place(
place,
PlaceContext::MutatingUse(MutatingUseContext::Retag),
location,
);
}
fn super_place(&mut self,
place: & $($mutability)* Place<'tcx>,
context: PlaceContext<'tcx>,
@ -1010,6 +1021,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 +1033,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

@ -600,9 +600,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

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

@ -24,13 +24,6 @@ use super::{
EvalContext, PlaceTy, OpTy, Pointer, MemPlace, MemoryKind,
};
/// Classifying memory accesses
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryAccess {
Read,
Write,
}
/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
fn may_leak(self) -> bool;
@ -181,17 +174,22 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
dest: PlaceTy<'tcx, Self::PointerTag>,
) -> EvalResult<'tcx>;
/// Hook for performing extra checks on a memory access.
///
/// Takes read-only access to the allocation so we can keep all the memory read
/// operations take `&self`. Use a `RefCell` in `AllocExtra` if you
/// need to mutate.
/// Hook for performing extra checks on a memory read access.
#[inline]
fn memory_accessed(
fn memory_read(
_alloc: &Allocation<Self::PointerTag, Self::AllocExtra>,
_ptr: Pointer<Self::PointerTag>,
_size: Size,
_access: MemoryAccess,
) -> EvalResult<'tcx> {
Ok(())
}
/// Hook for performing extra checks on a memory write access.
#[inline]
fn memory_written(
_alloc: &mut Allocation<Self::PointerTag, Self::AllocExtra>,
_ptr: Pointer<Self::PointerTag>,
_size: Size,
) -> EvalResult<'tcx> {
Ok(())
}
@ -201,6 +199,7 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
fn memory_deallocated(
_alloc: &mut Allocation<Self::PointerTag, Self::AllocExtra>,
_ptr: Pointer<Self::PointerTag>,
_size: Size,
) -> EvalResult<'tcx> {
Ok(())
}
@ -242,10 +241,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

@ -30,7 +30,7 @@ use syntax::ast::Mutability;
use super::{
Pointer, AllocId, Allocation, ConstValue, GlobalId,
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
Machine, MemoryAccess, AllocMap, MayLeak, ScalarMaybeUndef, ErrorHandled,
Machine, AllocMap, MayLeak, ScalarMaybeUndef, ErrorHandled,
};
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
@ -232,7 +232,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
// Let the machine take some extra action
M::memory_deallocated(&mut alloc, ptr)?;
let size = Size::from_bytes(alloc.bytes.len() as u64);
M::memory_deallocated(&mut alloc, ptr, size)?;
// Don't forget to remember size and align of this now-dead allocation
let old = self.dead_alloc_map.insert(
@ -644,7 +645,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
let alloc = self.get(ptr.alloc_id)?;
M::memory_accessed(alloc, ptr, size, MemoryAccess::Read)?;
M::memory_read(alloc, ptr, size)?;
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
assert_eq!(size.bytes() as usize as u64, size.bytes());
@ -690,7 +691,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
self.clear_relocations(ptr, size)?;
let alloc = self.get_mut(ptr.alloc_id)?;
M::memory_accessed(alloc, ptr, size, MemoryAccess::Write)?;
M::memory_written(alloc, ptr, size)?;
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
assert_eq!(size.bytes() as usize as u64, size.bytes());

View File

@ -34,7 +34,7 @@ pub use self::place::{Place, PlaceTy, MemPlace, MPlaceTy};
pub use self::memory::{Memory, MemoryKind};
pub use self::machine::{Machine, AllocMap, MemoryAccess, MayLeak};
pub use self::machine::{Machine, AllocMap, MayLeak};
pub use self::operand::{ScalarMaybeUndef, Value, ValTy, Operand, OpTy};

View File

@ -299,23 +299,17 @@ where
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
/// This is the inverse of `ref_to_mplace`.
/// `mutbl` indicates whether we are create a shared or mutable ref, or a raw pointer (`None`).
pub fn create_ref(
&mut self,
place: MPlaceTy<'tcx, M::PointerTag>,
borrow_kind: Option<mir::BorrowKind>,
mutbl: Option<hir::Mutability>,
) -> EvalResult<'tcx, Value<M::PointerTag>> {
// Pointer tag tracking might want to adjust the tag
let place = if M::ENABLE_PTR_TRACKING_HOOKS {
let (size, _) = self.size_and_align_of_mplace(place)?
// for extern types, just cover what we can
.unwrap_or_else(|| place.layout.size_and_align());
let mutbl = match borrow_kind {
Some(mir::BorrowKind::Mut { .. }) |
Some(mir::BorrowKind::Unique) =>
Some(hir::MutMutable),
Some(_) => Some(hir::MutImmutable),
None => None,
};
M::tag_reference(self, *place, place.layout.ty, size, mutbl)?
} else {
*place

View File

@ -12,7 +12,7 @@
//!
//! The main entry point is the `step` method.
use rustc::mir;
use rustc::{hir, mir};
use rustc::ty::layout::LayoutOf;
use rustc::mir::interpret::{EvalResult, Scalar, PointerArithmetic};
@ -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(..) => {}
@ -251,7 +250,15 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
Ref(_, borrow_kind, ref place) => {
let src = self.eval_place(place)?;
let val = self.force_allocation(src)?;
let val = self.create_ref(val, Some(borrow_kind))?;
let mutbl = match borrow_kind {
mir::BorrowKind::Mut { .. } |
mir::BorrowKind::Unique =>
hir::MutMutable,
mir::BorrowKind::Shared |
mir::BorrowKind::Shallow =>
hir::MutImmutable,
};
let val = self.create_ref(val, Some(mutbl))?;
self.write_value(val, dest)?;
}

View File

@ -0,0 +1,174 @@
// 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,
ProjectionElem::Field { .. } |
ProjectionElem::ConstantIndex { .. } |
ProjectionElem::Subslice { .. } |
ProjectionElem::Downcast { .. } =>
// These just offset by a constant, entirely independent of everything else.
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

@ -691,6 +691,14 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Integrator<'a, 'tcx> {
self.in_cleanup_block = false;
}
fn visit_retag(&mut self, fn_entry: &mut bool, place: &mut Place<'tcx>, loc: Location) {
self.super_retag(fn_entry, place, loc);
// We have to patch all inlined retags to be aware that they are no longer
// happening on function entry.
*fn_entry = false;
}
fn visit_terminator_kind(&mut self, block: BasicBlock,
kind: &mut TerminatorKind<'tcx>, loc: Location) {
self.super_terminator_kind(block, kind, loc);

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

View File

@ -0,0 +1,45 @@
// 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.
// compile-flags: -Z span_free_formats -Z mir-emit-retag
// Tests that MIR inliner fixes up `Retag`'s `fn_entry` flag
fn main() {
println!("{}", bar());
}
#[inline(always)]
fn foo(x: &i32, y: &i32) -> bool {
*x == *y
}
fn bar() -> bool {
let f = foo;
f(&1, &-1)
}
// END RUST SOURCE
// START rustc.bar.Inline.after.mir
// ...
// bb0: {
// ...
// Retag(_3);
// Retag(_6);
// StorageLive(_9);
// _9 = (*_3);
// StorageLive(_10);
// _10 = (*_6);
// _0 = Eq(move _9, move _10);
// ...
// return;
// }
// ...
// END rustc.bar.Inline.after.mir

106
src/test/mir-opt/retag.rs Normal file
View File

@ -0,0 +1,106 @@
// 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 mir-emit-retag -Z mir-opt-level=0 -Z span_free_formats
#![allow(unused)]
struct Test(i32);
impl Test {
// Make sure we run the pass on a method, not just on bare functions.
fn foo<'x>(&self, x: &'x mut i32) -> &'x mut i32 { x }
fn foo_shr<'x>(&self, x: &'x i32) -> &'x i32 { x }
}
fn main() {
let mut x = 0;
{
let v = Test(0).foo(&mut x); // just making sure we do not panic when there is a tuple struct ctor
let w = { v }; // assignment
let _w = w; // reborrow
}
// Also test closures
let c: fn(&i32) -> &i32 = |x: &i32| -> &i32 { let _y = x; x };
let _w = c(&x);
// need to call `foo_shr` or it doesn't even get generated
Test(0).foo_shr(&0);
}
// END RUST SOURCE
// START rustc.{{impl}}-foo.EraseRegions.after.mir
// bb0: {
// Retag([fn entry] _1);
// Retag([fn entry] _2);
// ...
// _0 = &mut (*_3);
// ...
// return;
// }
// END rustc.{{impl}}-foo.EraseRegions.after.mir
// START rustc.{{impl}}-foo_shr.EraseRegions.after.mir
// bb0: {
// Retag([fn entry] _1);
// Retag([fn entry] _2);
// ...
// _0 = _2;
// Retag(_0);
// ...
// return;
// }
// END rustc.{{impl}}-foo_shr.EraseRegions.after.mir
// START rustc.main.EraseRegions.after.mir
// fn main() -> () {
// ...
// bb0: {
// ...
// _3 = const Test::foo(move _4, move _6) -> bb1;
// }
//
// bb1: {
// Retag(_3);
// ...
// _9 = move _3;
// Retag(_9);
// _8 = &mut (*_9);
// StorageDead(_9);
// StorageLive(_10);
// _10 = move _8;
// Retag(_10);
// ...
// _13 = move _14(move _15) -> bb2;
// }
//
// bb2: {
// Retag(_13);
// ...
// }
// ...
// }
// END rustc.main.EraseRegions.after.mir
// START rustc.main-{{closure}}.EraseRegions.after.mir
// fn main::{{closure}}(_1: &[closure@NodeId(117)], _2: &i32) -> &i32 {
// ...
// bb0: {
// Retag([fn entry] _1);
// Retag([fn entry] _2);
// StorageLive(_3);
// _3 = _2;
// Retag(_3);
// _0 = _2;
// Retag(_0);
// StorageDead(_3);
// return;
// }
// }
// END rustc.main-{{closure}}.EraseRegions.after.mir

View File

@ -1,76 +0,0 @@
// 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 -Z span_free_formats
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); // just making sure we do not panic when there is a tuple struct ctor
// Also test closures
let c = |x: &mut i32| { let y = &*x; *y };
c(&mut x);
}
// END RUST SOURCE
// START rustc.{{impl}}-foo.EraseRegions.after.mir
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/0:5 ~ validate_1[317d]::{{impl}}[0]::foo[0]), BrAnon(0)) Test, _2: &ReFree(DefId(0/0:5 ~ validate_1[317d]::{{impl}}[0]::foo[0]), BrAnon(1)) mut i32]);
// ...
// return;
// }
// END rustc.{{impl}}-foo.EraseRegions.after.mir
// START rustc.main.EraseRegions.after.mir
// fn main() -> () {
// ...
// bb0: {
// ...
// Validate(Suspend(ReScope(Node(ItemLocalId(13)))), [_1: i32]);
// _6 = &ReErased mut _1;
// Validate(Acquire, [(*_6): i32/ReScope(Node(ItemLocalId(13)))]);
// Validate(Suspend(ReScope(Node(ItemLocalId(13)))), [(*_6): i32/ReScope(Node(ItemLocalId(13)))]);
// _5 = &ReErased mut (*_6);
// Validate(Acquire, [(*_5): i32/ReScope(Node(ItemLocalId(13)))]);
// Validate(Release, [_2: (), _3: &ReScope(Node(ItemLocalId(13))) Test, _5: &ReScope(Node(ItemLocalId(13))) mut i32]);
// _2 = const Test::foo(move _3, move _5) -> bb1;
// }
//
// bb1: {
// Validate(Acquire, [_2: ()]);
// EndRegion(ReScope(Node(ItemLocalId(13))));
// ...
// return;
// }
// }
// END rustc.main.EraseRegions.after.mir
// START rustc.main-{{closure}}.EraseRegions.after.mir
// fn main::{{closure}}(_1: &ReErased [closure@NodeId(65)], _2: &ReErased mut i32) -> i32 {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/1:11 ~ validate_1[317d]::main[0]::{{closure}}[0]), BrEnv) [closure@NodeId(65)], _2: &ReFree(DefId(0/1:11 ~ validate_1[317d]::main[0]::{{closure}}[0]), BrAnon(0)) mut i32]);
// StorageLive(_3);
// Validate(Suspend(ReScope(Remainder { block: ItemLocalId(31), first_statement_index: 0 })), [(*_2): i32]);
// _3 = &ReErased (*_2);
// Validate(Acquire, [(*_3): i32/ReScope(Remainder { block: ItemLocalId(31), first_statement_index: 0 }) (imm)]);
// _0 = (*_3);
// EndRegion(ReScope(Remainder { block: ItemLocalId(31), first_statement_index: 0 }));
// StorageDead(_3);
// return;
// }
// }
// END rustc.main-{{closure}}.EraseRegions.after.mir

View File

@ -1,37 +0,0 @@
// 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
// ignore-wasm32-bare unwinding being disabled causes differences in output
// ignore-wasm64-bare unwinding being disabled causes differences in output
// 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.main.EraseRegions.after.mir
// fn main() -> () {
// ...
// bb1: {
// Validate(Acquire, [_2: std::boxed::Box<[i32; 3]>]);
// Validate(Release, [_2: std::boxed::Box<[i32; 3]>]);
// _1 = move _2 as std::boxed::Box<[i32]> (Unsize);
// Validate(Acquire, [_1: std::boxed::Box<[i32]>]);
// StorageDead(_2);
// StorageDead(_3);
// _0 = ();
// Validate(Release, [_1: std::boxed::Box<[i32]>]);
// drop(_1) -> [return: bb2, unwind: bb3];
// }
// ...
// }
// END rustc.main.EraseRegions.after.mir

View File

@ -1,77 +0,0 @@
// 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.main.EraseRegions.after.mir
// fn main() -> (){
// let mut _0: ();
// scope 1 {
// scope 3 {
// }
// scope 4 {
// let _2: &ReErased Test;
// }
// }
// scope 2 {
// let _1: Test;
// }
// let mut _3: ();
// let mut _4: &ReErased i32;
// let mut _5: &ReErased i32;
// bb0: {
// StorageLive(_1);
// _1 = Test { x: const 0i32 };
// StorageLive(_2);
// Validate(Suspend(ReScope(Remainder { block: ItemLocalId(24), first_statement_index: 3 })), [_1: Test]);
// _2 = &ReErased _1;
// Validate(Acquire, [(*_2): Test/ReScope(Remainder { block: ItemLocalId(24), first_statement_index: 3 }) (imm)]);
// StorageLive(_4);
// StorageLive(_5);
// Validate(Suspend(ReScope(Node(ItemLocalId(22)))), [((*_2).0: i32): i32/ReScope(Remainder { block: ItemLocalId(24), first_statement_index: 3 }) (imm)]);
// _5 = &ReErased ((*_2).0: i32);
// Validate(Acquire, [(*_5): i32/ReScope(Node(ItemLocalId(22))) (imm)]);
// Validate(Suspend(ReScope(Node(ItemLocalId(22)))), [(*_5): i32/ReScope(Node(ItemLocalId(22))) (imm)]);
// _4 = &ReErased (*_5);
// Validate(Acquire, [(*_4): i32/ReScope(Node(ItemLocalId(22))) (imm)]);
// Validate(Release, [_3: (), _4: &ReScope(Node(ItemLocalId(22))) i32]);
// _3 = const foo(move _4) -> bb1;
// }
// bb1: {
// Validate(Acquire, [_3: ()]);
// EndRegion(ReScope(Node(ItemLocalId(22))));
// StorageDead(_4);
// StorageDead(_5);
// _0 = ();
// EndRegion(ReScope(Remainder { block: ItemLocalId(24), first_statement_index: 3 }));
// StorageDead(_2);
// StorageDead(_1);
// return;
// }
// }
// END rustc.main.EraseRegions.after.mir

View File

@ -1,90 +0,0 @@
// 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 -Z span_free_formats
// 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.write_42.EraseRegions.after.mir
// fn write_42(_1: *mut i32) -> bool {
// ...
// bb0: {
// Validate(Acquire, [_1: *mut i32]);
// Validate(Release, [_1: *mut i32]);
// ...
// return;
// }
// }
// END rustc.write_42.EraseRegions.after.mir
// START rustc.write_42-{{closure}}.EraseRegions.after.mir
// fn write_42::{{closure}}(_1: &ReErased [closure@NodeId(32)], _2: *mut i32) -> () {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/1:9 ~ validate_4[317d]::write_42[0]::{{closure}}[0]), BrEnv) [closure@NodeId(32)], _2: *mut i32]);
// Validate(Release, [_1: &ReFree(DefId(0/1:9 ~ validate_4[317d]::write_42[0]::{{closure}}[0]), BrEnv) [closure@NodeId(32)], _2: *mut i32]);
// (*_2) = const 23i32;
// _0 = ();
// return;
// }
// }
// END rustc.write_42-{{closure}}.EraseRegions.after.mir
// START rustc.test.EraseRegions.after.mir
// fn test(_1: &ReErased mut i32) -> () {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/0:4 ~ validate_4[317d]::test[0]), BrAnon(0)) mut i32]);
// Validate(Release, [_1: &ReFree(DefId(0/0:4 ~ validate_4[317d]::test[0]), BrAnon(0)) mut i32]);
// ...
// _2 = const write_42(move _3) -> bb1;
// }
// bb1: {
// Validate(Acquire, [_2: bool]);
// Validate(Release, [_2: bool]);
// ...
// }
// }
// END rustc.test.EraseRegions.after.mir
// START rustc.main-{{closure}}.EraseRegions.after.mir
// fn main::{{closure}}(_1: &ReErased [closure@NodeId(80)], _2: &ReErased mut i32) -> bool {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/1:10 ~ validate_4[317d]::main[0]::{{closure}}[0]), BrEnv) [closure@NodeId(80)], _2: &ReFree(DefId(0/1:10 ~ validate_4[317d]::main[0]::{{closure}}[0]), BrAnon(0)) mut i32]);
// Validate(Release, [_1: &ReFree(DefId(0/1:10 ~ validate_4[317d]::main[0]::{{closure}}[0]), BrEnv) [closure@NodeId(80)], _2: &ReFree(DefId(0/1:10 ~ validate_4[317d]::main[0]::{{closure}}[0]), BrAnon(0)) mut i32]);
// StorageLive(_3);
// ...
// _0 = const write_42(move _3) -> bb1;
// }
// ...
// }
// END rustc.main-{{closure}}.EraseRegions.after.mir

View File

@ -1,69 +0,0 @@
// 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 -Z span_free_formats
// Make sure unsafe fns and fns with an unsafe block still 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) };
// Note that validation will fail if this is executed: The closure keeps the lock on
// x, so the write in write_42 fails. This test just checks code generation,
// so the UB doesn't matter.
test_closure(&mut 0);
}
// END RUST SOURCE
// START rustc.test.EraseRegions.after.mir
// fn test(_1: &ReErased mut i32) -> () {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/0:4 ~ validate_5[317d]::test[0]), BrAnon(0)) mut i32]);
// ...
// Validate(Release, [_2: bool, _3: *mut i32]);
// _2 = const write_42(move _3) -> bb1;
// }
// ...
// }
// END rustc.test.EraseRegions.after.mir
// START rustc.main-{{closure}}.EraseRegions.after.mir
// fn main::{{closure}}(_1: &ReErased [closure@NodeId(62)], _2: &ReErased mut i32) -> bool {
// ...
// bb0: {
// Validate(Acquire, [_1: &ReFree(DefId(0/1:9 ~ validate_5[317d]::main[0]::{{closure}}[0]), BrEnv) [closure@NodeId(62)], _2: &ReFree(DefId(0/1:9 ~ validate_5[317d]::main[0]::{{closure}}[0]), BrAnon(0)) mut i32]);
// StorageLive(_3);
// StorageLive(_4);
// StorageLive(_5);
// Validate(Suspend(ReScope(Node(ItemLocalId(16)))), [(*_2): i32]);
// _5 = &ReErased mut (*_2);
// Validate(Acquire, [(*_5): i32/ReScope(Node(ItemLocalId(16)))]);
// _4 = move _5 as *mut i32 (Misc);
// _3 = move _4;
// EndRegion(ReScope(Node(ItemLocalId(16))));
// StorageDead(_4);
// StorageDead(_5);
// Validate(Release, [_0: bool, _3: *mut i32]);
// _0 = const write_42(move _3) -> bb1;
// }
// ...
// }
// END rustc.main-{{closure}}.EraseRegions.after.mir