]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_dataflow/src/rustc_peek.rs
Rollup merge of #99192 - Amanieu:fix-asm-srcloc, r=petrochenkov
[rust.git] / compiler / rustc_mir_dataflow / src / rustc_peek.rs
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::framework::BitSetExt;
10 use crate::impls::{
11     DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
12 };
13 use crate::move_paths::{HasMoveData, MoveData};
14 use crate::move_paths::{LookupResult, MovePathIndex};
15 use crate::MoveDataParamEnv;
16 use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor};
17
18 pub struct SanityCheck;
19
20 // FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
21 impl<'tcx> MirPass<'tcx> for SanityCheck {
22     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23         use crate::has_rustc_mir_with;
24         let def_id = body.source.def_id();
25         if !tcx.has_attr(def_id, sym::rustc_mir) {
26             debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
27             return;
28         } else {
29             debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
30         }
31
32         let param_env = tcx.param_env(def_id);
33         let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap();
34         let mdpe = MoveDataParamEnv { move_data, param_env };
35
36         if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
37             let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
38                 .into_engine(tcx, body)
39                 .iterate_to_fixpoint();
40
41             sanity_check_via_rustc_peek(tcx, body, &flow_inits);
42         }
43
44         if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
45             let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
46                 .into_engine(tcx, body)
47                 .iterate_to_fixpoint();
48
49             sanity_check_via_rustc_peek(tcx, body, &flow_uninits);
50         }
51
52         if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() {
53             let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
54                 .into_engine(tcx, body)
55                 .iterate_to_fixpoint();
56
57             sanity_check_via_rustc_peek(tcx, body, &flow_def_inits);
58         }
59
60         if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
61             let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
62
63             sanity_check_via_rustc_peek(tcx, body, &flow_liveness);
64         }
65
66         if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
67             tcx.sess.fatal("stop_after_dataflow ended compilation");
68         }
69     }
70 }
71
72 /// This function scans `mir` for all calls to the intrinsic
73 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
74 ///
75 /// For each such call, determines what the dataflow bit-state is for
76 /// the L-value corresponding to `expr`; if the bit-state is a 1, then
77 /// that call to `rustc_peek` is ignored by the sanity check. If the
78 /// bit-state is a 0, then this pass emits an error message saying
79 /// "rustc_peek: bit not set".
80 ///
81 /// The intention is that one can write unit tests for dataflow by
82 /// putting code into a UI test and using `rustc_peek` to
83 /// make observations about the results of dataflow static analyses.
84 ///
85 /// (If there are any calls to `rustc_peek` that do not match the
86 /// expression form above, then that emits an error as well, but those
87 /// errors are not intended to be used for unit tests.)
88 pub fn sanity_check_via_rustc_peek<'tcx, A>(
89     tcx: TyCtxt<'tcx>,
90     body: &Body<'tcx>,
91     results: &Results<'tcx, A>,
92 ) where
93     A: RustcPeekAt<'tcx>,
94 {
95     let def_id = body.source.def_id();
96     debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
97
98     let mut cursor = ResultsCursor::new(body, results);
99
100     let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
101         PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
102     });
103
104     for (bb, block_data, call) in peek_calls {
105         // Look for a sequence like the following to indicate that we should be peeking at `_1`:
106         //    _2 = &_1;
107         //    rustc_peek(_2);
108         //
109         //    /* or */
110         //
111         //    _2 = _1;
112         //    rustc_peek(_2);
113         let (statement_index, peek_rval) = block_data
114             .statements
115             .iter()
116             .enumerate()
117             .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
118             .expect(
119                 "call to rustc_peek should be preceded by \
120                     assignment to temporary holding its argument",
121             );
122
123         match (call.kind, peek_rval) {
124             (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
125             | (
126                 PeekCallKind::ByVal,
127                 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
128             ) => {
129                 let loc = Location { block: bb, statement_index };
130                 cursor.seek_before_primary_effect(loc);
131                 let state = cursor.get();
132                 results.analysis.peek_at(tcx, *place, state, call);
133             }
134
135             _ => {
136                 let msg = "rustc_peek: argument expression \
137                            must be either `place` or `&place`";
138                 tcx.sess.span_err(call.span, msg);
139             }
140         }
141     }
142 }
143
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>,
148     local: Local,
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() {
152             if local == l {
153                 return Some(&*rvalue);
154             }
155         }
156     }
157
158     None
159 }
160
161 #[derive(Clone, Copy, Debug)]
162 enum PeekCallKind {
163     ByVal,
164     ByRef,
165 }
166
167 impl PeekCallKind {
168     fn from_arg_ty(arg: Ty<'_>) -> Self {
169         match arg.kind() {
170             ty::Ref(_, _, _) => PeekCallKind::ByRef,
171             _ => PeekCallKind::ByVal,
172         }
173     }
174 }
175
176 #[derive(Clone, Copy, Debug)]
177 pub struct PeekCall {
178     arg: Local,
179     kind: PeekCallKind,
180     span: Span,
181 }
182
183 impl PeekCall {
184     fn from_terminator<'tcx>(
185         tcx: TyCtxt<'tcx>,
186         terminator: &mir::Terminator<'tcx>,
187     ) -> Option<Self> {
188         use mir::Operand;
189
190         let span = terminator.source_info.span;
191         if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
192             &terminator.kind
193         {
194             if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
195                 let name = tcx.item_name(def_id);
196                 if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek {
197                     return None;
198                 }
199
200                 assert_eq!(args.len(), 1);
201                 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
202                 let arg = match &args[0] {
203                     Operand::Copy(place) | Operand::Move(place) => {
204                         if let Some(local) = place.as_local() {
205                             local
206                         } else {
207                             tcx.sess.diagnostic().span_err(
208                                 span,
209                                 "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
210                             );
211                             return None;
212                         }
213                     }
214                     _ => {
215                         tcx.sess.diagnostic().span_err(
216                             span,
217                             "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
218                         );
219                         return None;
220                     }
221                 };
222
223                 return Some(PeekCall { arg, kind, span });
224             }
225         }
226
227         None
228     }
229 }
230
231 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
232     fn peek_at(
233         &self,
234         tcx: TyCtxt<'tcx>,
235         place: mir::Place<'tcx>,
236         flow_state: &Self::Domain,
237         call: PeekCall,
238     );
239 }
240
241 impl<'tcx, A, D> RustcPeekAt<'tcx> for A
242 where
243     A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
244     D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
245 {
246     fn peek_at(
247         &self,
248         tcx: TyCtxt<'tcx>,
249         place: mir::Place<'tcx>,
250         flow_state: &Self::Domain,
251         call: PeekCall,
252     ) {
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);
257                 if !bit_state {
258                     tcx.sess.span_err(call.span, "rustc_peek: bit not set");
259                 }
260             }
261
262             LookupResult::Parent(..) => {
263                 tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
264             }
265         }
266     }
267 }
268
269 impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
270     fn peek_at(
271         &self,
272         tcx: TyCtxt<'tcx>,
273         place: mir::Place<'tcx>,
274         flow_state: &ChunkedBitSet<Local>,
275         call: PeekCall,
276     ) {
277         info!(?place, "peek_at");
278         let Some(local) = place.as_local() else {
279             tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
280             return;
281         };
282
283         if !flow_state.contains(local) {
284             tcx.sess.span_err(call.span, "rustc_peek: bit not set");
285         }
286     }
287 }