93c4ffe72f
Made `do_dataflow` and related API `pub(crate)`.
235 lines
10 KiB
Rust
235 lines
10 KiB
Rust
// Copyright 2012-2016 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.
|
|
|
|
use syntax::abi::{Abi};
|
|
use syntax::ast;
|
|
use syntax_pos::Span;
|
|
|
|
use rustc::ty::{self, TyCtxt};
|
|
use rustc::mir::{self, Mir, Location};
|
|
use rustc_data_structures::indexed_set::IdxSetBuf;
|
|
use rustc_data_structures::indexed_vec::Idx;
|
|
use transform::{MirPass, MirSource};
|
|
|
|
use dataflow::{do_dataflow, DebugFormatted};
|
|
use dataflow::MoveDataParamEnv;
|
|
use dataflow::BitDenotation;
|
|
use dataflow::DataflowResults;
|
|
use dataflow::{DefinitelyInitializedLvals, MaybeInitializedLvals, MaybeUninitializedLvals};
|
|
use dataflow::move_paths::{MovePathIndex, LookupResult};
|
|
use dataflow::move_paths::{HasMoveData, MoveData};
|
|
use dataflow;
|
|
|
|
use dataflow::has_rustc_mir_with;
|
|
|
|
pub struct SanityCheck;
|
|
|
|
impl MirPass for SanityCheck {
|
|
fn run_pass<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|
src: MirSource, mir: &mut Mir<'tcx>) {
|
|
let def_id = src.def_id;
|
|
let id = tcx.hir.as_local_node_id(def_id).unwrap();
|
|
if !tcx.has_attr(def_id, "rustc_mir_borrowck") {
|
|
debug!("skipping rustc_peek::SanityCheck on {}", tcx.item_path_str(def_id));
|
|
return;
|
|
} else {
|
|
debug!("running rustc_peek::SanityCheck on {}", tcx.item_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(mir, tcx).unwrap();
|
|
let mdpe = MoveDataParamEnv { move_data: move_data, param_env: param_env };
|
|
let dead_unwinds = IdxSetBuf::new_empty(mir.basic_blocks().len());
|
|
let flow_inits =
|
|
do_dataflow(tcx, mir, id, &attributes, &dead_unwinds,
|
|
MaybeInitializedLvals::new(tcx, mir, &mdpe),
|
|
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]));
|
|
let flow_uninits =
|
|
do_dataflow(tcx, mir, id, &attributes, &dead_unwinds,
|
|
MaybeUninitializedLvals::new(tcx, mir, &mdpe),
|
|
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]));
|
|
let flow_def_inits =
|
|
do_dataflow(tcx, mir, id, &attributes, &dead_unwinds,
|
|
DefinitelyInitializedLvals::new(tcx, mir, &mdpe),
|
|
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]));
|
|
|
|
if has_rustc_mir_with(&attributes, "rustc_peek_maybe_init").is_some() {
|
|
sanity_check_via_rustc_peek(tcx, mir, id, &attributes, &flow_inits);
|
|
}
|
|
if has_rustc_mir_with(&attributes, "rustc_peek_maybe_uninit").is_some() {
|
|
sanity_check_via_rustc_peek(tcx, mir, id, &attributes, &flow_uninits);
|
|
}
|
|
if has_rustc_mir_with(&attributes, "rustc_peek_definite_init").is_some() {
|
|
sanity_check_via_rustc_peek(tcx, mir, id, &attributes, &flow_def_inits);
|
|
}
|
|
if has_rustc_mir_with(&attributes, "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<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|
mir: &Mir<'tcx>,
|
|
id: ast::NodeId,
|
|
_attributes: &[ast::Attribute],
|
|
results: &DataflowResults<O>)
|
|
where O: BitDenotation<Idx=MovePathIndex> + HasMoveData<'tcx>
|
|
{
|
|
debug!("sanity_check_via_rustc_peek id: {:?}", id);
|
|
// FIXME: this is not DRY. Figure out way to abstract this and
|
|
// `dataflow::build_sets`. (But note it is doing non-standard
|
|
// stuff, so such generalization may not be realistic.)
|
|
|
|
for bb in mir.basic_blocks().indices() {
|
|
each_block(tcx, mir, results, bb);
|
|
}
|
|
}
|
|
|
|
fn each_block<'a, 'tcx, O>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|
mir: &Mir<'tcx>,
|
|
results: &DataflowResults<O>,
|
|
bb: mir::BasicBlock) where
|
|
O: BitDenotation<Idx=MovePathIndex> + HasMoveData<'tcx>
|
|
{
|
|
let move_data = results.0.operator.move_data();
|
|
let mir::BasicBlockData { ref statements, ref terminator, is_cleanup: _ } = mir[bb];
|
|
|
|
let (args, span) = match is_rustc_peek(tcx, terminator) {
|
|
Some(args_and_span) => args_and_span,
|
|
None => return,
|
|
};
|
|
assert!(args.len() == 1);
|
|
let peek_arg_place = match args[0] {
|
|
mir::Operand::Copy(ref place @ mir::Place::Local(_)) |
|
|
mir::Operand::Move(ref place @ mir::Place::Local(_)) => Some(place),
|
|
_ => None,
|
|
};
|
|
|
|
let peek_arg_place = match peek_arg_place {
|
|
Some(arg) => arg,
|
|
None => {
|
|
tcx.sess.diagnostic().span_err(
|
|
span, "dataflow::sanity_check cannot feed a non-temp to rustc_peek.");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let mut entry = results.0.sets.on_entry_set_for(bb.index()).to_owned();
|
|
let mut gen = results.0.sets.gen_set_for(bb.index()).to_owned();
|
|
let mut kill = results.0.sets.kill_set_for(bb.index()).to_owned();
|
|
|
|
// Emulate effect of all statements in the block up to (but not
|
|
// including) the borrow within `peek_arg_place`. Do *not* include
|
|
// call to `peek_arg_place` itself (since we are peeking the state
|
|
// of the argument at time immediate preceding Call to
|
|
// `rustc_peek`).
|
|
|
|
let mut sets = dataflow::BlockSets { on_entry: &mut entry,
|
|
gen_set: &mut gen,
|
|
kill_set: &mut kill };
|
|
|
|
for (j, stmt) in statements.iter().enumerate() {
|
|
debug!("rustc_peek: ({:?},{}) {:?}", bb, j, stmt);
|
|
let (place, rvalue) = match stmt.kind {
|
|
mir::StatementKind::Assign(ref place, ref rvalue) => {
|
|
(place, rvalue)
|
|
}
|
|
mir::StatementKind::StorageLive(_) |
|
|
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,
|
|
"sanity_check should run before Deaggregator inserts SetDiscriminant"),
|
|
};
|
|
|
|
if place == peek_arg_place {
|
|
if let mir::Rvalue::Ref(_, mir::BorrowKind::Shared, ref peeking_at_place) = *rvalue {
|
|
// Okay, our search is over.
|
|
match move_data.rev_lookup.find(peeking_at_place) {
|
|
LookupResult::Exact(peek_mpi) => {
|
|
let bit_state = sets.on_entry.contains(&peek_mpi);
|
|
debug!("rustc_peek({:?} = &{:?}) bit_state: {}",
|
|
place, peeking_at_place, bit_state);
|
|
if !bit_state {
|
|
tcx.sess.span_err(span, "rustc_peek: bit not set");
|
|
}
|
|
}
|
|
LookupResult::Parent(..) => {
|
|
tcx.sess.span_err(span, "rustc_peek: argument untracked");
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
// Our search should have been over, but the input
|
|
// does not match expectations of `rustc_peek` for
|
|
// this sanity_check.
|
|
let msg = "rustc_peek: argument expression \
|
|
must be immediate borrow of form `&expr`";
|
|
tcx.sess.span_err(span, msg);
|
|
}
|
|
}
|
|
|
|
let lhs_mpi = move_data.rev_lookup.find(place);
|
|
|
|
debug!("rustc_peek: computing effect on place: {:?} ({:?}) in stmt: {:?}",
|
|
place, lhs_mpi, stmt);
|
|
// reset GEN and KILL sets before emulating their effect.
|
|
for e in sets.gen_set.words_mut() { *e = 0; }
|
|
for e in sets.kill_set.words_mut() { *e = 0; }
|
|
results.0.operator.statement_effect(&mut sets, Location { block: bb, statement_index: j });
|
|
sets.on_entry.union(sets.gen_set);
|
|
sets.on_entry.subtract(sets.kill_set);
|
|
}
|
|
|
|
tcx.sess.span_err(span, &format!("rustc_peek: MIR did not match \
|
|
anticipated pattern; note that \
|
|
rustc_peek expects input of \
|
|
form `&expr`"));
|
|
}
|
|
|
|
fn is_rustc_peek<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|
terminator: &'a Option<mir::Terminator<'tcx>>)
|
|
-> Option<(&'a [mir::Operand<'tcx>], Span)> {
|
|
if let Some(mir::Terminator { ref kind, source_info, .. }) = *terminator {
|
|
if let mir::TerminatorKind::Call { func: ref oper, ref args, .. } = *kind {
|
|
if let mir::Operand::Constant(ref func) = *oper {
|
|
if let ty::TyFnDef(def_id, _) = func.ty.sty {
|
|
let abi = tcx.fn_sig(def_id).abi();
|
|
let name = tcx.item_name(def_id);
|
|
if abi == Abi::RustIntrinsic && name == "rustc_peek" {
|
|
return Some((args, source_info.span));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return None;
|
|
}
|