rustc_mir_dataflow/
rustc_peek.rs

1use rustc_ast::MetaItem;
2use rustc_hir::def_id::DefId;
3use rustc_middle::mir::{self, Body, Local, Location};
4use rustc_middle::ty::{self, Ty, TyCtxt};
5use rustc_span::{Span, Symbol, sym};
6use tracing::{debug, info};
7
8use crate::errors::{
9    PeekArgumentNotALocal, PeekArgumentUntracked, PeekBitNotSet, PeekMustBeNotTemporary,
10    PeekMustBePlaceOrRefPlace, StopAfterDataFlowEndedCompilation,
11};
12use crate::framework::BitSetExt;
13use crate::impls::{MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces};
14use crate::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
15use crate::{Analysis, JoinSemiLattice, ResultsCursor};
16
17fn has_rustc_mir_with(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Option<MetaItem> {
18    for attr in tcx.get_attrs(def_id, sym::rustc_mir) {
19        let items = attr.meta_item_list();
20        for item in items.iter().flat_map(|l| l.iter()) {
21            match item.meta_item() {
22                Some(mi) if mi.has_name(name) => return Some(mi.clone()),
23                _ => continue,
24            }
25        }
26    }
27    None
28}
29
30pub fn sanity_check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
31    let def_id = body.source.def_id();
32    if !tcx.has_attr(def_id, sym::rustc_mir) {
33        debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
34        return;
35    } else {
36        debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
37    }
38
39    let move_data = MoveData::gather_moves(body, tcx, |_| true);
40
41    if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {
42        let flow_inits =
43            MaybeInitializedPlaces::new(tcx, body, &move_data).iterate_to_fixpoint(tcx, body, None);
44
45        sanity_check_via_rustc_peek(tcx, flow_inits.into_results_cursor(body));
46    }
47
48    if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() {
49        let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data)
50            .iterate_to_fixpoint(tcx, body, None);
51
52        sanity_check_via_rustc_peek(tcx, flow_uninits.into_results_cursor(body));
53    }
54
55    if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() {
56        let flow_liveness = MaybeLiveLocals.iterate_to_fixpoint(tcx, body, None);
57
58        sanity_check_via_rustc_peek(tcx, flow_liveness.into_results_cursor(body));
59    }
60
61    if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() {
62        tcx.dcx().emit_fatal(StopAfterDataFlowEndedCompilation);
63    }
64}
65
66/// This function scans `mir` for all calls to the intrinsic
67/// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
68///
69/// For each such call, determines what the dataflow bit-state is for
70/// the L-value corresponding to `expr`; if the bit-state is a 1, then
71/// that call to `rustc_peek` is ignored by the sanity check. If the
72/// bit-state is a 0, then this pass emits an error message saying
73/// "rustc_peek: bit not set".
74///
75/// The intention is that one can write unit tests for dataflow by
76/// putting code into a UI test and using `rustc_peek` to
77/// make observations about the results of dataflow static analyses.
78///
79/// (If there are any calls to `rustc_peek` that do not match the
80/// expression form above, then that emits an error as well, but those
81/// errors are not intended to be used for unit tests.)
82fn sanity_check_via_rustc_peek<'tcx, A>(tcx: TyCtxt<'tcx>, mut cursor: ResultsCursor<'_, 'tcx, A>)
83where
84    A: RustcPeekAt<'tcx>,
85{
86    let def_id = cursor.body().source.def_id();
87    debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
88
89    let peek_calls = cursor.body().basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| {
90        PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
91    });
92
93    for (bb, block_data, call) in peek_calls {
94        // Look for a sequence like the following to indicate that we should be peeking at `_1`:
95        //    _2 = &_1;
96        //    rustc_peek(_2);
97        //
98        //    /* or */
99        //
100        //    _2 = _1;
101        //    rustc_peek(_2);
102        let (statement_index, peek_rval) = block_data
103            .statements
104            .iter()
105            .enumerate()
106            .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
107            .expect(
108                "call to rustc_peek should be preceded by \
109                    assignment to temporary holding its argument",
110            );
111
112        match (call.kind, peek_rval) {
113            (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
114            | (
115                PeekCallKind::ByVal,
116                mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
117            ) => {
118                let loc = Location { block: bb, statement_index };
119                cursor.seek_before_primary_effect(loc);
120                let state = cursor.get();
121                let analysis = cursor.analysis();
122                analysis.peek_at(tcx, *place, state, call);
123            }
124
125            _ => {
126                tcx.dcx().emit_err(PeekMustBePlaceOrRefPlace { span: call.span });
127            }
128        }
129    }
130}
131
132/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
133/// RHS of the assignment.
134fn value_assigned_to_local<'a, 'tcx>(
135    stmt: &'a mir::Statement<'tcx>,
136    local: Local,
137) -> Option<&'a mir::Rvalue<'tcx>> {
138    if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
139        if let Some(l) = place.as_local() {
140            if local == l {
141                return Some(&*rvalue);
142            }
143        }
144    }
145
146    None
147}
148
149#[derive(Clone, Copy, Debug)]
150enum PeekCallKind {
151    ByVal,
152    ByRef,
153}
154
155impl PeekCallKind {
156    fn from_arg_ty(arg: Ty<'_>) -> Self {
157        match arg.kind() {
158            ty::Ref(_, _, _) => PeekCallKind::ByRef,
159            _ => PeekCallKind::ByVal,
160        }
161    }
162}
163
164#[derive(Clone, Copy, Debug)]
165struct PeekCall {
166    arg: Local,
167    kind: PeekCallKind,
168    span: Span,
169}
170
171impl PeekCall {
172    fn from_terminator<'tcx>(
173        tcx: TyCtxt<'tcx>,
174        terminator: &mir::Terminator<'tcx>,
175    ) -> Option<Self> {
176        use mir::Operand;
177
178        let span = terminator.source_info.span;
179        if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
180            &terminator.kind
181        {
182            if let ty::FnDef(def_id, fn_args) = *func.const_.ty().kind() {
183                if tcx.intrinsic(def_id)?.name != sym::rustc_peek {
184                    return None;
185                }
186
187                assert_eq!(fn_args.len(), 1);
188                let kind = PeekCallKind::from_arg_ty(fn_args.type_at(0));
189                let arg = match &args[0].node {
190                    Operand::Copy(place) | Operand::Move(place) => {
191                        if let Some(local) = place.as_local() {
192                            local
193                        } else {
194                            tcx.dcx().emit_err(PeekMustBeNotTemporary { span });
195                            return None;
196                        }
197                    }
198                    _ => {
199                        tcx.dcx().emit_err(PeekMustBeNotTemporary { span });
200                        return None;
201                    }
202                };
203
204                return Some(PeekCall { arg, kind, span });
205            }
206        }
207
208        None
209    }
210}
211
212trait RustcPeekAt<'tcx>: Analysis<'tcx> {
213    fn peek_at(
214        &self,
215        tcx: TyCtxt<'tcx>,
216        place: mir::Place<'tcx>,
217        state: &Self::Domain,
218        call: PeekCall,
219    );
220}
221
222impl<'tcx, A, D> RustcPeekAt<'tcx> for A
223where
224    A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
225    D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
226{
227    fn peek_at(
228        &self,
229        tcx: TyCtxt<'tcx>,
230        place: mir::Place<'tcx>,
231        state: &Self::Domain,
232        call: PeekCall,
233    ) {
234        match self.move_data().rev_lookup.find(place.as_ref()) {
235            LookupResult::Exact(peek_mpi) => {
236                let bit_state = state.contains(peek_mpi);
237                debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
238                if !bit_state {
239                    tcx.dcx().emit_err(PeekBitNotSet { span: call.span });
240                }
241            }
242
243            LookupResult::Parent(..) => {
244                tcx.dcx().emit_err(PeekArgumentUntracked { span: call.span });
245            }
246        }
247    }
248}
249
250impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
251    fn peek_at(
252        &self,
253        tcx: TyCtxt<'tcx>,
254        place: mir::Place<'tcx>,
255        state: &Self::Domain,
256        call: PeekCall,
257    ) {
258        info!(?place, "peek_at");
259        let Some(local) = place.as_local() else {
260            tcx.dcx().emit_err(PeekArgumentNotALocal { span: call.span });
261            return;
262        };
263
264        if !state.contains(local) {
265            tcx.dcx().emit_err(PeekBitNotSet { span: call.span });
266        }
267    }
268}