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