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:
parent
4e88b7363b
commit
aafcf2c942
26 changed files with 233 additions and 543 deletions
|
@ -254,7 +254,12 @@ fn main() {
|
||||||
// When running miri tests, we need to generate MIR for all libraries
|
// When running miri tests, we need to generate MIR for all libraries
|
||||||
if env::var("TEST_MIRI").ok().map_or(false, |val| val == "true") {
|
if env::var("TEST_MIRI").ok().map_or(false, |val| val == "true") {
|
||||||
cmd.arg("-Zalways-encode-mir");
|
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)
|
// Force all crates compiled by this compiler to (a) be unstable and (b)
|
||||||
|
|
|
@ -257,9 +257,9 @@ for mir::StatementKind<'gcx> {
|
||||||
mir::StatementKind::EndRegion(ref region_scope) => {
|
mir::StatementKind::EndRegion(ref region_scope) => {
|
||||||
region_scope.hash_stable(hcx, hasher);
|
region_scope.hash_stable(hcx, hasher);
|
||||||
}
|
}
|
||||||
mir::StatementKind::Validate(ref op, ref places) => {
|
mir::StatementKind::Retag { fn_entry, ref place } => {
|
||||||
op.hash_stable(hcx, hasher);
|
fn_entry.hash_stable(hcx, hasher);
|
||||||
places.hash_stable(hcx, hasher);
|
place.hash_stable(hcx, hasher);
|
||||||
}
|
}
|
||||||
mir::StatementKind::AscribeUserType(ref place, ref variance, ref c_ty) => {
|
mir::StatementKind::AscribeUserType(ref place, ref variance, ref c_ty) => {
|
||||||
place.hash_stable(hcx, hasher);
|
place.hash_stable(hcx, hasher);
|
||||||
|
@ -278,23 +278,6 @@ for mir::StatementKind<'gcx> {
|
||||||
|
|
||||||
impl_stable_hash_for!(enum mir::FakeReadCause { ForMatchGuard, ForMatchedPlace, ForLet });
|
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> {
|
impl<'a, 'gcx> HashStable<StableHashingContext<'a>> for mir::Place<'gcx> {
|
||||||
fn hash_stable<W: StableHasherResult>(&self,
|
fn hash_stable<W: StableHasherResult>(&self,
|
||||||
hcx: &mut StableHashingContext<'a>,
|
hcx: &mut StableHashingContext<'a>,
|
||||||
|
|
|
@ -1754,10 +1754,13 @@ pub enum StatementKind<'tcx> {
|
||||||
inputs: Box<[Operand<'tcx>]>,
|
inputs: Box<[Operand<'tcx>]>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Assert the given places to be valid inhabitants of their type. These statements are
|
/// Retag references in the given place, ensuring they got fresh tags. This is
|
||||||
/// currently only interpreted by miri and only generated when "-Z mir-emit-validate" is passed.
|
/// part of the Stacked Borrows model. `fn_entry` indicates whether this
|
||||||
/// See <https://internals.rust-lang.org/t/types-as-contracts/5562/73> for more details.
|
/// is the initial retag that happens in the function prolog. These statements are
|
||||||
Validate(ValidationOp, Vec<ValidationOperand<'tcx, Place<'tcx>>>),
|
/// 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).
|
/// Mark one terminating point of a region scope (i.e. static region).
|
||||||
/// (The starting point(s) arise implicitly from borrows.)
|
/// (The starting point(s) arise implicitly from borrows.)
|
||||||
|
@ -1810,57 +1813,6 @@ pub enum FakeReadCause {
|
||||||
ForLet,
|
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> {
|
impl<'tcx> Debug for Statement<'tcx> {
|
||||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||||
use self::StatementKind::*;
|
use self::StatementKind::*;
|
||||||
|
@ -1869,7 +1821,8 @@ impl<'tcx> Debug for Statement<'tcx> {
|
||||||
FakeRead(ref cause, ref place) => write!(fmt, "FakeRead({:?}, {:?})", cause, place),
|
FakeRead(ref cause, ref place) => write!(fmt, "FakeRead({:?}, {:?})", cause, place),
|
||||||
// (reuse lifetime rendering policy from ppaux.)
|
// (reuse lifetime rendering policy from ppaux.)
|
||||||
EndRegion(ref ce) => write!(fmt, "EndRegion({})", ty::ReScope(*ce)),
|
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),
|
StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place),
|
||||||
StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place),
|
StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place),
|
||||||
SetDiscriminant {
|
SetDiscriminant {
|
||||||
|
@ -2944,7 +2897,6 @@ CloneTypeFoldableAndLiftImpls! {
|
||||||
SourceInfo,
|
SourceInfo,
|
||||||
UpvarDecl,
|
UpvarDecl,
|
||||||
FakeReadCause,
|
FakeReadCause,
|
||||||
ValidationOp,
|
|
||||||
SourceScope,
|
SourceScope,
|
||||||
SourceScopeData,
|
SourceScopeData,
|
||||||
SourceScopeLocalData,
|
SourceScopeLocalData,
|
||||||
|
@ -2997,12 +2949,6 @@ BraceStructTypeFoldableImpl! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BraceStructTypeFoldableImpl! {
|
|
||||||
impl<'tcx> TypeFoldable<'tcx> for ValidationOperand<'tcx, Place<'tcx>> {
|
|
||||||
place, ty, re, mutbl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BraceStructTypeFoldableImpl! {
|
BraceStructTypeFoldableImpl! {
|
||||||
impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> {
|
impl<'tcx> TypeFoldable<'tcx> for Statement<'tcx> {
|
||||||
source_info, kind
|
source_info, kind
|
||||||
|
@ -3017,7 +2963,7 @@ EnumTypeFoldableImpl! {
|
||||||
(StatementKind::StorageLive)(a),
|
(StatementKind::StorageLive)(a),
|
||||||
(StatementKind::StorageDead)(a),
|
(StatementKind::StorageDead)(a),
|
||||||
(StatementKind::InlineAsm) { asm, outputs, inputs },
|
(StatementKind::InlineAsm) { asm, outputs, inputs },
|
||||||
(StatementKind::Validate)(a, b),
|
(StatementKind::Retag) { fn_entry, place },
|
||||||
(StatementKind::EndRegion)(a),
|
(StatementKind::EndRegion)(a),
|
||||||
(StatementKind::AscribeUserType)(a, v, b),
|
(StatementKind::AscribeUserType)(a, v, b),
|
||||||
(StatementKind::Nop),
|
(StatementKind::Nop),
|
||||||
|
|
|
@ -371,16 +371,12 @@ macro_rules! make_mir_visitor {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
StatementKind::EndRegion(_) => {}
|
StatementKind::EndRegion(_) => {}
|
||||||
StatementKind::Validate(_, ref $($mutability)* places) => {
|
StatementKind::Retag { fn_entry: _, ref $($mutability)* place } => {
|
||||||
for operand in places {
|
self.visit_place(
|
||||||
self.visit_place(
|
place,
|
||||||
& $($mutability)* operand.place,
|
PlaceContext::MutatingUse(MutatingUseContext::Retag),
|
||||||
PlaceContext::NonUse(NonUseContext::Validate),
|
location,
|
||||||
location
|
);
|
||||||
);
|
|
||||||
self.visit_ty(& $($mutability)* operand.ty,
|
|
||||||
TyContext::Location(location));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
StatementKind::SetDiscriminant{ ref $($mutability)* place, .. } => {
|
StatementKind::SetDiscriminant{ ref $($mutability)* place, .. } => {
|
||||||
self.visit_place(
|
self.visit_place(
|
||||||
|
@ -1010,6 +1006,8 @@ pub enum MutatingUseContext<'tcx> {
|
||||||
/// f(&mut x.y);
|
/// f(&mut x.y);
|
||||||
///
|
///
|
||||||
Projection,
|
Projection,
|
||||||
|
/// Retagging (updating the "Stacked Borrows" tag)
|
||||||
|
Retag,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -1020,8 +1018,6 @@ pub enum NonUseContext {
|
||||||
StorageDead,
|
StorageDead,
|
||||||
/// User type annotation assertions for NLL.
|
/// User type annotation assertions for NLL.
|
||||||
AscribeUserTy,
|
AscribeUserTy,
|
||||||
/// Validation command.
|
|
||||||
Validate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
|
@ -1282,9 +1282,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
|
||||||
"in addition to `.mir` files, create graphviz `.dot` files"),
|
"in addition to `.mir` files, create graphviz `.dot` files"),
|
||||||
dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED],
|
dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED],
|
||||||
"if set, exclude the pass number when dumping MIR (used in tests)"),
|
"if set, exclude the pass number when dumping MIR (used in tests)"),
|
||||||
mir_emit_validate: usize = (0, parse_uint, [TRACKED],
|
mir_emit_retag: bool = (false, parse_bool, [TRACKED],
|
||||||
"emit Validate MIR statements, interpreted e.g. by miri (0: do not emit; 1: if function \
|
"emit Retagging MIR statements, interpreted e.g. by miri; implies -Zmir-opt-level=0"),
|
||||||
contains unsafe block, only validate arguments; 2: always emit full validation)"),
|
|
||||||
perf_stats: bool = (false, parse_bool, [UNTRACKED],
|
perf_stats: bool = (false, parse_bool, [UNTRACKED],
|
||||||
"print some performance-related statistics"),
|
"print some performance-related statistics"),
|
||||||
hir_stats: bool = (false, parse_bool, [UNTRACKED],
|
hir_stats: bool = (false, parse_bool, [UNTRACKED],
|
||||||
|
|
|
@ -1547,11 +1547,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should we emit EndRegion MIR statements? These are consumed by
|
/// Should we emit EndRegion MIR statements? These are consumed by
|
||||||
/// MIR borrowck, but not when NLL is used. They are also consumed
|
/// MIR borrowck, but not when NLL is used.
|
||||||
/// by the validation stuff.
|
|
||||||
pub fn emit_end_regions(self) -> bool {
|
pub fn emit_end_regions(self) -> bool {
|
||||||
self.sess.opts.debugging_opts.emit_end_regions ||
|
self.sess.opts.debugging_opts.emit_end_regions ||
|
||||||
self.sess.opts.debugging_opts.mir_emit_validate > 0 ||
|
|
||||||
self.use_mir_borrowck()
|
self.use_mir_borrowck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,8 @@ impl Visitor<'tcx> for LocalAnalyzer<'mir, 'a, 'll, 'tcx> {
|
||||||
self.assign(local, location);
|
self.assign(local, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaceContext::NonUse(_) => {}
|
PlaceContext::NonUse(_) |
|
||||||
|
PlaceContext::MutatingUse(MutatingUseContext::Retag) => {}
|
||||||
|
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl FunctionCx<'a, 'll, 'tcx> {
|
||||||
}
|
}
|
||||||
mir::StatementKind::FakeRead(..) |
|
mir::StatementKind::FakeRead(..) |
|
||||||
mir::StatementKind::EndRegion(_) |
|
mir::StatementKind::EndRegion(_) |
|
||||||
mir::StatementKind::Validate(..) |
|
mir::StatementKind::Retag { .. } |
|
||||||
mir::StatementKind::AscribeUserType(..) |
|
mir::StatementKind::AscribeUserType(..) |
|
||||||
mir::StatementKind::Nop => bx,
|
mir::StatementKind::Nop => bx,
|
||||||
}
|
}
|
||||||
|
|
|
@ -574,9 +574,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
|
||||||
}
|
}
|
||||||
StatementKind::Nop
|
StatementKind::Nop
|
||||||
| StatementKind::AscribeUserType(..)
|
| StatementKind::AscribeUserType(..)
|
||||||
| StatementKind::Validate(..)
|
| StatementKind::Retag { .. }
|
||||||
| StatementKind::StorageLive(..) => {
|
| StatementKind::StorageLive(..) => {
|
||||||
// `Nop`, `AscribeUserType`, `Validate`, and `StorageLive` are irrelevant
|
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
|
||||||
// to borrow check.
|
// to borrow check.
|
||||||
}
|
}
|
||||||
StatementKind::StorageDead(local) => {
|
StatementKind::StorageDead(local) => {
|
||||||
|
|
|
@ -136,9 +136,9 @@ impl<'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx, 'gcx> {
|
||||||
StatementKind::EndRegion(..) |
|
StatementKind::EndRegion(..) |
|
||||||
StatementKind::Nop |
|
StatementKind::Nop |
|
||||||
StatementKind::AscribeUserType(..) |
|
StatementKind::AscribeUserType(..) |
|
||||||
StatementKind::Validate(..) |
|
StatementKind::Retag { .. } |
|
||||||
StatementKind::StorageLive(..) => {
|
StatementKind::StorageLive(..) => {
|
||||||
// `Nop`, `AscribeUserType`, `Validate`, and `StorageLive` are irrelevant
|
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
|
||||||
// to borrow check.
|
// to borrow check.
|
||||||
}
|
}
|
||||||
StatementKind::StorageDead(local) => {
|
StatementKind::StorageDead(local) => {
|
||||||
|
|
|
@ -1264,7 +1264,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
|
||||||
| StatementKind::StorageDead(_)
|
| StatementKind::StorageDead(_)
|
||||||
| StatementKind::InlineAsm { .. }
|
| StatementKind::InlineAsm { .. }
|
||||||
| StatementKind::EndRegion(_)
|
| StatementKind::EndRegion(_)
|
||||||
| StatementKind::Validate(..)
|
| StatementKind::Retag { .. }
|
||||||
| StatementKind::Nop => {}
|
| StatementKind::Nop => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,7 +338,7 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Borrows<'a, 'gcx, 'tcx> {
|
||||||
mir::StatementKind::FakeRead(..) |
|
mir::StatementKind::FakeRead(..) |
|
||||||
mir::StatementKind::SetDiscriminant { .. } |
|
mir::StatementKind::SetDiscriminant { .. } |
|
||||||
mir::StatementKind::StorageLive(..) |
|
mir::StatementKind::StorageLive(..) |
|
||||||
mir::StatementKind::Validate(..) |
|
mir::StatementKind::Retag { .. } |
|
||||||
mir::StatementKind::AscribeUserType(..) |
|
mir::StatementKind::AscribeUserType(..) |
|
||||||
mir::StatementKind::Nop => {}
|
mir::StatementKind::Nop => {}
|
||||||
|
|
||||||
|
|
|
@ -302,7 +302,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
|
||||||
"SetDiscriminant should not exist during borrowck");
|
"SetDiscriminant should not exist during borrowck");
|
||||||
}
|
}
|
||||||
StatementKind::EndRegion(_) |
|
StatementKind::EndRegion(_) |
|
||||||
StatementKind::Validate(..) |
|
StatementKind::Retag { .. } |
|
||||||
StatementKind::AscribeUserType(..) |
|
StatementKind::AscribeUserType(..) |
|
||||||
StatementKind::Nop => {}
|
StatementKind::Nop => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,10 +242,10 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
|
||||||
|
|
||||||
/// Execute a validation operation
|
/// Execute a validation operation
|
||||||
#[inline]
|
#[inline]
|
||||||
fn validation_op(
|
fn retag(
|
||||||
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
|
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
|
||||||
_op: ::rustc::mir::ValidationOp,
|
_fn_entry: bool,
|
||||||
_operand: &::rustc::mir::ValidationOperand<'tcx, ::rustc::mir::Place<'tcx>>,
|
_place: PlaceTy<'tcx, Self::PointerTag>,
|
||||||
) -> EvalResult<'tcx> {
|
) -> EvalResult<'tcx> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// interpreter is solely intended for borrowck'ed code.
|
||||||
FakeRead(..) => {}
|
FakeRead(..) => {}
|
||||||
|
|
||||||
// Validity checks.
|
// Retagging.
|
||||||
Validate(op, ref places) => {
|
Retag { fn_entry, ref place } => {
|
||||||
for operand in places {
|
let dest = self.eval_place(place)?;
|
||||||
M::validation_op(self, op, operand)?;
|
M::retag(self, fn_entry, dest)?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EndRegion(..) => {}
|
EndRegion(..) => {}
|
||||||
|
|
169
src/librustc_mir/transform/add_retag.rs
Normal file
169
src/librustc_mir/transform/add_retag.rs
Normal 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() },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -113,7 +113,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
|
||||||
StatementKind::StorageLive(..) |
|
StatementKind::StorageLive(..) |
|
||||||
StatementKind::StorageDead(..) |
|
StatementKind::StorageDead(..) |
|
||||||
StatementKind::EndRegion(..) |
|
StatementKind::EndRegion(..) |
|
||||||
StatementKind::Validate(..) |
|
StatementKind::Retag { .. } |
|
||||||
StatementKind::AscribeUserType(..) |
|
StatementKind::AscribeUserType(..) |
|
||||||
StatementKind::Nop => {
|
StatementKind::Nop => {
|
||||||
// safe (at least as emitted during MIR construction)
|
// safe (at least as emitted during MIR construction)
|
||||||
|
|
|
@ -22,23 +22,19 @@ use transform::{MirPass, MirSource};
|
||||||
|
|
||||||
struct EraseRegionsVisitor<'a, 'tcx: 'a> {
|
struct EraseRegionsVisitor<'a, 'tcx: 'a> {
|
||||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||||
in_validation_statement: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> EraseRegionsVisitor<'a, 'tcx> {
|
impl<'a, 'tcx> EraseRegionsVisitor<'a, 'tcx> {
|
||||||
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
|
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
|
||||||
EraseRegionsVisitor {
|
EraseRegionsVisitor {
|
||||||
tcx,
|
tcx,
|
||||||
in_validation_statement: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> {
|
impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> {
|
||||||
fn visit_ty(&mut self, ty: &mut Ty<'tcx>, _: TyContext) {
|
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);
|
self.super_ty(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,20 +54,11 @@ impl<'a, 'tcx> MutVisitor<'tcx> for EraseRegionsVisitor<'a, 'tcx> {
|
||||||
block: BasicBlock,
|
block: BasicBlock,
|
||||||
statement: &mut Statement<'tcx>,
|
statement: &mut Statement<'tcx>,
|
||||||
location: Location) {
|
location: Location) {
|
||||||
// Do NOT delete EndRegion if validation statements are emitted.
|
if let StatementKind::EndRegion(_) = statement.kind {
|
||||||
// Validation needs EndRegion.
|
statement.kind = StatementKind::Nop;
|
||||||
if self.tcx.sess.opts.debugging_opts.mir_emit_validate == 0 {
|
|
||||||
if let StatementKind::EndRegion(_) = statement.kind {
|
|
||||||
statement.kind = StatementKind::Nop;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.in_validation_statement = match statement.kind {
|
|
||||||
StatementKind::Validate(..) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
self.super_statement(block, statement, location);
|
self.super_statement(block, statement, location);
|
||||||
self.in_validation_statement = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ use std::borrow::Cow;
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
use syntax_pos::Span;
|
use syntax_pos::Span;
|
||||||
|
|
||||||
pub mod add_validation;
|
pub mod add_retag;
|
||||||
pub mod add_moves_for_packed_drops;
|
pub mod add_moves_for_packed_drops;
|
||||||
pub mod cleanup_post_borrowck;
|
pub mod cleanup_post_borrowck;
|
||||||
pub mod check_unsafety;
|
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
|
// Remove all `FakeRead` statements and the borrows that are only
|
||||||
// used for checking matches
|
// used for checking matches
|
||||||
&cleanup_post_borrowck::CleanFakeReadsAndBorrows,
|
&cleanup_post_borrowck::CleanFakeReadsAndBorrows,
|
||||||
|
|
||||||
&simplify::SimplifyCfg::new("early-opt"),
|
&simplify::SimplifyCfg::new("early-opt"),
|
||||||
|
|
||||||
// These next passes must be executed together
|
// These next passes must be executed together
|
||||||
&add_call_guards::CriticalCallEdges,
|
&add_call_guards::CriticalCallEdges,
|
||||||
&elaborate_drops::ElaborateDrops,
|
&elaborate_drops::ElaborateDrops,
|
||||||
&no_landing_pads::NoLandingPads,
|
&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
|
// AddMovesForPackedDrops needs to run after drop
|
||||||
// elaboration.
|
// elaboration.
|
||||||
&add_moves_for_packed_drops::AddMovesForPackedDrops,
|
&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"),
|
&simplify::SimplifyCfg::new("elaborate-drops"),
|
||||||
|
|
||||||
|
|
|
@ -1167,7 +1167,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
|
||||||
StatementKind::StorageDead(_) |
|
StatementKind::StorageDead(_) |
|
||||||
StatementKind::InlineAsm {..} |
|
StatementKind::InlineAsm {..} |
|
||||||
StatementKind::EndRegion(_) |
|
StatementKind::EndRegion(_) |
|
||||||
StatementKind::Validate(..) |
|
StatementKind::Retag { .. } |
|
||||||
StatementKind::AscribeUserType(..) |
|
StatementKind::AscribeUserType(..) |
|
||||||
StatementKind::Nop => {}
|
StatementKind::Nop => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,7 +241,7 @@ fn check_statement(
|
||||||
// These are all NOPs
|
// These are all NOPs
|
||||||
| StatementKind::StorageLive(_)
|
| StatementKind::StorageLive(_)
|
||||||
| StatementKind::StorageDead(_)
|
| StatementKind::StorageDead(_)
|
||||||
| StatementKind::Validate(..)
|
| StatementKind::Retag { .. }
|
||||||
| StatementKind::EndRegion(_)
|
| StatementKind::EndRegion(_)
|
||||||
| StatementKind::AscribeUserType(..)
|
| StatementKind::AscribeUserType(..)
|
||||||
| StatementKind::Nop => Ok(()),
|
| StatementKind::Nop => Ok(()),
|
||||||
|
|
|
@ -68,7 +68,7 @@ impl RemoveNoopLandingPads {
|
||||||
StatementKind::Assign(_, _) |
|
StatementKind::Assign(_, _) |
|
||||||
StatementKind::SetDiscriminant { .. } |
|
StatementKind::SetDiscriminant { .. } |
|
||||||
StatementKind::InlineAsm { .. } |
|
StatementKind::InlineAsm { .. } |
|
||||||
StatementKind::Validate { .. } => {
|
StatementKind::Retag { .. } => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ fn each_block<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||||
mir::StatementKind::StorageDead(_) |
|
mir::StatementKind::StorageDead(_) |
|
||||||
mir::StatementKind::InlineAsm { .. } |
|
mir::StatementKind::InlineAsm { .. } |
|
||||||
mir::StatementKind::EndRegion(_) |
|
mir::StatementKind::EndRegion(_) |
|
||||||
mir::StatementKind::Validate(..) |
|
mir::StatementKind::Retag { .. } |
|
||||||
mir::StatementKind::AscribeUserType(..) |
|
mir::StatementKind::AscribeUserType(..) |
|
||||||
mir::StatementKind::Nop => continue,
|
mir::StatementKind::Nop => continue,
|
||||||
mir::StatementKind::SetDiscriminant{ .. } =>
|
mir::StatementKind::SetDiscriminant{ .. } =>
|
||||||
|
|
|
@ -204,7 +204,7 @@ pub fn categorize<'tcx>(context: PlaceContext<'tcx>) -> Option<DefUse> {
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
|
||||||
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
|
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
|
||||||
PlaceContext::NonUse(NonUseContext::Validate) =>
|
PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
|
||||||
Some(DefUse::Use),
|
Some(DefUse::Use),
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl<'a, 'tcx> mir_visit::Visitor<'tcx> for StatCollector<'a, 'tcx> {
|
||||||
StatementKind::Assign(..) => "StatementKind::Assign",
|
StatementKind::Assign(..) => "StatementKind::Assign",
|
||||||
StatementKind::FakeRead(..) => "StatementKind::FakeRead",
|
StatementKind::FakeRead(..) => "StatementKind::FakeRead",
|
||||||
StatementKind::EndRegion(..) => "StatementKind::EndRegion",
|
StatementKind::EndRegion(..) => "StatementKind::EndRegion",
|
||||||
StatementKind::Validate(..) => "StatementKind::Validate",
|
StatementKind::Retag { .. } => "StatementKind::Retag",
|
||||||
StatementKind::SetDiscriminant { .. } => "StatementKind::SetDiscriminant",
|
StatementKind::SetDiscriminant { .. } => "StatementKind::SetDiscriminant",
|
||||||
StatementKind::StorageLive(..) => "StatementKind::StorageLive",
|
StatementKind::StorageLive(..) => "StatementKind::StorageLive",
|
||||||
StatementKind::StorageDead(..) => "StatementKind::StorageDead",
|
StatementKind::StorageDead(..) => "StatementKind::StorageDead",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue