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