Skip to content

Add MIR Validate statement #43403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 4, 2017
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5264103
add new instructions for asserting when values are valid, and to desc…
RalfJung Jul 11, 2017
735ace9
add a pass for validation commands; for now just emit the initial Acq…
RalfJung Jul 11, 2017
33585f4
CleanEndRegions: do not clean regions that occur in types in validati…
RalfJung Jul 11, 2017
82786b2
emit validation for function calls and Ref
RalfJung Jul 11, 2017
24a2ac9
add_validation: handle drop
RalfJung Jul 14, 2017
511b88c
only emit Suspend validation for mutable paths
RalfJung Jul 20, 2017
a233afa
respect lifetime rendering when rendering Suspend validation op
RalfJung Jul 20, 2017
60096b9
when suspending, we need to specify for which lifetime to recover
RalfJung Jul 21, 2017
e869cf2
make ValidationOperand generic so that we can reuse it in miri with a…
RalfJung Jul 21, 2017
23cd90e
add -Z flag for AddValidation pass
RalfJung Jul 22, 2017
b6816b2
please the tidy
RalfJung Jul 22, 2017
04f962a
after a Ref, only acquire the Deref'd destination
RalfJung Jul 25, 2017
b934506
Reorder passes so that AddValidation can run after ElaborateDrops
RalfJung Jul 25, 2017
7ec50df
also release/validate around non-Misc casts
RalfJung Jul 27, 2017
57958d1
Add tests for emitting validation statements
RalfJung Jul 28, 2017
29ed317
silence tidy
RalfJung Jul 28, 2017
6641415
do not use doc comments inside functions
RalfJung Jul 30, 2017
6ff7c8f
more documentation
RalfJung Jul 31, 2017
6135461
CleanEndRegions: use default impl where possible
RalfJung Jul 31, 2017
5e426e1
optionally only emit basic validation for functions containing unsafe…
RalfJung Jul 31, 2017
09cbe58
more readable printing of validation operands
RalfJung Jul 31, 2017
26ca0d1
tidy
RalfJung Jul 31, 2017
e73d314
fix AddValidation on methods
RalfJung Aug 1, 2017
584d823
Handle closures. Add some more tests.
RalfJung Aug 1, 2017
4310edb
handle tuple struct ctors
RalfJung Aug 1, 2017
8f910bc
handle trait items as well
RalfJung Aug 1, 2017
c5154d0
use FnLike to recognize functions for us
RalfJung Aug 1, 2017
a8129d1
add a closure inside an unsafe fn to the tests
RalfJung Aug 1, 2017
321a72c
closure unsafety check: stop moving up when we hit an item
RalfJung Aug 2, 2017
7d8dc7a
also release-validate return value before a call
RalfJung Aug 2, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/librustc/hir/map/blocks.rs
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 13 additions & 3 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
@@ -49,7 +49,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.
@@ -76,14 +76,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,
@@ -684,6 +684,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)
25 changes: 23 additions & 2 deletions src/librustc/ich/impls_mir.rs
Original file line number Diff line number Diff line change
@@ -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>,
82 changes: 81 additions & 1 deletion src/librustc/mir/mod.rs
Original file line number Diff line number Diff line change
@@ -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,13 +838,65 @@ 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::*;
match self.kind {
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,
}
}
16 changes: 14 additions & 2 deletions src/librustc/mir/visit.rs
Original file line number Diff line number Diff line change
@@ -333,6 +333,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);
}
@@ -784,6 +791,9 @@ pub enum LvalueContext<'tcx> {
// Starting and ending a storage live range
StorageLive,
StorageDead,

// Validation command
Validate,
}

impl<'tcx> LvalueContext<'tcx> {
@@ -830,7 +840,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,
}
}

@@ -842,7 +853,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,
}
}

3 changes: 3 additions & 0 deletions src/librustc/session/config.rs
Original file line number Diff line number Diff line change
@@ -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],
19 changes: 13 additions & 6 deletions src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
@@ -933,25 +933,32 @@ 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,
mir::transform::simplify_branches::SimplifyBranches::new("initial"));
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);
1 change: 1 addition & 0 deletions src/librustc_mir/dataflow/drop_flag_effects.rs
Original file line number Diff line number Diff line change
@@ -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 => {
1 change: 1 addition & 0 deletions src/librustc_mir/dataflow/impls/mod.rs
Original file line number Diff line number Diff line change
@@ -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 => {}
}
}
1 change: 1 addition & 0 deletions src/librustc_mir/dataflow/move_paths/mod.rs
Original file line number Diff line number Diff line change
@@ -416,6 +416,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
}
StatementKind::InlineAsm { .. } |
StatementKind::EndRegion(_) |
StatementKind::Validate(..) |
StatementKind::Nop => {}
}
}
Loading