2 use rustc_span::symbol::sym;
4 use rustc_target::spec::abi::Abi;
6 use crate::transform::{MirPass, MirSource};
7 use rustc_hir::def_id::DefId;
8 use rustc_index::bit_set::BitSet;
9 use rustc_middle::mir::{self, Body, Local, Location};
10 use rustc_middle::ty::{self, Ty, TyCtxt};
12 use crate::dataflow::move_paths::{HasMoveData, MoveData};
13 use crate::dataflow::move_paths::{LookupResult, MovePathIndex};
14 use crate::dataflow::MaybeMutBorrowedLocals;
15 use crate::dataflow::MoveDataParamEnv;
16 use crate::dataflow::{Analysis, Results, ResultsCursor};
17 use crate::dataflow::{
18 DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
21 pub struct SanityCheck;
23 impl<'tcx> MirPass<'tcx> for SanityCheck {
24 fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) {
25 use crate::dataflow::has_rustc_mir_with;
26 let def_id = src.def_id();
27 if !tcx.has_attr(def_id, sym::rustc_mir) {
28 debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
31 debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
34 let attributes = tcx.get_attrs(def_id);
35 let param_env = tcx.param_env(def_id);
36 let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap();
37 let mdpe = MoveDataParamEnv { move_data, param_env };
39 let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
40 .into_engine(tcx, body, def_id)
41 .iterate_to_fixpoint();
42 let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
43 .into_engine(tcx, body, def_id)
44 .iterate_to_fixpoint();
45 let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
46 .into_engine(tcx, body, def_id)
47 .iterate_to_fixpoint();
48 let flow_mut_borrowed = MaybeMutBorrowedLocals::mut_borrows_only(tcx, body, param_env)
49 .into_engine(tcx, body, def_id)
50 .iterate_to_fixpoint();
52 if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_init).is_some() {
53 sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_inits);
55 if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_uninit).is_some() {
56 sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_uninits);
58 if has_rustc_mir_with(&attributes, sym::rustc_peek_definite_init).is_some() {
59 sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_def_inits);
61 if has_rustc_mir_with(&attributes, sym::rustc_peek_indirectly_mutable).is_some() {
62 sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_mut_borrowed);
64 if has_rustc_mir_with(&attributes, sym::stop_after_dataflow).is_some() {
65 tcx.sess.fatal("stop_after_dataflow ended compilation");
70 /// This function scans `mir` for all calls to the intrinsic
71 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
73 /// For each such call, determines what the dataflow bit-state is for
74 /// the L-value corresponding to `expr`; if the bit-state is a 1, then
75 /// that call to `rustc_peek` is ignored by the sanity check. If the
76 /// bit-state is a 0, then this pass emits a error message saying
77 /// "rustc_peek: bit not set".
79 /// The intention is that one can write unit tests for dataflow by
80 /// putting code into a compile-fail test and using `rustc_peek` to
81 /// make observations about the results of dataflow static analyses.
83 /// (If there are any calls to `rustc_peek` that do not match the
84 /// expression form above, then that emits an error as well, but those
85 /// errors are not intended to be used for unit tests.)
86 pub fn sanity_check_via_rustc_peek<'tcx, A>(
90 _attributes: &[ast::Attribute],
91 results: &Results<'tcx, A>,
95 debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
97 let mut cursor = ResultsCursor::new(body, results);
99 let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
100 PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
103 for (bb, block_data, call) in peek_calls {
104 // Look for a sequence like the following to indicate that we should be peeking at `_1`:
112 let (statement_index, peek_rval) = block_data
116 .filter_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
119 "call to rustc_peek should be preceded by \
120 assignment to temporary holding its argument",
123 match (call.kind, peek_rval) {
124 (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
127 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
129 let loc = Location { block: bb, statement_index };
130 cursor.seek_before(loc);
131 let state = cursor.get();
132 results.analysis.peek_at(tcx, *place, state, call);
136 let msg = "rustc_peek: argument expression \
137 must be either `place` or `&place`";
138 tcx.sess.span_err(call.span, msg);
144 /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
145 /// RHS of the assignment.
146 fn value_assigned_to_local<'a, 'tcx>(
147 stmt: &'a mir::Statement<'tcx>,
149 ) -> Option<&'a mir::Rvalue<'tcx>> {
150 if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
151 if let Some(l) = place.as_local() {
153 return Some(&*rvalue);
161 #[derive(Clone, Copy, Debug)]
168 fn from_arg_ty(arg: Ty<'_>) -> Self {
170 ty::Ref(_, _, _) => PeekCallKind::ByRef,
171 _ => PeekCallKind::ByVal,
176 #[derive(Clone, Copy, Debug)]
177 pub struct PeekCall {
184 fn from_terminator<'tcx>(
186 terminator: &mir::Terminator<'tcx>,
190 let span = terminator.source_info.span;
191 if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
194 if let ty::FnDef(def_id, substs) = func.literal.ty.kind {
195 let sig = tcx.fn_sig(def_id);
196 let name = tcx.item_name(def_id);
197 if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
201 assert_eq!(args.len(), 1);
202 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
203 let arg = match &args[0] {
204 Operand::Copy(place) | Operand::Move(place) => {
205 if let Some(local) = place.as_local() {
208 tcx.sess.diagnostic().span_err(
210 "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
216 tcx.sess.diagnostic().span_err(
218 "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
224 return Some(PeekCall { arg, kind, span });
232 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
236 place: mir::Place<'tcx>,
237 flow_state: &BitSet<Self::Idx>,
242 impl<'tcx, A> RustcPeekAt<'tcx> for A
244 A: Analysis<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
249 place: mir::Place<'tcx>,
250 flow_state: &BitSet<Self::Idx>,
253 match self.move_data().rev_lookup.find(place.as_ref()) {
254 LookupResult::Exact(peek_mpi) => {
255 let bit_state = flow_state.contains(peek_mpi);
256 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
258 tcx.sess.span_err(call.span, "rustc_peek: bit not set");
262 LookupResult::Parent(..) => {
263 tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
269 impl<'tcx> RustcPeekAt<'tcx> for MaybeMutBorrowedLocals<'_, 'tcx> {
273 place: mir::Place<'tcx>,
274 flow_state: &BitSet<Local>,
277 warn!("peek_at: place={:?}", place);
278 let local = if let Some(l) = place.as_local() {
281 tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
285 if !flow_state.contains(local) {
286 tcx.sess.span_err(call.span, "rustc_peek: bit not set");