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