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