use std::fmt::{Debug, Formatter}; use std::ops::Range; use rustc_abi::{FieldIdx, VariantIdx}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty, TyCtxt}; use tracing::debug; use crate::JoinSemiLattice; use crate::lattice::{HasBottom, HasTop}; rustc_index::newtype_index!( /// This index uniquely identifies a place. /// /// Not every place has a `PlaceIndex`, and not every `PlaceIndex` corresponds to a tracked /// place. However, every tracked place and all places along its projection have a `PlaceIndex`. pub struct PlaceIndex {} ); rustc_index::newtype_index!( /// This index uniquely identifies a tracked place and therefore a slot in [`State`]. /// /// It is an implementation detail of this module. struct ValueIndex {} ); /// See [`State`]. #[derive(PartialEq, Eq, Debug)] pub struct StateData { bottom: V, /// This map only contains values that are not `⊥`. map: FxHashMap, } impl StateData { fn new() -> StateData { StateData { bottom: V::BOTTOM, map: FxHashMap::default() } } fn get(&self, idx: ValueIndex) -> &V { self.map.get(&idx).unwrap_or(&self.bottom) } fn insert(&mut self, idx: ValueIndex, elem: V) { if elem.is_bottom() { self.map.remove(&idx); } else { self.map.insert(idx, elem); } } } impl Clone for StateData { fn clone(&self) -> Self { StateData { bottom: self.bottom.clone(), map: self.map.clone() } } fn clone_from(&mut self, source: &Self) { self.map.clone_from(&source.map) } } impl JoinSemiLattice for StateData { fn join(&mut self, other: &Self) -> bool { let mut changed = false; #[allow(rustc::potential_query_instability)] for (i, v) in other.map.iter() { match self.map.entry(*i) { StdEntry::Vacant(e) => { e.insert(v.clone()); changed = true } StdEntry::Occupied(e) => changed |= e.into_mut().join(v), } } changed } } /// Dataflow state. /// /// Every instance specifies a lattice that represents the possible values of a single tracked /// place. If we call this lattice `V` and set of tracked places `P`, then a [`State`] is an /// element of `{unreachable} ∪ (P -> V)`. This again forms a lattice, where the bottom element is /// `unreachable` and the top element is the mapping `p ↦ ⊤`. Note that the mapping `p ↦ ⊥` is not /// the bottom element (because joining an unreachable and any other reachable state yields a /// reachable state). All operations on unreachable states are ignored. /// /// Flooding means assigning a value (by default `⊤`) to all tracked projections of a given place. #[derive(PartialEq, Eq, Debug)] pub enum State { Unreachable, Reachable(StateData), } impl Clone for State { fn clone(&self) -> Self { match self { Self::Reachable(x) => Self::Reachable(x.clone()), Self::Unreachable => Self::Unreachable, } } fn clone_from(&mut self, source: &Self) { match (&mut *self, source) { (Self::Reachable(x), Self::Reachable(y)) => { x.clone_from(&y); } _ => *self = source.clone(), } } } impl State { pub fn new_reachable() -> State { State::Reachable(StateData::new()) } pub fn all_bottom(&self) -> bool { match self { State::Unreachable => false, State::Reachable(ref values) => { #[allow(rustc::potential_query_instability)] values.map.values().all(V::is_bottom) } } } pub fn is_reachable(&self) -> bool { matches!(self, State::Reachable(_)) } /// Assign `value` to all places that are contained in `place` or may alias one. pub fn flood_with(&mut self, place: PlaceRef<'_>, map: &Map<'_>, value: V) { self.flood_with_tail_elem(place, None, map, value) } /// Assign `TOP` to all places that are contained in `place` or may alias one. pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map<'_>) where V: HasTop, { self.flood_with(place, map, V::TOP) } /// Assign `value` to the discriminant of `place` and all places that may alias it. fn flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map<'_>, value: V) { self.flood_with_tail_elem(place, Some(TrackElem::Discriminant), map, value) } /// Assign `TOP` to the discriminant of `place` and all places that may alias it. pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map<'_>) where V: HasTop, { self.flood_discr_with(place, map, V::TOP) } /// This method is the most general version of the `flood_*` method. /// /// Assign `value` on the given place and all places that may alias it. In particular, when /// the given place has a variant downcast, we invoke the function on all the other variants. /// /// `tail_elem` allows to support discriminants that are not a place in MIR, but that we track /// as such. pub fn flood_with_tail_elem( &mut self, place: PlaceRef<'_>, tail_elem: Option, map: &Map<'_>, value: V, ) { let State::Reachable(values) = self else { return }; map.for_each_aliasing_place(place, tail_elem, &mut |vi| values.insert(vi, value.clone())); } /// Low-level method that assigns to a place. /// This does nothing if the place is not tracked. /// /// The target place must have been flooded before calling this method. fn insert_idx(&mut self, target: PlaceIndex, result: ValueOrPlace, map: &Map<'_>) { match result { ValueOrPlace::Value(value) => self.insert_value_idx(target, value, map), ValueOrPlace::Place(source) => self.insert_place_idx(target, source, map), } } /// Low-level method that assigns a value to a place. /// This does nothing if the place is not tracked. /// /// The target place must have been flooded before calling this method. pub fn insert_value_idx(&mut self, target: PlaceIndex, value: V, map: &Map<'_>) { let State::Reachable(values) = self else { return }; if let Some(value_index) = map.places[target].value_index { values.insert(value_index, value) } } /// Copies `source` to `target`, including all tracked places beneath. /// /// If `target` contains a place that is not contained in `source`, it will be overwritten with /// Top. Also, because this will copy all entries one after another, it may only be used for /// places that are non-overlapping or identical. /// /// The target place must have been flooded before calling this method. pub fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map<'_>) { let State::Reachable(values) = self else { return }; // If both places are tracked, we copy the value to the target. // If the target is tracked, but the source is not, we do nothing, as invalidation has // already been performed. if let Some(target_value) = map.places[target].value_index { if let Some(source_value) = map.places[source].value_index { values.insert(target_value, values.get(source_value).clone()); } } for target_child in map.children(target) { // Try to find corresponding child and recurse. Reasoning is similar as above. let projection = map.places[target_child].proj_elem.unwrap(); if let Some(source_child) = map.projections.get(&(source, projection)) { self.insert_place_idx(target_child, *source_child, map); } } } /// Helper method to interpret `target = result`. pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace, map: &Map<'_>) where V: HasTop, { self.flood(target, map); if let Some(target) = map.find(target) { self.insert_idx(target, result, map); } } /// Helper method for assignments to a discriminant. pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace, map: &Map<'_>) where V: HasTop, { self.flood_discr(target, map); if let Some(target) = map.find_discr(target) { self.insert_idx(target, result, map); } } /// Retrieve the value stored for a place, or `None` if it is not tracked. pub fn try_get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { let place = map.find(place)?; self.try_get_idx(place, map) } /// Retrieve the discriminant stored for a place, or `None` if it is not tracked. pub fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { let place = map.find_discr(place)?; self.try_get_idx(place, map) } /// Retrieve the slice length stored for a place, or `None` if it is not tracked. pub fn try_get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { let place = map.find_len(place)?; self.try_get_idx(place, map) } /// Retrieve the value stored for a place index, or `None` if it is not tracked. pub fn try_get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> Option { match self { State::Reachable(values) => { map.places[place].value_index.map(|v| values.get(v).clone()) } State::Unreachable => None, } } /// Retrieve the value stored for a place, or ⊤ if it is not tracked. /// /// This method returns ⊥ if the place is tracked and the state is unreachable. pub fn get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V where V: HasBottom + HasTop, { match self { State::Reachable(_) => self.try_get(place, map).unwrap_or(V::TOP), // Because this is unreachable, we can return any value we want. State::Unreachable => V::BOTTOM, } } /// Retrieve the value stored for a place, or ⊤ if it is not tracked. /// /// This method returns ⊥ the current state is unreachable. pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V where V: HasBottom + HasTop, { match self { State::Reachable(_) => self.try_get_discr(place, map).unwrap_or(V::TOP), // Because this is unreachable, we can return any value we want. State::Unreachable => V::BOTTOM, } } /// Retrieve the value stored for a place, or ⊤ if it is not tracked. /// /// This method returns ⊥ the current state is unreachable. pub fn get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V where V: HasBottom + HasTop, { match self { State::Reachable(_) => self.try_get_len(place, map).unwrap_or(V::TOP), // Because this is unreachable, we can return any value we want. State::Unreachable => V::BOTTOM, } } /// Retrieve the value stored for a place index, or ⊤ if it is not tracked. /// /// This method returns ⊥ the current state is unreachable. pub fn get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> V where V: HasBottom + HasTop, { match self { State::Reachable(values) => { map.places[place].value_index.map(|v| values.get(v).clone()).unwrap_or(V::TOP) } State::Unreachable => { // Because this is unreachable, we can return any value we want. V::BOTTOM } } } } impl JoinSemiLattice for State { fn join(&mut self, other: &Self) -> bool { match (&mut *self, other) { (_, State::Unreachable) => false, (State::Unreachable, _) => { *self = other.clone(); true } (State::Reachable(this), State::Reachable(ref other)) => this.join(other), } } } /// Partial mapping from [`Place`] to [`PlaceIndex`], where some places also have a [`ValueIndex`]. /// /// This data structure essentially maintains a tree of places and their projections. Some /// additional bookkeeping is done, to speed up traversal over this tree: /// - For iteration, every [`PlaceInfo`] contains an intrusive linked list of its children. /// - To directly get the child for a specific projection, there is a `projections` map. #[derive(Debug)] pub struct Map<'tcx> { locals: IndexVec>, projections: FxHashMap<(PlaceIndex, TrackElem), PlaceIndex>, places: IndexVec>, value_count: usize, // The Range corresponds to a slice into `inner_values_buffer`. inner_values: IndexVec>, inner_values_buffer: Vec, } impl<'tcx> Map<'tcx> { /// Returns a map that only tracks places whose type has scalar layout. /// /// This is currently the only way to create a [`Map`]. The way in which the tracked places are /// chosen is an implementation detail and may not be relied upon (other than that their type /// are scalars). pub fn new(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, value_limit: Option) -> Self { let mut map = Self { locals: IndexVec::from_elem(None, &body.local_decls), projections: FxHashMap::default(), places: IndexVec::new(), value_count: 0, inner_values: IndexVec::new(), inner_values_buffer: Vec::new(), }; let exclude = excluded_locals(body); map.register(tcx, body, exclude, value_limit); debug!("registered {} places ({} nodes in total)", map.value_count, map.places.len()); map } /// Register all non-excluded places that have scalar layout. #[tracing::instrument(level = "trace", skip(self, tcx, body))] fn register( &mut self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>, exclude: DenseBitSet, value_limit: Option, ) { // Start by constructing the places for each bare local. for (local, decl) in body.local_decls.iter_enumerated() { if exclude.contains(local) { continue; } // Create a place for the local. debug_assert!(self.locals[local].is_none()); let place = self.places.push(PlaceInfo::new(decl.ty, None)); self.locals[local] = Some(place); } // Collect syntactic places and assignments between them. let mut collector = PlaceCollector { tcx, body, map: self, assignments: Default::default() }; collector.visit_body(body); let PlaceCollector { mut assignments, .. } = collector; // Just collecting syntactic places is not enough. We may need to propagate this pattern: // _1 = (const 5u32, const 13i64); // _2 = _1; // _3 = (_2.0 as u32); // // `_1.0` does not appear, but we still need to track it. This is achieved by propagating // projections from assignments. We recorded an assignment between `_2` and `_1`, so we // want `_1` and `_2` to have the same sub-places. // // This is what this fixpoint loop does. While we are still creating places, run through // all the assignments, and register places for children. let mut num_places = 0; while num_places < self.places.len() { num_places = self.places.len(); for assign in 0.. { let Some(&(lhs, rhs)) = assignments.get_index(assign) else { break }; // Mirror children from `lhs` in `rhs`. let mut child = self.places[lhs].first_child; while let Some(lhs_child) = child { let PlaceInfo { ty, proj_elem, next_sibling, .. } = self.places[lhs_child]; let rhs_child = self.register_place(ty, rhs, proj_elem.expect("child is not a projection")); assignments.insert((lhs_child, rhs_child)); child = next_sibling; } // Conversely, mirror children from `rhs` in `lhs`. let mut child = self.places[rhs].first_child; while let Some(rhs_child) = child { let PlaceInfo { ty, proj_elem, next_sibling, .. } = self.places[rhs_child]; let lhs_child = self.register_place(ty, lhs, proj_elem.expect("child is not a projection")); assignments.insert((lhs_child, rhs_child)); child = next_sibling; } } } drop(assignments); // Create values for places whose type have scalar layout. let typing_env = body.typing_env(tcx); for place_info in self.places.iter_mut() { // The user requires a bound on the number of created values. if let Some(value_limit) = value_limit && self.value_count >= value_limit { break; } if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, place_info.ty) { place_info.ty = ty; } // Allocate a value slot if it doesn't have one, and the user requested one. assert!(place_info.value_index.is_none()); if let Ok(layout) = tcx.layout_of(typing_env.as_query_input(place_info.ty)) && layout.backend_repr.is_scalar() { place_info.value_index = Some(self.value_count.into()); self.value_count += 1; } } // Pre-compute the tree of ValueIndex nested in each PlaceIndex. // `inner_values_buffer[inner_values[place]]` is the set of all the values // reachable by projecting `place`. self.inner_values_buffer = Vec::with_capacity(self.value_count); self.inner_values = IndexVec::from_elem(0..0, &self.places); for local in body.local_decls.indices() { if let Some(place) = self.locals[local] { self.cache_preorder_invoke(place); } } // Trim useless places. for opt_place in self.locals.iter_mut() { if let Some(place) = *opt_place && self.inner_values[place].is_empty() { *opt_place = None; } } #[allow(rustc::potential_query_instability)] self.projections.retain(|_, child| !self.inner_values[*child].is_empty()); } #[tracing::instrument(level = "trace", skip(self), ret)] fn register_place(&mut self, ty: Ty<'tcx>, base: PlaceIndex, elem: TrackElem) -> PlaceIndex { *self.projections.entry((base, elem)).or_insert_with(|| { let next = self.places.push(PlaceInfo::new(ty, Some(elem))); self.places[next].next_sibling = self.places[base].first_child; self.places[base].first_child = Some(next); next }) } /// Precompute the list of values inside `root` and store it inside /// as a slice within `inner_values_buffer`. fn cache_preorder_invoke(&mut self, root: PlaceIndex) { let start = self.inner_values_buffer.len(); if let Some(vi) = self.places[root].value_index { self.inner_values_buffer.push(vi); } // We manually iterate instead of using `children` as we need to mutate `self`. let mut next_child = self.places[root].first_child; while let Some(child) = next_child { ensure_sufficient_stack(|| self.cache_preorder_invoke(child)); next_child = self.places[child].next_sibling; } let end = self.inner_values_buffer.len(); self.inner_values[root] = start..end; } } struct PlaceCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: &'a mut Map<'tcx>, assignments: FxIndexSet<(PlaceIndex, PlaceIndex)>, } impl<'tcx> PlaceCollector<'_, 'tcx> { #[tracing::instrument(level = "trace", skip(self))] fn register_place(&mut self, place: Place<'tcx>) -> Option { // Create a place for this projection. let mut place_index = self.map.locals[place.local]?; let mut ty = PlaceTy::from_ty(self.body.local_decls[place.local].ty); tracing::trace!(?place_index, ?ty); if let ty::Ref(_, ref_ty, _) | ty::RawPtr(ref_ty, _) = ty.ty.kind() && let ty::Slice(..) = ref_ty.kind() { self.map.register_place(self.tcx.types.usize, place_index, TrackElem::DerefLen); } else if ty.ty.is_enum() { let discriminant_ty = ty.ty.discriminant_ty(self.tcx); self.map.register_place(discriminant_ty, place_index, TrackElem::Discriminant); } for proj in place.projection { let track_elem = proj.try_into().ok()?; ty = ty.projection_ty(self.tcx, proj); place_index = self.map.register_place(ty.ty, place_index, track_elem); tracing::trace!(?proj, ?place_index, ?ty); if let ty::Ref(_, ref_ty, _) | ty::RawPtr(ref_ty, _) = ty.ty.kind() && let ty::Slice(..) = ref_ty.kind() { self.map.register_place(self.tcx.types.usize, place_index, TrackElem::DerefLen); } else if ty.ty.is_enum() { let discriminant_ty = ty.ty.discriminant_ty(self.tcx); self.map.register_place(discriminant_ty, place_index, TrackElem::Discriminant); } } Some(place_index) } } impl<'tcx> Visitor<'tcx> for PlaceCollector<'_, 'tcx> { #[tracing::instrument(level = "trace", skip(self))] fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, _: Location) { if !ctxt.is_use() { return; } self.register_place(*place); } fn visit_assign(&mut self, lhs: &Place<'tcx>, rhs: &Rvalue<'tcx>, location: Location) { self.super_assign(lhs, rhs, location); match rhs { Rvalue::Use(Operand::Move(rhs) | Operand::Copy(rhs)) | Rvalue::CopyForDeref(rhs) => { let Some(lhs) = self.register_place(*lhs) else { return }; let Some(rhs) = self.register_place(*rhs) else { return }; self.assignments.insert((lhs, rhs)); } Rvalue::Aggregate(kind, fields) => { let Some(mut lhs) = self.register_place(*lhs) else { return }; match **kind { // Do not propagate unions. AggregateKind::Adt(_, _, _, _, Some(_)) => return, AggregateKind::Adt(_, variant, _, _, None) => { let ty = self.map.places[lhs].ty; if ty.is_enum() { lhs = self.map.register_place(ty, lhs, TrackElem::Variant(variant)); } } AggregateKind::RawPtr(..) | AggregateKind::Array(_) | AggregateKind::Tuple | AggregateKind::Closure(..) | AggregateKind::Coroutine(..) | AggregateKind::CoroutineClosure(..) => {} } for (index, field) in fields.iter_enumerated() { if let Some(rhs) = field.place() && let Some(rhs) = self.register_place(rhs) { let lhs = self.map.register_place( self.map.places[rhs].ty, lhs, TrackElem::Field(index), ); self.assignments.insert((lhs, rhs)); } } } _ => {} } } } impl<'tcx> Map<'tcx> { /// Applies a single projection element, yielding the corresponding child. pub fn apply(&self, place: PlaceIndex, elem: TrackElem) -> Option { self.projections.get(&(place, elem)).copied() } /// Locates the given place, if it exists in the tree. fn find_extra( &self, place: PlaceRef<'_>, extra: impl IntoIterator, ) -> Option { let mut index = *self.locals[place.local].as_ref()?; for &elem in place.projection { index = self.apply(index, elem.try_into().ok()?)?; } for elem in extra { index = self.apply(index, elem)?; } Some(index) } /// Locates the given place, if it exists in the tree. pub fn find(&self, place: PlaceRef<'_>) -> Option { self.find_extra(place, []) } /// Locates the given place and applies `Discriminant`, if it exists in the tree. pub fn find_discr(&self, place: PlaceRef<'_>) -> Option { self.find_extra(place, [TrackElem::Discriminant]) } /// Locates the given place and applies `DerefLen`, if it exists in the tree. pub fn find_len(&self, place: PlaceRef<'_>) -> Option { self.find_extra(place, [TrackElem::DerefLen]) } /// Iterate over all direct children. fn children( &self, parent: PlaceIndex, ) -> impl Iterator + Captures<'_> + Captures<'tcx> { Children::new(self, parent) } /// Invoke a function on the given place and all places that may alias it. /// /// In particular, when the given place has a variant downcast, we invoke the function on all /// the other variants. /// /// `tail_elem` allows to support discriminants that are not a place in MIR, but that we track /// as such. fn for_each_aliasing_place( &self, place: PlaceRef<'_>, tail_elem: Option, f: &mut impl FnMut(ValueIndex), ) { if place.is_indirect_first_projection() { // We do not track indirect places. return; } let Some(mut index) = self.locals[place.local] else { // The local is not tracked at all, so it does not alias anything. return; }; let elems = place.projection.iter().map(|&elem| elem.try_into()).chain(tail_elem.map(Ok)); for elem in elems { // A field aliases the parent place. if let Some(vi) = self.places[index].value_index { f(vi); } let Ok(elem) = elem else { return }; let sub = self.apply(index, elem); if let TrackElem::Variant(..) | TrackElem::Discriminant = elem { // Enum variant fields and enum discriminants alias each another. self.for_each_variant_sibling(index, sub, f); } if let Some(sub) = sub { index = sub } else { return; } } self.for_each_value_inside(index, f); } /// Invoke the given function on all the descendants of the given place, except one branch. fn for_each_variant_sibling( &self, parent: PlaceIndex, preserved_child: Option, f: &mut impl FnMut(ValueIndex), ) { for sibling in self.children(parent) { let elem = self.places[sibling].proj_elem; // Only invalidate variants and discriminant. Fields (for coroutines) are not // invalidated by assignment to a variant. if let Some(TrackElem::Variant(..) | TrackElem::Discriminant) = elem // Only invalidate the other variants, the current one is fine. && Some(sibling) != preserved_child { self.for_each_value_inside(sibling, f); } } } /// Invoke a function on each value in the given place and all descendants. fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) { let range = self.inner_values[root].clone(); let values = &self.inner_values_buffer[range]; for &v in values { f(v) } } /// Invoke a function on each value in the given place and all descendants. pub fn for_each_projection_value( &self, root: PlaceIndex, value: O, project: &mut impl FnMut(TrackElem, &O) -> Option, f: &mut impl FnMut(PlaceIndex, &O), ) { // Fast path is there is nothing to do. if self.inner_values[root].is_empty() { return; } if self.places[root].value_index.is_some() { f(root, &value) } for child in self.children(root) { let elem = self.places[child].proj_elem.unwrap(); if let Some(value) = project(elem, &value) { self.for_each_projection_value(child, value, project, f); } } } } /// This is the information tracked for every [`PlaceIndex`] and is stored by [`Map`]. /// /// Together, `first_child` and `next_sibling` form an intrusive linked list, which is used to /// model a tree structure (a replacement for a member like `children: Vec`). #[derive(Debug)] struct PlaceInfo<'tcx> { /// Type of the referenced place. ty: Ty<'tcx>, /// We store a [`ValueIndex`] if and only if the placed is tracked by the analysis. value_index: Option, /// The projection used to go from parent to this node (only None for root). proj_elem: Option, /// The leftmost child. first_child: Option, /// Index of the sibling to the right of this node. next_sibling: Option, } impl<'tcx> PlaceInfo<'tcx> { fn new(ty: Ty<'tcx>, proj_elem: Option) -> Self { Self { ty, next_sibling: None, first_child: None, proj_elem, value_index: None } } } struct Children<'a, 'tcx> { map: &'a Map<'tcx>, next: Option, } impl<'a, 'tcx> Children<'a, 'tcx> { fn new(map: &'a Map<'tcx>, parent: PlaceIndex) -> Self { Self { map, next: map.places[parent].first_child } } } impl Iterator for Children<'_, '_> { type Item = PlaceIndex; fn next(&mut self) -> Option { match self.next { Some(child) => { self.next = self.map.places[child].next_sibling; Some(child) } None => None, } } } /// Used as the result of an operand or r-value. #[derive(Debug)] pub enum ValueOrPlace { Value(V), Place(PlaceIndex), } impl ValueOrPlace { pub const TOP: Self = ValueOrPlace::Value(V::TOP); } /// The set of projection elements that can be used by a tracked place. /// /// Although only field projections are currently allowed, this could change in the future. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum TrackElem { Field(FieldIdx), Variant(VariantIdx), Discriminant, // Length of a slice. DerefLen, } impl TryFrom> for TrackElem { type Error = (); fn try_from(value: ProjectionElem) -> Result { match value { ProjectionElem::Field(field, _) => Ok(TrackElem::Field(field)), ProjectionElem::Downcast(_, idx) => Ok(TrackElem::Variant(idx)), _ => Err(()), } } } /// Invokes `f` on all direct fields of `ty`. pub fn iter_fields<'tcx>( ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, mut f: impl FnMut(Option, FieldIdx, Ty<'tcx>), ) { match ty.kind() { ty::Tuple(list) => { for (field, ty) in list.iter().enumerate() { f(None, field.into(), ty); } } ty::Adt(def, args) => { if def.is_union() { return; } for (v_index, v_def) in def.variants().iter_enumerated() { let variant = if def.is_struct() { None } else { Some(v_index) }; for (f_index, f_def) in v_def.fields.iter().enumerate() { let field_ty = f_def.ty(tcx, args); let field_ty = tcx .try_normalize_erasing_regions(typing_env, field_ty) .unwrap_or_else(|_| tcx.erase_regions(field_ty)); f(variant, f_index.into(), field_ty); } } } ty::Closure(_, args) => { iter_fields(args.as_closure().tupled_upvars_ty(), tcx, typing_env, f); } ty::Coroutine(_, args) => { iter_fields(args.as_coroutine().tupled_upvars_ty(), tcx, typing_env, f); } ty::CoroutineClosure(_, args) => { iter_fields(args.as_coroutine_closure().tupled_upvars_ty(), tcx, typing_env, f); } _ => (), } } /// Returns all locals with projections that have their reference or address taken. pub fn excluded_locals(body: &Body<'_>) -> DenseBitSet { struct Collector { result: DenseBitSet, } impl<'tcx> Visitor<'tcx> for Collector { fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) { if (context.is_borrow() || context.is_address_of() || context.is_drop() || context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)) && !place.is_indirect() { // A pointer to a place could be used to access other places with the same local, // hence we have to exclude the local completely. self.result.insert(place.local); } } } let mut collector = Collector { result: DenseBitSet::new_empty(body.local_decls.len()) }; collector.visit_body(body); collector.result } fn debug_with_context_rec( place: PlaceIndex, place_str: &str, new: &StateData, old: Option<&StateData>, map: &Map<'_>, f: &mut Formatter<'_>, ) -> std::fmt::Result { if let Some(value) = map.places[place].value_index { match old { None => writeln!(f, "{}: {:?}", place_str, new.get(value))?, Some(old) => { if new.get(value) != old.get(value) { writeln!(f, "\u{001f}-{}: {:?}", place_str, old.get(value))?; writeln!(f, "\u{001f}+{}: {:?}", place_str, new.get(value))?; } } } } for child in map.children(place) { let info_elem = map.places[child].proj_elem.unwrap(); let child_place_str = match info_elem { TrackElem::Discriminant => { format!("discriminant({place_str})") } TrackElem::Variant(idx) => { format!("({place_str} as {idx:?})") } TrackElem::Field(field) => { if place_str.starts_with('*') { format!("({}).{}", place_str, field.index()) } else { format!("{}.{}", place_str, field.index()) } } TrackElem::DerefLen => { format!("Len(*{})", place_str) } }; debug_with_context_rec(child, &child_place_str, new, old, map, f)?; } Ok(()) } pub fn debug_with_context( new: &StateData, old: Option<&StateData>, map: &Map<'_>, f: &mut Formatter<'_>, ) -> std::fmt::Result { for (local, place) in map.locals.iter_enumerated() { if let Some(place) = place { debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?; } } Ok(()) }