1 use rustc_span::symbol::sym;
4 use rustc_index::bit_set::ChunkedBitSet;
5 use rustc_middle::mir::MirPass;
6 use rustc_middle::mir::{self, Body, Local, Location};
7 use rustc_middle::ty::{self, Ty, TyCtxt};
10 PeekArgumentNotALocal, PeekArgumentUntracked, PeekBitNotSet, PeekMustBeNotTemporary,
11 PeekMustBePlaceOrRefPlace, StopAfterDataFlowEndedCompilation,
13 use crate::framework::BitSetExt;
15 DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
17 use crate::move_paths::{HasMoveData, MoveData};
18 use crate::move_paths::{LookupResult, MovePathIndex};
19 use crate::MoveDataParamEnv;
20 use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor};
22 pub struct SanityCheck;
24 // FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
25 impl<'tcx> MirPass<'tcx> for SanityCheck {
26 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
27 use crate::has_rustc_mir_with;
28 let def_id = body.source.def_id();
29 if !tcx.has_attr(def_id, sym::rustc_mir) {
30 debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
33 debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
36 let param_env = tcx.param_env(def_id);
37 let (_, move_data) = MoveData::gather_moves(body, tcx, param_env).unwrap();
38 let mdpe = MoveDataParamEnv { move_data, param_env };
40 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
41 let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
42 .into_engine(tcx, body)
43 .iterate_to_fixpoint();
45 sanity_check_via_rustc_peek(tcx, body, &flow_inits);
48 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
49 let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
50 .into_engine(tcx, body)
51 .iterate_to_fixpoint();
53 sanity_check_via_rustc_peek(tcx, body, &flow_uninits);
56 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() {
57 let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
58 .into_engine(tcx, body)
59 .iterate_to_fixpoint();
61 sanity_check_via_rustc_peek(tcx, body, &flow_def_inits);
64 if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
65 let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
67 sanity_check_via_rustc_peek(tcx, body, &flow_liveness);
70 if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
71 tcx.sess.emit_fatal(StopAfterDataFlowEndedCompilation);
76 /// This function scans `mir` for all calls to the intrinsic
77 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
79 /// For each such call, determines what the dataflow bit-state is for
80 /// the L-value corresponding to `expr`; if the bit-state is a 1, then
81 /// that call to `rustc_peek` is ignored by the sanity check. If the
82 /// bit-state is a 0, then this pass emits an error message saying
83 /// "rustc_peek: bit not set".
85 /// The intention is that one can write unit tests for dataflow by
86 /// putting code into a UI test and using `rustc_peek` to
87 /// make observations about the results of dataflow static analyses.
89 /// (If there are any calls to `rustc_peek` that do not match the
90 /// expression form above, then that emits an error as well, but those
91 /// errors are not intended to be used for unit tests.)
92 pub fn sanity_check_via_rustc_peek<'tcx, A>(
95 results: &Results<'tcx, A>,
99 let def_id = body.source.def_id();
100 debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
102 let mut cursor = ResultsCursor::new(body, results);
104 let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
105 PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
108 for (bb, block_data, call) in peek_calls {
109 // Look for a sequence like the following to indicate that we should be peeking at `_1`:
117 let (statement_index, peek_rval) = block_data
121 .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
123 "call to rustc_peek should be preceded by \
124 assignment to temporary holding its argument",
127 match (call.kind, peek_rval) {
128 (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
131 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
133 let loc = Location { block: bb, statement_index };
134 cursor.seek_before_primary_effect(loc);
135 let state = cursor.get();
136 results.analysis.peek_at(tcx, *place, state, call);
140 tcx.sess.emit_err(PeekMustBePlaceOrRefPlace { span: call.span });
146 /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
147 /// RHS of the assignment.
148 fn value_assigned_to_local<'a, 'tcx>(
149 stmt: &'a mir::Statement<'tcx>,
151 ) -> Option<&'a mir::Rvalue<'tcx>> {
152 if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
153 if let Some(l) = place.as_local() {
155 return Some(&*rvalue);
163 #[derive(Clone, Copy, Debug)]
170 fn from_arg_ty(arg: Ty<'_>) -> Self {
172 ty::Ref(_, _, _) => PeekCallKind::ByRef,
173 _ => PeekCallKind::ByVal,
178 #[derive(Clone, Copy, Debug)]
179 pub struct PeekCall {
186 fn from_terminator<'tcx>(
188 terminator: &mir::Terminator<'tcx>,
192 let span = terminator.source_info.span;
193 if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
196 if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
197 let name = tcx.item_name(def_id);
198 if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek {
202 assert_eq!(args.len(), 1);
203 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
204 let arg = match &args[0] {
205 Operand::Copy(place) | Operand::Move(place) => {
206 if let Some(local) = place.as_local() {
209 tcx.sess.emit_err(PeekMustBeNotTemporary { span });
214 tcx.sess.emit_err(PeekMustBeNotTemporary { span });
219 return Some(PeekCall { arg, kind, span });
227 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
231 place: mir::Place<'tcx>,
232 flow_state: &Self::Domain,
237 impl<'tcx, A, D> RustcPeekAt<'tcx> for A
239 A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
240 D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
245 place: mir::Place<'tcx>,
246 flow_state: &Self::Domain,
249 match self.move_data().rev_lookup.find(place.as_ref()) {
250 LookupResult::Exact(peek_mpi) => {
251 let bit_state = flow_state.contains(peek_mpi);
252 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
254 tcx.sess.emit_err(PeekBitNotSet { span: call.span });
258 LookupResult::Parent(..) => {
259 tcx.sess.emit_err(PeekArgumentUntracked { span: call.span });
265 impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
269 place: mir::Place<'tcx>,
270 flow_state: &ChunkedBitSet<Local>,
273 info!(?place, "peek_at");
274 let Some(local) = place.as_local() else {
275 tcx.sess.emit_err(PeekArgumentNotALocal { span: call.span });
279 if !flow_state.contains(local) {
280 tcx.sess.emit_err(PeekBitNotSet { span: call.span });