Skip to main content

flux_refineck/ghost_statements/
points_to.rs

1//! This module implements a points-to analysis for mutable references.
2//!
3//! We use the result of the analysis to insert ghost statements at the points where pointers (`ptr(l)`)
4//! have to be converted to borrows (`&mut T`). For example, consider the function
5//! ```ignore
6//! fn foo(mut x: i32, mut y: i32, b: bool) {
7//!     let r;
8//!     if b {
9//!         r = &mut x
10//!     } else {
11//!         r = &mut y
12//!     }
13//! }
14//! ```
15//! In the then branch (resp. else) we know `r` must point to `x` (resp. `y`). Thus, during refinement
16//! type checking, we give `r` types `ptr(x)` and `ptr(y)` in each branch respectively. However, at the
17//! join point, `r` could point to either `x` or `y` so we must find a type that joins the two pointers.
18//! We use the result of the analysis to insert a ghost statement at the end of each branch to convert
19//! the pointer to a borrow `&mut i32{v: ...}` and use it as the join.
20use std::{collections::VecDeque, fmt, iter, ops::Range};
21
22use flux_common::tracked_span_bug;
23use flux_middle::{
24    global_env::GlobalEnv,
25    queries::QueryResult,
26    rty::{self, Loc, Path},
27};
28use rustc_abi::FieldIdx;
29use rustc_data_structures::stack::ensure_sufficient_stack;
30use rustc_hash::FxHashMap;
31use rustc_index::{IndexSlice, IndexVec, bit_set::DenseBitSet};
32use rustc_middle::{
33    mir::{self, BasicBlock, TerminatorEdges, visit::Visitor},
34    ty,
35};
36use rustc_mir_dataflow::{
37    Analysis, JoinSemiLattice, ResultsVisitor,
38    fmt::DebugWithContext,
39    lattice::{FlatSet, HasBottom, HasTop},
40    visit_reachable_results,
41};
42
43use super::GhostStatements;
44use crate::ghost_statements::{GhostStatement, Point};
45
46pub(crate) fn add_ghost_statements<'tcx>(
47    stmts: &mut GhostStatements,
48    genv: GlobalEnv<'_, 'tcx>,
49    body: &mir::Body<'tcx>,
50    fn_sig: Option<&rty::EarlyBinder<rty::PolyFnSig>>,
51) -> QueryResult {
52    let map = Map::new(body);
53    let points_to = PointsToAnalysis::new(&map, fn_sig);
54    let results = points_to.iterate_to_fixpoint(genv.tcx(), body, None);
55    let mut visitor = CollectPointerToBorrows::new(&map, stmts, &results.entry_states);
56    visit_reachable_results(body, &results, &mut visitor);
57
58    Ok(())
59}
60
61/// This implement a points to analysis for mutable references over a [`FlatSet`]. The analysis is
62/// a may analysis. If you want to know if a reference definitively points to a location you have to
63/// combine it with the result of a definitely initialized analysis. See module level documentation
64/// for more details.
65struct PointsToAnalysis<'a> {
66    fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>,
67    map: &'a Map,
68}
69
70impl<'a> PointsToAnalysis<'a> {
71    fn new(map: &'a Map, fn_sig: Option<&'a rty::EarlyBinder<rty::PolyFnSig>>) -> Self {
72        Self { fn_sig, map }
73    }
74
75    fn handle_statement(&self, statement: &mir::Statement, state: &mut State) {
76        match &statement.kind {
77            mir::StatementKind::Assign(box (target, rvalue)) => {
78                self.handle_assign(*target, rvalue, state);
79            }
80            mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) => {
81                // StorageLive leaves the local in an uninitialized state.
82                // StorageDead makes it UB to access the local afterwards.
83                state.flood_with(mir::Place::from(*local).as_ref(), self.map, FlatSet::BOTTOM);
84            }
85            mir::StatementKind::Retag(..)
86            | mir::StatementKind::Intrinsic(..)
87            | mir::StatementKind::SetDiscriminant { .. }
88            | mir::StatementKind::ConstEvalCounter
89            | mir::StatementKind::Nop
90            | mir::StatementKind::FakeRead(..)
91            | mir::StatementKind::PlaceMention(..)
92            | mir::StatementKind::Coverage(..)
93            | mir::StatementKind::AscribeUserType(..)
94            | mir::StatementKind::BackwardIncompatibleDropHint { .. } => {}
95        }
96    }
97
98    fn handle_assign(&self, target: mir::Place, rvalue: &mir::Rvalue, state: &mut State) {
99        match rvalue {
100            mir::Rvalue::Use(operand) => {
101                let result = self
102                    .handle_operand(operand)
103                    .map_or(PlaceOrValue::TOP, PlaceOrValue::Place);
104                state.assign(target.as_ref(), result, self.map);
105            }
106            mir::Rvalue::Ref(_, _, place) => {
107                let result = PlaceOrValue::Value(self.handle_ref(place, state));
108                state.assign(target.as_ref(), result, self.map);
109            }
110            mir::Rvalue::Aggregate(box mir::AggregateKind::Tuple, operands) => {
111                state.flood(target.as_ref(), self.map);
112                let Some(target_idx) = self.map.find(target.as_ref()) else { return };
113                for (elem, operand) in operands.iter_enumerated() {
114                    let Some(rhs_idx) = self.handle_operand(operand) else { continue };
115                    if let Some(field) = self.map.apply(target_idx, elem) {
116                        state.insert_place_idx(field, rhs_idx, self.map);
117                    }
118                }
119            }
120            // Conservatively assume the target may point anywhere. Flooding is necessary, or a
121            // place assigned by an unmodeled r-value, e.g., an unsizing cast, would keep the `⊥`
122            // it was given by `StorageLive`.
123            _ => state.flood(target.as_ref(), self.map),
124        }
125    }
126
127    /// This mirrors the way `PlacesTree::lookup_inner` walks a place: a dereference moves the root
128    /// of the path to whatever the pointer points to, and fields are accumulated onto it.
129    fn handle_ref(&self, place: &mir::Place, state: &State) -> FlatSet<Path> {
130        let mut loc = Loc::Local(place.local);
131        let mut projection = vec![];
132        for (i, elem) in place.projection.iter().enumerate() {
133            match elem {
134                mir::PlaceElem::Deref => {
135                    // Move the root to the path pointed to by the prefix we've walked so far.
136                    let prefix =
137                        mir::PlaceRef { local: place.local, projection: &place.projection[..i] };
138                    match state.get(prefix, self.map) {
139                        FlatSet::Elem(path) => {
140                            loc = path.loc;
141                            projection = path.projection().to_vec();
142                        }
143                        FlatSet::Top => return FlatSet::TOP,
144                        // `⊥` means the place is uninitialized, which can't be dereferenced.
145                        FlatSet::Bottom => tracked_span_bug!("dereference of uninitialized place"),
146                    }
147                }
148                mir::PlaceElem::Field(field, _) => projection.push(field),
149                mir::PlaceElem::Downcast(..) => {}
150                _ => return FlatSet::TOP,
151            }
152        }
153        FlatSet::Elem(Path::new(loc, projection))
154    }
155
156    fn handle_operand(&self, operand: &mir::Operand) -> Option<PlaceIndex> {
157        match operand {
158            mir::Operand::Copy(place) | mir::Operand::Move(place) => {
159                // On move, we would ideally flood the place with bottom. But with the current
160                // framework this is not possible (similar to `InterpCx::eval_operand`).
161                self.map.find(place.as_ref())
162            }
163            mir::Operand::Constant(..) | mir::Operand::RuntimeChecks(_) => None,
164        }
165    }
166
167    /// The effect of a successful function call return should not be
168    /// applied here, see [`Analysis::apply_primary_terminator_effect`].
169    fn handle_terminator<'mir, 'tcx>(
170        &self,
171        terminator: &'mir mir::Terminator<'tcx>,
172        state: &mut State,
173    ) -> TerminatorEdges<'mir, 'tcx> {
174        match &terminator.kind {
175            mir::TerminatorKind::TailCall { .. }
176            | mir::TerminatorKind::Call { .. }
177            | mir::TerminatorKind::InlineAsm { .. } => {
178                // Effect is applied by `handle_call_return`.
179            }
180            mir::TerminatorKind::Drop { place, .. } => {
181                state.flood_with(place.as_ref(), self.map, FlatSet::BOTTOM);
182            }
183            mir::TerminatorKind::SwitchInt { .. }
184            | mir::TerminatorKind::Yield { .. }
185            | mir::TerminatorKind::Goto { .. }
186            | mir::TerminatorKind::UnwindResume
187            | mir::TerminatorKind::UnwindTerminate(_)
188            | mir::TerminatorKind::Return
189            | mir::TerminatorKind::Unreachable
190            | mir::TerminatorKind::Assert { .. }
191            | mir::TerminatorKind::CoroutineDrop
192            | mir::TerminatorKind::FalseEdge { .. }
193            | mir::TerminatorKind::FalseUnwind { .. } => {
194                // These terminators have no effect on the analysis.
195            }
196        }
197        terminator.edges()
198    }
199
200    fn handle_call_return(&self, return_places: mir::CallReturnPlaces, state: &mut State) {
201        return_places.for_each(|place| {
202            state.flood(place.as_ref(), self.map);
203        });
204    }
205}
206
207impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for PointsToAnalysis<'_> {
208    type Domain = State;
209
210    const NAME: &'static str = "PointsToAnalysis";
211
212    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
213        State { values: IndexVec::from_elem_n(FlatSet::BOTTOM, self.map.value_count) }
214    }
215
216    fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
217        // Since we are skipping the early binder, we are using the early bound variables as locs instead
218        // of fresh names. This is fine because the loc is just used as a unique value for the analysis.
219        // We never have late bounds locs.
220        if let Some(fn_sig) = self.fn_sig {
221            let fn_sig = fn_sig.as_ref().skip_binder().as_ref().skip_binder();
222            for (local, ty) in iter::zip(body.args_iter(), fn_sig.inputs()) {
223                if let rty::TyKind::Ptr(_, path) = ty.kind() {
224                    let path = FlatSet::Elem(path.clone());
225                    state.flood_with(mir::PlaceRef { local, projection: &[] }, self.map, path);
226                } else {
227                    state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
228                }
229            }
230        } else {
231            for local in body.args_iter() {
232                state.flood(mir::PlaceRef { local, projection: &[] }, self.map);
233            }
234        }
235    }
236
237    fn apply_primary_statement_effect(
238        &self,
239        state: &mut Self::Domain,
240        statement: &mir::Statement<'tcx>,
241        _location: mir::Location,
242    ) {
243        self.handle_statement(statement, state);
244    }
245
246    fn apply_primary_terminator_effect<'mir>(
247        &self,
248        state: &mut Self::Domain,
249        terminator: &'mir mir::Terminator<'tcx>,
250        _location: mir::Location,
251    ) -> mir::TerminatorEdges<'mir, 'tcx> {
252        self.handle_terminator(terminator, state)
253    }
254
255    fn apply_call_return_effect(
256        &self,
257        state: &mut Self::Domain,
258        _block: BasicBlock,
259        return_places: mir::CallReturnPlaces<'_, 'tcx>,
260    ) {
261        self.handle_call_return(return_places, state);
262    }
263}
264
265struct CollectPointerToBorrows<'a> {
266    map: &'a Map,
267    tracked_places: FxHashMap<PlaceIndex, flux_rustc_bridge::mir::Place>,
268    stmts: &'a mut GhostStatements,
269    before_state: Vec<(PlaceIndex, FlatSet<Path>)>,
270    results: &'a IndexSlice<BasicBlock, State>,
271}
272
273impl<'a> CollectPointerToBorrows<'a> {
274    fn new(
275        map: &'a Map,
276        stmts: &'a mut GhostStatements,
277        results: &'a IndexSlice<BasicBlock, State>,
278    ) -> Self {
279        let mut tracked_places = FxHashMap::default();
280        map.for_each_tracked_place(|place_idx, local, projection| {
281            let projection = projection
282                .iter()
283                .copied()
284                .map(flux_rustc_bridge::mir::PlaceElem::Field)
285                .collect();
286            tracked_places.insert(place_idx, flux_rustc_bridge::mir::Place::new(local, projection));
287        });
288        Self { map, tracked_places, stmts, before_state: vec![], results }
289    }
290}
291
292impl<'a, 'tcx> ResultsVisitor<'tcx, PointsToAnalysis<'a>> for CollectPointerToBorrows<'_> {
293    fn visit_block_start(&mut self, state: &State) {
294        self.before_state.clear();
295        for place_idx in self.tracked_places.keys() {
296            let value = state.get_idx(*place_idx, self.map);
297            self.before_state.push((*place_idx, value));
298        }
299    }
300
301    fn visit_after_primary_statement_effect(
302        &mut self,
303        _analysis: &PointsToAnalysis<'a>,
304        state: &State,
305        _statement: &mir::Statement<'tcx>,
306        location: mir::Location,
307    ) {
308        let point = Point::BeforeLocation(location);
309        for (place_idx, old_value) in &mut self.before_state {
310            let new_value = state.get_idx(*place_idx, self.map);
311            if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, &new_value) {
312                let place = self
313                    .tracked_places
314                    .get(place_idx)
315                    .unwrap_or_else(|| tracked_span_bug!())
316                    .clone();
317                self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
318            }
319            *old_value = new_value;
320        }
321    }
322
323    fn visit_after_primary_terminator_effect(
324        &mut self,
325        _analysis: &PointsToAnalysis<'a>,
326        _state: &State,
327        terminator: &mir::Terminator<'tcx>,
328        location: mir::Location,
329    ) {
330        let block = location.block;
331        for target in terminator.successors() {
332            let point = Point::Edge(block, target);
333            let target_state = self.results.get(target).unwrap();
334            for (place_idx, old_value) in &self.before_state {
335                let new_value = target_state.get_idx(*place_idx, self.map);
336                if let (FlatSet::Elem(_), FlatSet::Top) = (&old_value, new_value) {
337                    let place = self
338                        .tracked_places
339                        .get(place_idx)
340                        .unwrap_or_else(|| tracked_span_bug!())
341                        .clone();
342                    self.stmts.insert_at(point, GhostStatement::PtrToRef(place));
343                }
344            }
345        }
346    }
347}
348
349/// Partial mapping from `Place` to [`PlaceIndex`], where some places also have a [`ValueIndex`].
350///
351/// This data structure essentially maintains a tree of places and their projections. Some
352/// additional bookkeeping is done, to speed up traversal over this tree:
353/// - For iteration, every [`PlaceInfo`] contains an intrusive linked list of its children.
354/// - To directly get the child for a specific projection, there is a `projections` map.
355#[derive(Debug)]
356pub struct Map {
357    locals: IndexVec<mir::Local, Option<PlaceIndex>>,
358    projections: FxHashMap<(PlaceIndex, FieldIdx), PlaceIndex>,
359    places: IndexVec<PlaceIndex, PlaceInfo>,
360    value_count: usize,
361    // The Range corresponds to a slice into `inner_values_buffer`.
362    inner_values: IndexVec<PlaceIndex, Range<usize>>,
363    inner_values_buffer: Vec<ValueIndex>,
364}
365
366impl Map {
367    /// Returns a map that only tracks places whose type has scalar layout.
368    ///
369    /// This is currently the only way to create a [`Map`]. The way in which the tracked places are
370    /// chosen is an implementation detail and may not be relied upon (other than that their type
371    /// are scalars).
372    fn new(body: &mir::Body) -> Self {
373        let mut map = Self {
374            locals: IndexVec::new(),
375            projections: FxHashMap::default(),
376            places: IndexVec::new(),
377            value_count: 0,
378            inner_values: IndexVec::new(),
379            inner_values_buffer: Vec::new(),
380        };
381        let exclude = excluded_locals(body);
382        map.register(body, exclude);
383        map
384    }
385
386    /// Register all non-excluded places that have scalar layout.
387    fn register(&mut self, body: &mir::Body, exclude: DenseBitSet<mir::Local>) {
388        let mut worklist = VecDeque::with_capacity(body.local_decls.len());
389
390        // Start by constructing the places for each bare local.
391        self.locals = IndexVec::from_elem(None, &body.local_decls);
392        for (local, decl) in body.local_decls.iter_enumerated() {
393            if exclude.contains(local) {
394                continue;
395            }
396
397            // Create a place for the local.
398            debug_assert!(self.locals[local].is_none());
399            let place = self.places.push(PlaceInfo::new(None));
400            self.locals[local] = Some(place);
401
402            // And push the eventual children places to the worklist.
403            self.register_children(place, decl.ty, &mut worklist);
404        }
405
406        // `place.elem` with type `ty`.
407        while let Some((mut place, elem, ty)) = worklist.pop_front() {
408            // Create a place for this projection.
409            place = *self.projections.entry((place, elem)).or_insert_with(|| {
410                // Prepend new child to the linked list.
411                let next = self.places.push(PlaceInfo::new(Some(elem)));
412                self.places[next].next_sibling = self.places[place].first_child;
413                self.places[place].first_child = Some(next);
414                next
415            });
416
417            // And push the eventual children places to the worklist.
418            self.register_children(place, ty, &mut worklist);
419        }
420
421        // Pre-compute the tree of ValueIndex nested in each PlaceIndex.
422        // `inner_values_buffer[inner_values[place]]` is the set of all the values
423        // reachable by projecting `place`.
424        self.inner_values_buffer = Vec::with_capacity(self.value_count);
425        self.inner_values = IndexVec::from_elem(0..0, &self.places);
426        for local in body.local_decls.indices() {
427            if let Some(place) = self.locals[local] {
428                self.cache_preorder_invoke(place);
429            }
430        }
431
432        // Trim useless places.
433        for opt_place in &mut self.locals {
434            if let Some(place) = *opt_place
435                && self.inner_values[place].is_empty()
436            {
437                *opt_place = None;
438            }
439        }
440        self.projections
441            .retain(|_, child| !self.inner_values[*child].is_empty());
442    }
443
444    /// Potentially register the (local, projection) place and its fields, recursively.
445    ///
446    /// Invariant: The projection must only contain trackable elements.
447    fn register_children<'tcx>(
448        &mut self,
449        place: PlaceIndex,
450        ty: ty::Ty<'tcx>,
451        worklist: &mut VecDeque<(PlaceIndex, FieldIdx, ty::Ty<'tcx>)>,
452    ) {
453        // Allocate a value slot if it doesn't have one, and the user requested one.
454        assert!(self.places[place].value_index.is_none());
455        if let ty::TyKind::Ref(.., mir::Mutability::Mut) = ty.kind() {
456            self.places[place].value_index = Some(self.value_count.into());
457            self.value_count += 1;
458        }
459
460        // Add tuple fields to the worklist.
461        if let ty::Tuple(list) = ty.kind() {
462            for (field, ty) in list.iter().enumerate() {
463                worklist.push_back((place, field.into(), ty));
464            }
465        }
466    }
467
468    /// Precompute the list of values inside `root` and store it inside
469    /// as a slice within `inner_values_buffer`.
470    fn cache_preorder_invoke(&mut self, root: PlaceIndex) {
471        let start = self.inner_values_buffer.len();
472        if let Some(vi) = self.places[root].value_index {
473            self.inner_values_buffer.push(vi);
474        }
475
476        // We manually iterate instead of using `children` as we need to mutate `self`.
477        let mut next_child = self.places[root].first_child;
478        while let Some(child) = next_child {
479            ensure_sufficient_stack(|| self.cache_preorder_invoke(child));
480            next_child = self.places[child].next_sibling;
481        }
482
483        let end = self.inner_values_buffer.len();
484        self.inner_values[root] = start..end;
485    }
486
487    /// Locates the given place, if it exists in the tree.
488    fn find(&self, place: mir::PlaceRef<'_>) -> Option<PlaceIndex> {
489        let mut index = *self.locals[place.local].as_ref()?;
490
491        for &elem in place.projection {
492            let mir::ProjectionElem::Field(elem, _) = elem else { return None };
493            index = self.apply(index, elem)?;
494        }
495
496        Some(index)
497    }
498
499    /// Iterate over all direct children.
500    fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
501        Children::new(self, parent)
502    }
503
504    /// Applies a single projection element, yielding the corresponding child.
505    fn apply(&self, place: PlaceIndex, elem: FieldIdx) -> Option<PlaceIndex> {
506        self.projections.get(&(place, elem)).copied()
507    }
508
509    /// Invoke a function on the given place and all places that may alias it.
510    fn for_each_aliasing_place(&self, place: mir::PlaceRef<'_>, f: &mut impl FnMut(ValueIndex)) {
511        let Some(mut index) = self.locals[place.local] else {
512            // The local is not tracked at all, so it does not alias anything.
513            return;
514        };
515        for elem in place.projection {
516            let mir::ProjectionElem::Field(elem, _) = *elem else {
517                return;
518            };
519            // A field aliases the parent place.
520            if let Some(vi) = self.places[index].value_index {
521                f(vi);
522            }
523
524            let Some(sub) = self.apply(index, elem) else {
525                return;
526            };
527            index = sub;
528        }
529        self.for_each_value_inside(index, f);
530    }
531
532    /// Invoke a function on each value in the given place and all descendants.
533    fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) {
534        let range = self.inner_values[root].clone();
535        let values = &self.inner_values_buffer[range];
536        for &v in values {
537            f(v);
538        }
539    }
540
541    fn for_each_tracked_place(&self, mut f: impl FnMut(PlaceIndex, mir::Local, &[FieldIdx])) {
542        let mut projection = Vec::new();
543        for (local, place) in self.locals.iter_enumerated() {
544            if let Some(place) = place {
545                self.for_each_tracked_place_rec(
546                    *place,
547                    &mut projection,
548                    &mut |place, projection| {
549                        f(place, local, projection);
550                    },
551                );
552            }
553        }
554    }
555
556    fn for_each_tracked_place_rec(
557        &self,
558        root: PlaceIndex,
559        projection: &mut Vec<FieldIdx>,
560        f: &mut impl FnMut(PlaceIndex, &[FieldIdx]),
561    ) {
562        // Fast path is there is nothing to do.
563        if self.inner_values[root].is_empty() {
564            return;
565        }
566
567        if self.places[root].value_index.is_some() {
568            f(root, projection);
569        }
570
571        for child in self.children(root) {
572            let elem = self.places[child]
573                .proj_elem
574                .unwrap_or_else(|| tracked_span_bug!());
575            projection.push(elem);
576            self.for_each_tracked_place_rec(child, projection, f);
577            projection.pop();
578        }
579    }
580}
581
582/// This is the information tracked for every [`PlaceIndex`] and is stored by [`Map`].
583///
584/// Together, `first_child` and `next_sibling` form an intrusive linked list, which is used to
585/// model a tree structure (a replacement for a member like `children: Vec<PlaceIndex>`).
586#[derive(Debug)]
587struct PlaceInfo {
588    /// We store a [`ValueIndex`] if and only if the placed is tracked by the analysis.
589    value_index: Option<ValueIndex>,
590
591    /// The projection used to go from parent to this node (only None for root).
592    proj_elem: Option<FieldIdx>,
593
594    /// The left-most child.
595    first_child: Option<PlaceIndex>,
596
597    /// Index of the sibling to the right of this node.
598    next_sibling: Option<PlaceIndex>,
599}
600
601impl PlaceInfo {
602    fn new(proj_elem: Option<FieldIdx>) -> Self {
603        Self { next_sibling: None, proj_elem, first_child: None, value_index: None }
604    }
605}
606
607struct Children<'a> {
608    map: &'a Map,
609    next: Option<PlaceIndex>,
610}
611
612impl<'a> Children<'a> {
613    fn new(map: &'a Map, parent: PlaceIndex) -> Self {
614        Self { map, next: map.places[parent].first_child }
615    }
616}
617
618impl Iterator for Children<'_> {
619    type Item = PlaceIndex;
620
621    fn next(&mut self) -> Option<Self::Item> {
622        match self.next {
623            Some(child) => {
624                self.next = self.map.places[child].next_sibling;
625                Some(child)
626            }
627            None => None,
628        }
629    }
630}
631
632/// Returns all locals with projections that have their reference or address taken.
633fn excluded_locals(body: &mir::Body<'_>) -> DenseBitSet<mir::Local> {
634    struct Collector {
635        result: DenseBitSet<mir::Local>,
636    }
637
638    impl<'tcx> mir::visit::Visitor<'tcx> for Collector {
639        fn visit_place(
640            &mut self,
641            place: &mir::Place<'tcx>,
642            context: mir::visit::PlaceContext,
643            _location: mir::Location,
644        ) {
645            if (context.is_borrow()
646                || context.is_address_of()
647                || context.is_drop()
648                || context
649                    == mir::visit::PlaceContext::MutatingUse(
650                        mir::visit::MutatingUseContext::AsmOutput,
651                    ))
652                && !place.is_indirect()
653            {
654                // A pointer to a place could be used to access other places with the same local,
655                // hence we have to exclude the local completely.
656                self.result.insert(place.local);
657            }
658        }
659    }
660
661    let mut collector = Collector { result: DenseBitSet::new_empty(body.local_decls.len()) };
662    collector.visit_body(body);
663    collector.result
664}
665
666rustc_index::newtype_index!(
667    /// This index uniquely identifies a place.
668    ///
669    /// Not every place has a `PlaceIndex`, and not every `PlaceIndex` corresponds to a tracked
670    /// place. However, every tracked place and all places along its projection have a `PlaceIndex`.
671    struct PlaceIndex {}
672);
673
674rustc_index::newtype_index!(
675    /// This index uniquely identifies a tracked place and therefore a slot in [`State`].
676    ///
677    /// It is an implementation detail of this module.
678    struct ValueIndex {}
679);
680
681/// Used as the result for r-value.
682enum PlaceOrValue {
683    Value(FlatSet<Path>),
684    Place(PlaceIndex),
685}
686
687impl PlaceOrValue {
688    const TOP: Self = PlaceOrValue::Value(FlatSet::TOP);
689}
690
691/// The dataflow state for the [`PointsToAnalysis`].
692///
693/// Every instance specifies a lattice that represents the possible values of a single tracked
694/// place. If we call this lattice `V` and set of tracked places `P`, then a [`State`] is an
695/// element of `{unreachable} ∪ (P -> V)`. This again forms a lattice, where the bottom element is
696/// `unreachable` and the top element is the mapping `p ↦ ⊤`. Note that the mapping `p ↦ ⊥` is not
697/// the bottom element (because joining an unreachable and any other reachable state yields a
698/// reachable state). All operations on unreachable states are ignored.
699///
700/// Flooding means assigning a value (by default `⊤`) to all tracked projections of a given place.
701#[derive(PartialEq, Eq, Debug)]
702struct State {
703    values: IndexVec<ValueIndex, FlatSet<Path>>,
704}
705
706impl Clone for State {
707    fn clone(&self) -> Self {
708        Self { values: self.values.clone() }
709    }
710
711    fn clone_from(&mut self, source: &Self) {
712        self.values.clone_from(&source.values);
713    }
714}
715
716impl JoinSemiLattice for State {
717    fn join(&mut self, other: &Self) -> bool {
718        assert_eq!(self.values.len(), other.values.len());
719        let mut changed = false;
720        for (a, b) in iter::zip(&mut self.values, &other.values) {
721            changed |= a.join(b);
722        }
723        changed
724    }
725}
726
727impl State {
728    fn flood(&mut self, place: mir::PlaceRef<'_>, map: &Map) {
729        self.flood_with(place, map, FlatSet::TOP);
730    }
731
732    fn flood_with(&mut self, place: mir::PlaceRef<'_>, map: &Map, value: FlatSet<Path>) {
733        map.for_each_aliasing_place(place, &mut |vi| {
734            self.values[vi] = value.clone();
735        });
736    }
737
738    /// Helper method to interpret `target = result`.
739    fn assign(&mut self, target: mir::PlaceRef<'_>, result: PlaceOrValue, map: &Map) {
740        self.flood(target, map);
741        if let Some(target) = map.find(target) {
742            self.insert_idx(target, result, map);
743        }
744    }
745
746    /// Low-level method that assigns to a place.
747    /// This does nothing if the place is not tracked.
748    ///
749    /// The target place must have been flooded before calling this method.
750    fn insert_idx(&mut self, target: PlaceIndex, result: PlaceOrValue, map: &Map) {
751        match result {
752            PlaceOrValue::Value(value) => self.insert_value_idx(target, value, map),
753            PlaceOrValue::Place(source) => self.insert_place_idx(target, source, map),
754        }
755    }
756
757    /// Copies `source` to `target`, including all tracked places beneath.
758    ///
759    /// If `target` contains a place that is not contained in `source`, it will be overwritten with
760    /// Top. Also, because this will copy all entries one after another, it may only be used for
761    /// places that are non-overlapping or identical.
762    ///
763    /// The target place must have been flooded before calling this method.
764    fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
765        // If both places are tracked, we copy the value to the target.
766        // If the target is tracked, but the source is not, we do nothing, as invalidation has
767        // already been performed.
768        if let Some(target_value) = map.places[target].value_index
769            && let Some(source_value) = map.places[source].value_index
770        {
771            self.values[target_value] = self.values[source_value].clone();
772        }
773        for target_child in map.children(target) {
774            // Try to find corresponding child and recurse. Reasoning is similar as above.
775            let projection = map.places[target_child]
776                .proj_elem
777                .unwrap_or_else(|| tracked_span_bug!());
778            if let Some(source_child) = map.projections.get(&(source, projection)) {
779                self.insert_place_idx(target_child, *source_child, map);
780            }
781        }
782    }
783
784    /// Low-level method that assigns a value to a place.
785    /// This does nothing if the place is not tracked.
786    ///
787    /// The target place must have been flooded before calling this method.
788    fn insert_value_idx(&mut self, target: PlaceIndex, value: FlatSet<Path>, map: &Map) {
789        if let Some(value_index) = map.places[target].value_index {
790            self.values[value_index] = value;
791        }
792    }
793
794    /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
795    fn get(&self, place: mir::PlaceRef<'_>, map: &Map) -> FlatSet<Path> {
796        map.find(place)
797            .map_or(FlatSet::TOP, |place| self.get_idx(place, map))
798    }
799
800    /// Retrieve the value stored for a place index, or ⊤ if it is not tracked.
801    fn get_idx(&self, place: PlaceIndex, map: &Map) -> FlatSet<Path> {
802        self.get_tracked_idx(place, map).unwrap_or(FlatSet::Top)
803    }
804
805    /// Retrieve the value stored for a place index if tracked
806    fn get_tracked_idx(&self, place: PlaceIndex, map: &Map) -> Option<FlatSet<Path>> {
807        map.places[place]
808            .value_index
809            .map(|v| self.values[v].clone())
810    }
811}
812
813/// This is used to visualize the dataflow analysis.
814impl DebugWithContext<PointsToAnalysis<'_>> for State {
815    fn fmt_with(&self, ctxt: &PointsToAnalysis, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
816        debug_with_context(&self.values, None, ctxt.map, f)
817    }
818
819    fn fmt_diff_with(
820        &self,
821        old: &Self,
822        ctxt: &PointsToAnalysis,
823        f: &mut fmt::Formatter<'_>,
824    ) -> std::fmt::Result {
825        debug_with_context(&self.values, Some(&old.values), ctxt.map, f)
826    }
827}
828
829fn debug_with_context_rec<V: fmt::Debug + Eq>(
830    place: PlaceIndex,
831    place_str: &str,
832    new: &IndexSlice<ValueIndex, V>,
833    old: Option<&IndexSlice<ValueIndex, V>>,
834    map: &Map,
835    f: &mut fmt::Formatter<'_>,
836) -> std::fmt::Result {
837    if let Some(value) = map.places[place].value_index {
838        match old {
839            None => writeln!(f, "{}: {:?}", place_str, new[value])?,
840            Some(old) => {
841                if new[value] != old[value] {
842                    writeln!(f, "\u{001f}-{}: {:?}", place_str, old[value])?;
843                    writeln!(f, "\u{001f}+{}: {:?}", place_str, new[value])?;
844                }
845            }
846        }
847    }
848
849    for child in map.children(place) {
850        let info_elem = map.places[child]
851            .proj_elem
852            .unwrap_or_else(|| tracked_span_bug!());
853        let child_place_str = format!("{}.{}", place_str, info_elem.index());
854        debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
855    }
856
857    Ok(())
858}
859
860fn debug_with_context<V: fmt::Debug + Eq>(
861    new: &IndexSlice<ValueIndex, V>,
862    old: Option<&IndexSlice<ValueIndex, V>>,
863    map: &Map,
864    f: &mut fmt::Formatter<'_>,
865) -> std::fmt::Result {
866    for (local, place) in map.locals.iter_enumerated() {
867        if let Some(place) = place {
868            debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?;
869        }
870    }
871    Ok(())
872}