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