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