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