| use std::borrow::Borrow; |
| |
| use rustc_ast::ast; |
| use rustc_span::symbol::sym; |
| use rustc_span::Span; |
| use rustc_target::spec::abi::Abi; |
| |
| use crate::transform::MirPass; |
| use rustc_index::bit_set::BitSet; |
| use rustc_middle::mir::{self, Body, Local, Location}; |
| use rustc_middle::ty::{self, Ty, TyCtxt}; |
| |
| use crate::dataflow::impls::{ |
| DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeMutBorrowedLocals, |
| MaybeUninitializedPlaces, |
| }; |
| use crate::dataflow::move_paths::{HasMoveData, MoveData}; |
| use crate::dataflow::move_paths::{LookupResult, MovePathIndex}; |
| use crate::dataflow::MoveDataParamEnv; |
| use crate::dataflow::{Analysis, JoinSemiLattice, Results, ResultsCursor}; |
| |
| pub struct SanityCheck; |
| |
| impl<'tcx> MirPass<'tcx> for SanityCheck { |
| fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { |
| use crate::dataflow::has_rustc_mir_with; |
| let def_id = body.source.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, param_env }; |
| let sess = &tcx.sess; |
| |
| if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_init).is_some() { |
| let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe) |
| .into_engine(tcx, body) |
| .iterate_to_fixpoint(); |
| |
| sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_inits); |
| } |
| |
| if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_uninit).is_some() { |
| let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe) |
| .into_engine(tcx, body) |
| .iterate_to_fixpoint(); |
| |
| sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_uninits); |
| } |
| |
| if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_definite_init).is_some() { |
| let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe) |
| .into_engine(tcx, body) |
| .iterate_to_fixpoint(); |
| |
| sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_def_inits); |
| } |
| |
| if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_indirectly_mutable).is_some() { |
| let flow_mut_borrowed = MaybeMutBorrowedLocals::mut_borrows_only(tcx, body, param_env) |
| .into_engine(tcx, body) |
| .iterate_to_fixpoint(); |
| |
| sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_mut_borrowed); |
| } |
| |
| if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_liveness).is_some() { |
| let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint(); |
| |
| sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_liveness); |
| } |
| |
| if has_rustc_mir_with(sess, &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 an error message saying |
| /// "rustc_peek: bit not set". |
| /// |
| /// The intention is that one can write unit tests for dataflow by |
| /// putting code into a UI 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, A>( |
| tcx: TyCtxt<'tcx>, |
| body: &Body<'tcx>, |
| _attributes: &[ast::Attribute], |
| results: &Results<'tcx, A>, |
| ) where |
| A: RustcPeekAt<'tcx>, |
| { |
| let def_id = body.source.def_id(); |
| debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id); |
| |
| let mut cursor = ResultsCursor::new(body, results); |
| |
| 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() |
| .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval))) |
| .expect( |
| "call to rustc_peek should be preceded by \ |
| assignment to temporary holding its argument", |
| ); |
| |
| match (call.kind, peek_rval) { |
| (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place)) |
| | ( |
| PeekCallKind::ByVal, |
| mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)), |
| ) => { |
| let loc = Location { block: bb, statement_index }; |
| cursor.seek_before_primary_effect(loc); |
| let state = cursor.get(); |
| results.analysis.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 |
| } |
| |
| #[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; |
| } |
| }; |
| |
| return Some(PeekCall { arg, kind, span }); |
| } |
| } |
| |
| None |
| } |
| } |
| |
| pub trait RustcPeekAt<'tcx>: Analysis<'tcx> { |
| fn peek_at( |
| &self, |
| tcx: TyCtxt<'tcx>, |
| place: mir::Place<'tcx>, |
| flow_state: &Self::Domain, |
| call: PeekCall, |
| ); |
| } |
| |
| impl<'tcx, A, D> RustcPeekAt<'tcx> for A |
| where |
| A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>, |
| D: JoinSemiLattice + Clone + Borrow<BitSet<MovePathIndex>>, |
| { |
| fn peek_at( |
| &self, |
| tcx: TyCtxt<'tcx>, |
| place: mir::Place<'tcx>, |
| flow_state: &Self::Domain, |
| call: PeekCall, |
| ) { |
| match self.move_data().rev_lookup.find(place.as_ref()) { |
| LookupResult::Exact(peek_mpi) => { |
| let bit_state = flow_state.borrow().contains(peek_mpi); |
| 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 MaybeMutBorrowedLocals<'_, '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"); |
| } |
| } |
| } |
| |
| impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals { |
| 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"); |
| } |
| } |
| } |