]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_dataflow/src/rustc_peek.rs
Rollup merge of #92775 - xfix:osstringext-inline, r=m-ou-se
[rust.git] / compiler / rustc_mir_dataflow / src / 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 rustc_index::bit_set::BitSet;
9 use rustc_middle::mir::MirPass;
10 use rustc_middle::mir::{self, Body, Local, Location};
11 use rustc_middle::ty::{self, Ty, TyCtxt};
12
13 use crate::impls::{
14     DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
15 };
16 use crate::move_paths::{HasMoveData, MoveData};
17 use crate::move_paths::{LookupResult, MovePathIndex};
18 use crate::MoveDataParamEnv;
19 use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor};
20
21 pub struct SanityCheck;
22
23 // FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
24 impl<'tcx> MirPass<'tcx> for SanityCheck {
25     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
26         use crate::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_liveness).is_some() {
66             let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint();
67
68             sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_liveness);
69         }
70
71         if has_rustc_mir_with(sess, &attributes, sym::stop_after_dataflow).is_some() {
72             tcx.sess.fatal("stop_after_dataflow ended compilation");
73         }
74     }
75 }
76
77 /// This function scans `mir` for all calls to the intrinsic
78 /// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
79 ///
80 /// For each such call, determines what the dataflow bit-state is for
81 /// the L-value corresponding to `expr`; if the bit-state is a 1, then
82 /// that call to `rustc_peek` is ignored by the sanity check. If the
83 /// bit-state is a 0, then this pass emits an error message saying
84 /// "rustc_peek: bit not set".
85 ///
86 /// The intention is that one can write unit tests for dataflow by
87 /// putting code into a UI test and using `rustc_peek` to
88 /// make observations about the results of dataflow static analyses.
89 ///
90 /// (If there are any calls to `rustc_peek` that do not match the
91 /// expression form above, then that emits an error as well, but those
92 /// errors are not intended to be used for unit tests.)
93 pub fn sanity_check_via_rustc_peek<'tcx, A>(
94     tcx: TyCtxt<'tcx>,
95     body: &Body<'tcx>,
96     _attributes: &[ast::Attribute],
97     results: &Results<'tcx, A>,
98 ) where
99     A: RustcPeekAt<'tcx>,
100 {
101     let def_id = body.source.def_id();
102     debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
103
104     let mut cursor = ResultsCursor::new(body, results);
105
106     let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| {
107         PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
108     });
109
110     for (bb, block_data, call) in peek_calls {
111         // Look for a sequence like the following to indicate that we should be peeking at `_1`:
112         //    _2 = &_1;
113         //    rustc_peek(_2);
114         //
115         //    /* or */
116         //
117         //    _2 = _1;
118         //    rustc_peek(_2);
119         let (statement_index, peek_rval) = block_data
120             .statements
121             .iter()
122             .enumerate()
123             .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
124             .expect(
125                 "call to rustc_peek should be preceded by \
126                     assignment to temporary holding its argument",
127             );
128
129         match (call.kind, peek_rval) {
130             (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
131             | (
132                 PeekCallKind::ByVal,
133                 mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
134             ) => {
135                 let loc = Location { block: bb, statement_index };
136                 cursor.seek_before_primary_effect(loc);
137                 let state = cursor.get();
138                 results.analysis.peek_at(tcx, *place, state, call);
139             }
140
141             _ => {
142                 let msg = "rustc_peek: argument expression \
143                            must be either `place` or `&place`";
144                 tcx.sess.span_err(call.span, msg);
145             }
146         }
147     }
148 }
149
150 /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
151 /// RHS of the assignment.
152 fn value_assigned_to_local<'a, 'tcx>(
153     stmt: &'a mir::Statement<'tcx>,
154     local: Local,
155 ) -> Option<&'a mir::Rvalue<'tcx>> {
156     if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
157         if let Some(l) = place.as_local() {
158             if local == l {
159                 return Some(&*rvalue);
160             }
161         }
162     }
163
164     None
165 }
166
167 #[derive(Clone, Copy, Debug)]
168 enum PeekCallKind {
169     ByVal,
170     ByRef,
171 }
172
173 impl PeekCallKind {
174     fn from_arg_ty(arg: Ty<'_>) -> Self {
175         match arg.kind() {
176             ty::Ref(_, _, _) => PeekCallKind::ByRef,
177             _ => PeekCallKind::ByVal,
178         }
179     }
180 }
181
182 #[derive(Clone, Copy, Debug)]
183 pub struct PeekCall {
184     arg: Local,
185     kind: PeekCallKind,
186     span: Span,
187 }
188
189 impl PeekCall {
190     fn from_terminator<'tcx>(
191         tcx: TyCtxt<'tcx>,
192         terminator: &mir::Terminator<'tcx>,
193     ) -> Option<Self> {
194         use mir::Operand;
195
196         let span = terminator.source_info.span;
197         if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
198             &terminator.kind
199         {
200             if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() {
201                 let sig = tcx.fn_sig(def_id);
202                 let name = tcx.item_name(def_id);
203                 if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
204                     return None;
205                 }
206
207                 assert_eq!(args.len(), 1);
208                 let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
209                 let arg = match &args[0] {
210                     Operand::Copy(place) | Operand::Move(place) => {
211                         if let Some(local) = place.as_local() {
212                             local
213                         } else {
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                         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                 return Some(PeekCall { arg, kind, span });
231             }
232         }
233
234         None
235     }
236 }
237
238 pub trait RustcPeekAt<'tcx>: Analysis<'tcx> {
239     fn peek_at(
240         &self,
241         tcx: TyCtxt<'tcx>,
242         place: mir::Place<'tcx>,
243         flow_state: &Self::Domain,
244         call: PeekCall,
245     );
246 }
247
248 impl<'tcx, A, D> RustcPeekAt<'tcx> for A
249 where
250     A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
251     D: JoinSemiLattice + Clone + Borrow<BitSet<MovePathIndex>>,
252 {
253     fn peek_at(
254         &self,
255         tcx: TyCtxt<'tcx>,
256         place: mir::Place<'tcx>,
257         flow_state: &Self::Domain,
258         call: PeekCall,
259     ) {
260         match self.move_data().rev_lookup.find(place.as_ref()) {
261             LookupResult::Exact(peek_mpi) => {
262                 let bit_state = flow_state.borrow().contains(peek_mpi);
263                 debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
264                 if !bit_state {
265                     tcx.sess.span_err(call.span, "rustc_peek: bit not set");
266                 }
267             }
268
269             LookupResult::Parent(..) => {
270                 tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
271             }
272         }
273     }
274 }
275
276 impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
277     fn peek_at(
278         &self,
279         tcx: TyCtxt<'tcx>,
280         place: mir::Place<'tcx>,
281         flow_state: &BitSet<Local>,
282         call: PeekCall,
283     ) {
284         info!(?place, "peek_at");
285         let Some(local) = place.as_local() else {
286             tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
287             return;
288         };
289
290         if !flow_state.contains(local) {
291             tcx.sess.span_err(call.span, "rustc_peek: bit not set");
292         }
293     }
294 }