rust/src/librustc_mir/transform/rustc_peek.rs

316 lines
10 KiB
Rust
Raw Normal View History

2019-12-22 16:42:04 -06:00
use rustc_target::spec::abi::Abi;
use syntax::ast;
use syntax::symbol::sym;
use syntax_pos::Span;
2019-12-22 16:42:04 -06:00
use crate::transform::{MirPass, MirSource};
2019-03-17 05:36:10 -05:00
use rustc::hir::def_id::DefId;
2019-12-22 16:42:04 -06:00
use rustc::mir::{self, Body, BodyAndCache, Local, Location};
use rustc::ty::{self, Ty, TyCtxt};
use rustc_index::bit_set::BitSet;
2019-12-22 16:42:04 -06:00
use crate::dataflow::move_paths::{HasMoveData, MoveData};
use crate::dataflow::move_paths::{LookupResult, MovePathIndex};
2019-02-07 15:28:15 -06:00
use crate::dataflow::BitDenotation;
use crate::dataflow::DataflowResults;
use crate::dataflow::DataflowResultsCursor;
2019-12-22 16:42:04 -06:00
use crate::dataflow::IndirectlyMutableLocals;
use crate::dataflow::MoveDataParamEnv;
use crate::dataflow::{do_dataflow, DebugFormatted};
2019-02-07 15:28:15 -06:00
use crate::dataflow::{
2019-12-22 16:42:04 -06:00
DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
2019-02-07 15:28:15 -06:00
};
2019-02-07 15:28:15 -06:00
use crate::dataflow::has_rustc_mir_with;
pub struct SanityCheck;
2019-08-04 15:20:00 -05:00
impl<'tcx> MirPass<'tcx> for SanityCheck {
fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>) {
2019-02-03 04:51:07 -06:00
let def_id = src.def_id();
if !tcx.has_attr(def_id, sym::rustc_mir) {
debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
return;
} else {
debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
}
let attributes = tcx.get_attrs(def_id);
let param_env = tcx.param_env(def_id);
let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap();
let mdpe = MoveDataParamEnv { move_data: move_data, param_env: param_env };
let dead_unwinds = BitSet::new_empty(body.basic_blocks().len());
2019-12-22 16:42:04 -06:00
let flow_inits = do_dataflow(
tcx,
body,
def_id,
&attributes,
&dead_unwinds,
MaybeInitializedPlaces::new(tcx, body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]),
);
let flow_uninits = do_dataflow(
tcx,
body,
def_id,
&attributes,
&dead_unwinds,
MaybeUninitializedPlaces::new(tcx, body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]),
);
let flow_def_inits = do_dataflow(
tcx,
body,
def_id,
&attributes,
&dead_unwinds,
DefinitelyInitializedPlaces::new(tcx, body, &mdpe),
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]),
);
let flow_indirectly_mut = do_dataflow(
tcx,
body,
def_id,
&attributes,
&dead_unwinds,
IndirectlyMutableLocals::new(tcx, body, param_env),
|_, i| DebugFormatted::new(&i),
);
if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_init).is_some() {
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_inits);
}
if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_uninit).is_some() {
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_uninits);
}
if has_rustc_mir_with(&attributes, sym::rustc_peek_definite_init).is_some() {
2019-12-22 16:42:04 -06:00
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_def_inits);
}
if has_rustc_mir_with(&attributes, sym::rustc_peek_indirectly_mutable).is_some() {
2019-12-22 16:42:04 -06:00
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_indirectly_mut);
}
if has_rustc_mir_with(&attributes, sym::stop_after_dataflow).is_some() {
tcx.sess.fatal("stop_after_dataflow ended compilation");
}
}
}
/// This function scans `mir` for all calls to the intrinsic
/// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
///
/// For each such call, determines what the dataflow bit-state is for
/// the L-value corresponding to `expr`; if the bit-state is a 1, then
/// that call to `rustc_peek` is ignored by the sanity check. If the
/// bit-state is a 0, then this pass emits a error message saying
/// "rustc_peek: bit not set".
///
/// The intention is that one can write unit tests for dataflow by
/// putting code into a compile-fail test and using `rustc_peek` to
/// make observations about the results of dataflow static analyses.
///
/// (If there are any calls to `rustc_peek` that do not match the
/// expression form above, then that emits an error as well, but those
/// errors are not intended to be used for unit tests.)
pub fn sanity_check_via_rustc_peek<'tcx, O>(
2019-06-13 16:48:52 -05:00
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
def_id: DefId,
_attributes: &[ast::Attribute],
results: &DataflowResults<'tcx, O>,
2019-12-22 16:42:04 -06:00
) where
O: RustcPeekAt<'tcx>,
{
2019-03-17 05:36:10 -05:00
debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
let mut cursor = DataflowResultsCursor::new(results, body);
2019-12-22 16:42:04 -06:00
let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
});
for (bb, block_data, call) in peek_calls {
// Look for a sequence like the following to indicate that we should be peeking at `_1`:
// _2 = &_1;
// rustc_peek(_2);
//
// /* or */
//
// _2 = _1;
// rustc_peek(_2);
let (statement_index, peek_rval) = block_data
.statements
.iter()
.enumerate()
.filter_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
.next()
2019-12-22 16:42:04 -06:00
.expect(
"call to rustc_peek should be preceded by \
assignment to temporary holding its argument",
);
match (call.kind, peek_rval) {
2019-12-22 16:42:04 -06:00
(PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Move(place)))
2019-12-22 16:42:04 -06:00
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Copy(place))) => {
let loc = Location { block: bb, statement_index };
cursor.seek(loc);
let state = cursor.get();
results.operator().peek_at(tcx, place, state, call);
}
_ => {
let msg = "rustc_peek: argument expression \
must be either `place` or `&place`";
tcx.sess.span_err(call.span, msg);
}
}
}
}
/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
/// RHS of the assignment.
fn value_assigned_to_local<'a, 'tcx>(
stmt: &'a mir::Statement<'tcx>,
local: Local,
) -> Option<&'a mir::Rvalue<'tcx>> {
if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
if let Some(l) = place.as_local() {
if local == l {
return Some(&*rvalue);
}
}
}
None
}
2017-12-23 16:45:53 -06:00
#[derive(Clone, Copy, Debug)]
enum PeekCallKind {
ByVal,
ByRef,
}
impl PeekCallKind {
fn from_arg_ty(arg: Ty<'_>) -> Self {
match arg.kind {
ty::Ref(_, _, _) => PeekCallKind::ByRef,
_ => PeekCallKind::ByVal,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PeekCall {
arg: Local,
kind: PeekCallKind,
span: Span,
}
impl PeekCall {
fn from_terminator<'tcx>(
tcx: TyCtxt<'tcx>,
terminator: &mir::Terminator<'tcx>,
) -> Option<Self> {
use mir::Operand;
let span = terminator.source_info.span;
if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
&terminator.kind
{
if let ty::FnDef(def_id, substs) = func.literal.ty.kind {
let sig = tcx.fn_sig(def_id);
let name = tcx.item_name(def_id);
if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
return None;
}
assert_eq!(args.len(), 1);
let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
let arg = match &args[0] {
Operand::Copy(place) | Operand::Move(place) => {
if let Some(local) = place.as_local() {
local
} else {
tcx.sess.diagnostic().span_err(
span,
"dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
);
return None;
}
}
_ => {
tcx.sess.diagnostic().span_err(
span,
"dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
);
return None;
}
};
2019-12-22 16:42:04 -06:00
return Some(PeekCall { arg, kind, span });
}
}
None
}
}
pub trait RustcPeekAt<'tcx>: BitDenotation<'tcx> {
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
call: PeekCall,
);
}
impl<'tcx, O> RustcPeekAt<'tcx> for O
2019-12-22 16:42:04 -06:00
where
O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
{
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Self::Idx>,
call: PeekCall,
) {
match self.move_data().rev_lookup.find(place.as_ref()) {
LookupResult::Exact(peek_mpi) => {
let bit_state = flow_state.contains(peek_mpi);
2019-12-22 16:42:04 -06:00
debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
if !bit_state {
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
}
}
LookupResult::Parent(..) => {
tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
}
}
}
}
impl<'tcx> RustcPeekAt<'tcx> for IndirectlyMutableLocals<'_, 'tcx> {
fn peek_at(
&self,
tcx: TyCtxt<'tcx>,
place: &mir::Place<'tcx>,
flow_state: &BitSet<Local>,
call: PeekCall,
) {
warn!("peek_at: place={:?}", place);
let local = if let Some(l) = place.as_local() {
l
} else {
tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
return;
};
if !flow_state.contains(local) {
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
}
}
}