]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/rustc_peek.rs
Add comment about the lack of `ExpnData` serialization for proc-macro crates
[rust.git] / src / librustc_mir / transform / rustc_peek.rs
1 use rustc_ast::ast;
2 use rustc_span::symbol::sym;
3 use rustc_span::Span;
4 use rustc_target::spec::abi::Abi;
5
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};
11
12 use crate::dataflow::impls::{
13     DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeMutBorrowedLocals,
14     MaybeUninitializedPlaces,
15 };
16 use crate::dataflow::move_paths::{HasMoveData, MoveData};
17 use crate::dataflow::move_paths::{LookupResult, MovePathIndex};
18 use crate::dataflow::MoveDataParamEnv;
19 use crate::dataflow::{Analysis, Results, ResultsCursor};
20
21 pub struct SanityCheck;
22
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));
29             return;
30         } else {
31             debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
32         }
33
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 };
38
39         if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_init).is_some() {
40             let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
41                 .into_engine(tcx, body, def_id)
42                 .iterate_to_fixpoint();
43
44             sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_inits);
45         }
46
47         if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_uninit).is_some() {
48             let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe)
49                 .into_engine(tcx, body, def_id)
50                 .iterate_to_fixpoint();
51
52             sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_uninits);
53         }
54
55         if has_rustc_mir_with(&attributes, sym::rustc_peek_definite_init).is_some() {
56             let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe)
57                 .into_engine(tcx, body, def_id)
58                 .iterate_to_fixpoint();
59
60             sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_def_inits);
61         }
62
63         if has_rustc_mir_with(&attributes, sym::rustc_peek_indirectly_mutable).is_some() {
64             let flow_mut_borrowed = MaybeMutBorrowedLocals::mut_borrows_only(tcx, body, param_env)
65                 .into_engine(tcx, body, def_id)
66                 .iterate_to_fixpoint();
67
68             sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_mut_borrowed);
69         }
70
71         if has_rustc_mir_with(&attributes, sym::rustc_peek_liveness).is_some() {
72             let flow_liveness =
73                 MaybeLiveLocals.into_engine(tcx, body, def_id).iterate_to_fixpoint();
74
75             sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_liveness);
76         }
77
78         if has_rustc_mir_with(&attributes, sym::stop_after_dataflow).is_some() {
79             tcx.sess.fatal("stop_after_dataflow ended compilation");
80         }
81     }
82 }
83
84 /// This function scans `mir` for all calls to the intrinsic
85 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
86 ///
87 /// For each such call, determines what the dataflow bit-state is for
88 /// the L-value corresponding to `expr`; if the bit-state is a 1, then
89 /// that call to `rustc_peek` is ignored by the sanity check. If the
90 /// bit-state is a 0, then this pass emits a error message saying
91 /// "rustc_peek: bit not set".
92 ///
93 /// The intention is that one can write unit tests for dataflow by
94 /// putting code into a compile-fail test and using `rustc_peek` to
95 /// make observations about the results of dataflow static analyses.
96 ///
97 /// (If there are any calls to `rustc_peek` that do not match the
98 /// expression form above, then that emits an error as well, but those
99 /// errors are not intended to be used for unit tests.)
100 pub fn sanity_check_via_rustc_peek<'tcx, A>(
101     tcx: TyCtxt<'tcx>,
102     body: &Body<'tcx>,
103     def_id: DefId,
104     _attributes: &[ast::Attribute],
105     results: &Results<'tcx, A>,
106 ) where
107     A: RustcPeekAt<'tcx>,
108 {
109     debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
110
111     let mut cursor = ResultsCursor::new(body, results);
112
113     let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
114         PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
115     });
116
117     for (bb, block_data, call) in peek_calls {
118         // Look for a sequence like the following to indicate that we should be peeking at `_1`:
119         //    _2 = &_1;
120         //    rustc_peek(_2);
121         //
122         //    /* or */
123         //
124         //    _2 = _1;
125         //    rustc_peek(_2);
126         let (statement_index, peek_rval) = block_data
127             .statements
128             .iter()
129             .enumerate()
130             .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
131             .expect(
132                 "call to rustc_peek should be preceded by \
133                     assignment to temporary holding its argument",
134             );
135
136         match (call.kind, peek_rval) {
137             (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
138             | (
139                 PeekCallKind::ByVal,
140                 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
141             ) => {
142                 let loc = Location { block: bb, statement_index };
143                 cursor.seek_before_primary_effect(loc);
144                 let state = cursor.get();
145                 results.analysis.peek_at(tcx, *place, state, call);
146             }
147
148             _ => {
149                 let msg = "rustc_peek: argument expression \
150                            must be either `place` or `&place`";
151                 tcx.sess.span_err(call.span, msg);
152             }
153         }
154     }
155 }
156
157 /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
158 /// RHS of the assignment.
159 fn value_assigned_to_local<'a, 'tcx>(
160     stmt: &'a mir::Statement<'tcx>,
161     local: Local,
162 ) -> Option<&'a mir::Rvalue<'tcx>> {
163     if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
164         if let Some(l) = place.as_local() {
165             if local == l {
166                 return Some(&*rvalue);
167             }
168         }
169     }
170
171     None
172 }
173
174 #[derive(Clone, Copy, Debug)]
175 enum PeekCallKind {
176     ByVal,
177     ByRef,
178 }
179
180 impl PeekCallKind {
181     fn from_arg_ty(arg: Ty<'_>) -> Self {
182         match arg.kind {
183             ty::Ref(_, _, _) => PeekCallKind::ByRef,
184             _ => PeekCallKind::ByVal,
185         }
186     }
187 }
188
189 #[derive(Clone, Copy, Debug)]
190 pub struct PeekCall {
191     arg: Local,
192     kind: PeekCallKind,
193     span: Span,
194 }
195
196 impl PeekCall {
197     fn from_terminator<'tcx>(
198         tcx: TyCtxt<'tcx>,
199         terminator: &mir::Terminator<'tcx>,
200     ) -> Option<Self> {
201         use mir::Operand;
202
203         let span = terminator.source_info.span;
204         if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
205             &terminator.kind
206         {
207             if let ty::FnDef(def_id, substs) = func.literal.ty.kind {
208                 let sig = tcx.fn_sig(def_id);
209                 let name = tcx.item_name(def_id);
210                 if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
211                     return None;
212                 }
213
214                 assert_eq!(args.len(), 1);
215                 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
216                 let arg = match &args[0] {
217                     Operand::Copy(place) | Operand::Move(place) => {
218                         if let Some(local) = place.as_local() {
219                             local
220                         } else {
221                             tcx.sess.diagnostic().span_err(
222                                 span,
223                                 "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
224                             );
225                             return None;
226                         }
227                     }
228                     _ => {
229                         tcx.sess.diagnostic().span_err(
230                             span,
231                             "dataflow::sanity_check cannot feed a non-temp to rustc_peek.",
232                         );
233                         return None;
234                     }
235                 };
236
237                 return Some(PeekCall { arg, kind, span });
238             }
239         }
240
241         None
242     }
243 }
244
245 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
246     fn peek_at(
247         &self,
248         tcx: TyCtxt<'tcx>,
249         place: mir::Place<'tcx>,
250         flow_state: &BitSet<Self::Idx>,
251         call: PeekCall,
252     );
253 }
254
255 impl<'tcx, A> RustcPeekAt<'tcx> for A
256 where
257     A: Analysis<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
258 {
259     fn peek_at(
260         &self,
261         tcx: TyCtxt<'tcx>,
262         place: mir::Place<'tcx>,
263         flow_state: &BitSet<Self::Idx>,
264         call: PeekCall,
265     ) {
266         match self.move_data().rev_lookup.find(place.as_ref()) {
267             LookupResult::Exact(peek_mpi) => {
268                 let bit_state = flow_state.contains(peek_mpi);
269                 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
270                 if !bit_state {
271                     tcx.sess.span_err(call.span, "rustc_peek: bit not set");
272                 }
273             }
274
275             LookupResult::Parent(..) => {
276                 tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
277             }
278         }
279     }
280 }
281
282 impl<'tcx> RustcPeekAt<'tcx> for MaybeMutBorrowedLocals<'_, 'tcx> {
283     fn peek_at(
284         &self,
285         tcx: TyCtxt<'tcx>,
286         place: mir::Place<'tcx>,
287         flow_state: &BitSet<Local>,
288         call: PeekCall,
289     ) {
290         warn!("peek_at: place={:?}", place);
291         let local = if let Some(l) = place.as_local() {
292             l
293         } else {
294             tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
295             return;
296         };
297
298         if !flow_state.contains(local) {
299             tcx.sess.span_err(call.span, "rustc_peek: bit not set");
300         }
301     }
302 }
303
304 impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
305     fn peek_at(
306         &self,
307         tcx: TyCtxt<'tcx>,
308         place: mir::Place<'tcx>,
309         flow_state: &BitSet<Local>,
310         call: PeekCall,
311     ) {
312         warn!("peek_at: place={:?}", place);
313         let local = if let Some(l) = place.as_local() {
314             l
315         } else {
316             tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
317             return;
318         };
319
320         if !flow_state.contains(local) {
321             tcx.sess.span_err(call.span, "rustc_peek: bit not set");
322         }
323     }
324 }