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