• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use rustc_span::symbol::sym;
2 use rustc_span::Span;
3 
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};
8 
9 use crate::errors::{
10     PeekArgumentNotALocal, PeekArgumentUntracked, PeekBitNotSet, PeekMustBeNotTemporary,
11     PeekMustBePlaceOrRefPlace, StopAfterDataFlowEndedCompilation,
12 };
13 use crate::framework::BitSetExt;
14 use crate::impls::{
15     DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
16 };
17 use crate::move_paths::{HasMoveData, MoveData};
18 use crate::move_paths::{LookupResult, MovePathIndex};
19 use crate::MoveDataParamEnv;
20 use crate::{Analysis, JoinSemiLattice, ResultsCursor};
21 
22 pub struct SanityCheck;
23 
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 {
run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>)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));
31             return;
32         } else {
33             debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
34         }
35 
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 };
39 
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();
44 
45             sanity_check_via_rustc_peek(tcx, flow_inits.into_results_cursor(body));
46         }
47 
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();
52 
53             sanity_check_via_rustc_peek(tcx, flow_uninits.into_results_cursor(body));
54         }
55 
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();
60 
61             sanity_check_via_rustc_peek(tcx, flow_def_inits.into_results_cursor(body));
62         }
63 
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();
66 
67             sanity_check_via_rustc_peek(tcx, flow_liveness.into_results_cursor(body));
68         }
69 
70         if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
71             tcx.sess.emit_fatal(StopAfterDataFlowEndedCompilation);
72         }
73     }
74 }
75 
76 /// This function scans `mir` for all calls to the intrinsic
77 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
78 ///
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".
84 ///
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.
88 ///
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.)
sanity_check_via_rustc_peek<'tcx, A>( tcx: TyCtxt<'tcx>, mut cursor: ResultsCursor<'_, 'tcx, A>, ) where A: RustcPeekAt<'tcx>,92 pub fn sanity_check_via_rustc_peek<'tcx, A>(
93     tcx: TyCtxt<'tcx>,
94     mut cursor: ResultsCursor<'_, 'tcx, A>,
95 ) where
96     A: RustcPeekAt<'tcx>,
97 {
98     let def_id = cursor.body().source.def_id();
99     debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
100 
101     let peek_calls = cursor.body().basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| {
102         PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
103     });
104 
105     for (bb, block_data, call) in peek_calls {
106         // Look for a sequence like the following to indicate that we should be peeking at `_1`:
107         //    _2 = &_1;
108         //    rustc_peek(_2);
109         //
110         //    /* or */
111         //
112         //    _2 = _1;
113         //    rustc_peek(_2);
114         let (statement_index, peek_rval) = block_data
115             .statements
116             .iter()
117             .enumerate()
118             .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
119             .expect(
120                 "call to rustc_peek should be preceded by \
121                     assignment to temporary holding its argument",
122             );
123 
124         match (call.kind, peek_rval) {
125             (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
126             | (
127                 PeekCallKind::ByVal,
128                 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
129             ) => {
130                 let loc = Location { block: bb, statement_index };
131                 cursor.seek_before_primary_effect(loc);
132                 let (state, analysis) = cursor.get_with_analysis();
133                 analysis.peek_at(tcx, *place, state, call);
134             }
135 
136             _ => {
137                 tcx.sess.emit_err(PeekMustBePlaceOrRefPlace { span: call.span });
138             }
139         }
140     }
141 }
142 
143 /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
144 /// RHS of the assignment.
value_assigned_to_local<'a, 'tcx>( stmt: &'a mir::Statement<'tcx>, local: Local, ) -> Option<&'a mir::Rvalue<'tcx>>145 fn value_assigned_to_local<'a, 'tcx>(
146     stmt: &'a mir::Statement<'tcx>,
147     local: Local,
148 ) -> Option<&'a mir::Rvalue<'tcx>> {
149     if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
150         if let Some(l) = place.as_local() {
151             if local == l {
152                 return Some(&*rvalue);
153             }
154         }
155     }
156 
157     None
158 }
159 
160 #[derive(Clone, Copy, Debug)]
161 enum PeekCallKind {
162     ByVal,
163     ByRef,
164 }
165 
166 impl PeekCallKind {
from_arg_ty(arg: Ty<'_>) -> Self167     fn from_arg_ty(arg: Ty<'_>) -> Self {
168         match arg.kind() {
169             ty::Ref(_, _, _) => PeekCallKind::ByRef,
170             _ => PeekCallKind::ByVal,
171         }
172     }
173 }
174 
175 #[derive(Clone, Copy, Debug)]
176 pub struct PeekCall {
177     arg: Local,
178     kind: PeekCallKind,
179     span: Span,
180 }
181 
182 impl PeekCall {
from_terminator<'tcx>( tcx: TyCtxt<'tcx>, terminator: &mir::Terminator<'tcx>, ) -> Option<Self>183     fn from_terminator<'tcx>(
184         tcx: TyCtxt<'tcx>,
185         terminator: &mir::Terminator<'tcx>,
186     ) -> Option<Self> {
187         use mir::Operand;
188 
189         let span = terminator.source_info.span;
190         if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
191             &terminator.kind
192         {
193             if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
194                 let name = tcx.item_name(def_id);
195                 if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek {
196                     return None;
197                 }
198 
199                 assert_eq!(args.len(), 1);
200                 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
201                 let arg = match &args[0] {
202                     Operand::Copy(place) | Operand::Move(place) => {
203                         if let Some(local) = place.as_local() {
204                             local
205                         } else {
206                             tcx.sess.emit_err(PeekMustBeNotTemporary { span });
207                             return None;
208                         }
209                     }
210                     _ => {
211                         tcx.sess.emit_err(PeekMustBeNotTemporary { span });
212                         return None;
213                     }
214                 };
215 
216                 return Some(PeekCall { arg, kind, span });
217             }
218         }
219 
220         None
221     }
222 }
223 
224 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
peek_at( &self, tcx: TyCtxt<'tcx>, place: mir::Place<'tcx>, flow_state: &Self::Domain, call: PeekCall, )225     fn peek_at(
226         &self,
227         tcx: TyCtxt<'tcx>,
228         place: mir::Place<'tcx>,
229         flow_state: &Self::Domain,
230         call: PeekCall,
231     );
232 }
233 
234 impl<'tcx, A, D> RustcPeekAt<'tcx> for A
235 where
236     A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
237     D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
238 {
peek_at( &self, tcx: TyCtxt<'tcx>, place: mir::Place<'tcx>, flow_state: &Self::Domain, call: PeekCall, )239     fn peek_at(
240         &self,
241         tcx: TyCtxt<'tcx>,
242         place: mir::Place<'tcx>,
243         flow_state: &Self::Domain,
244         call: PeekCall,
245     ) {
246         match self.move_data().rev_lookup.find(place.as_ref()) {
247             LookupResult::Exact(peek_mpi) => {
248                 let bit_state = flow_state.contains(peek_mpi);
249                 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
250                 if !bit_state {
251                     tcx.sess.emit_err(PeekBitNotSet { span: call.span });
252                 }
253             }
254 
255             LookupResult::Parent(..) => {
256                 tcx.sess.emit_err(PeekArgumentUntracked { span: call.span });
257             }
258         }
259     }
260 }
261 
262 impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
peek_at( &self, tcx: TyCtxt<'tcx>, place: mir::Place<'tcx>, flow_state: &ChunkedBitSet<Local>, call: PeekCall, )263     fn peek_at(
264         &self,
265         tcx: TyCtxt<'tcx>,
266         place: mir::Place<'tcx>,
267         flow_state: &ChunkedBitSet<Local>,
268         call: PeekCall,
269     ) {
270         info!(?place, "peek_at");
271         let Some(local) = place.as_local() else {
272             tcx.sess.emit_err(PeekArgumentNotALocal { span: call.span });
273             return;
274         };
275 
276         if !flow_state.contains(local) {
277             tcx.sess.emit_err(PeekBitNotSet { span: call.span });
278         }
279     }
280 }
281