• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! This module provides a framework on top of the normal MIR dataflow framework to simplify the
2 //! implementation of analyses that track information about the values stored in certain places.
3 //! We are using the term "place" here to refer to a `mir::Place` (a place expression) instead of
4 //! an `interpret::Place` (a memory location).
5 //!
6 //! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`)
7 //! provide some behavior that should be valid for all abstract domains that are based only on the
8 //! value stored in a certain place. On top of these default rules, an implementation should
9 //! override some of the `handle_` methods. For an example, see `ConstAnalysis`.
10 //!
11 //! An implementation must also provide a [`Map`]. Before the analysis begins, all places that
12 //! should be tracked during the analysis must be registered. During the analysis, no new places
13 //! can be registered. The [`State`] can be queried to retrieve the abstract value stored for a
14 //! certain place by passing the map.
15 //!
16 //! This framework is currently experimental. Originally, it supported shared references and enum
17 //! variants. However, it was discovered that both of these were unsound, and especially references
18 //! had subtle but serious issues. In the future, they could be added back in, but we should clarify
19 //! the rules for optimizations that rely on the aliasing model first.
20 //!
21 //!
22 //! # Notes
23 //!
24 //! - The bottom state denotes uninitialized memory. Because we are only doing a sound approximation
25 //! of the actual execution, we can also use this state for places where access would be UB.
26 //!
27 //! - The assignment logic in `State::insert_place_idx` assumes that the places are non-overlapping,
28 //! or identical. Note that this refers to place expressions, not memory locations.
29 //!
30 //! - Currently, places that have their reference taken cannot be tracked. Although this would be
31 //! possible, it has to rely on some aliasing model, which we are not ready to commit to yet.
32 //! Because of that, we can assume that the only way to change the value behind a tracked place is
33 //! by direct assignment.
34 
35 use std::collections::VecDeque;
36 use std::fmt::{Debug, Formatter};
37 use std::ops::Range;
38 
39 use rustc_data_structures::fx::FxHashMap;
40 use rustc_data_structures::stack::ensure_sufficient_stack;
41 use rustc_index::bit_set::BitSet;
42 use rustc_index::{IndexSlice, IndexVec};
43 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
44 use rustc_middle::mir::*;
45 use rustc_middle::ty::{self, Ty, TyCtxt};
46 use rustc_target::abi::{FieldIdx, VariantIdx};
47 
48 use crate::lattice::{HasBottom, HasTop};
49 use crate::{
50     fmt::DebugWithContext, Analysis, AnalysisDomain, CallReturnPlaces, JoinSemiLattice,
51     SwitchIntEdgeEffects,
52 };
53 
54 pub trait ValueAnalysis<'tcx> {
55     /// For each place of interest, the analysis tracks a value of the given type.
56     type Value: Clone + JoinSemiLattice + HasBottom + HasTop;
57 
58     const NAME: &'static str;
59 
map(&self) -> &Map60     fn map(&self) -> &Map;
61 
handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>)62     fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
63         self.super_statement(statement, state)
64     }
65 
super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>)66     fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
67         match &statement.kind {
68             StatementKind::Assign(box (place, rvalue)) => {
69                 self.handle_assign(*place, rvalue, state);
70             }
71             StatementKind::SetDiscriminant { box place, variant_index } => {
72                 self.handle_set_discriminant(*place, *variant_index, state);
73             }
74             StatementKind::Intrinsic(box intrinsic) => {
75                 self.handle_intrinsic(intrinsic, state);
76             }
77             StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
78                 // StorageLive leaves the local in an uninitialized state.
79                 // StorageDead makes it UB to access the local afterwards.
80                 state.flood_with(Place::from(*local).as_ref(), self.map(), Self::Value::BOTTOM);
81             }
82             StatementKind::Deinit(box place) => {
83                 // Deinit makes the place uninitialized.
84                 state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
85             }
86             StatementKind::Retag(..) => {
87                 // We don't track references.
88             }
89             StatementKind::ConstEvalCounter
90             | StatementKind::Nop
91             | StatementKind::FakeRead(..)
92             | StatementKind::PlaceMention(..)
93             | StatementKind::Coverage(..)
94             | StatementKind::AscribeUserType(..) => (),
95         }
96     }
97 
handle_set_discriminant( &self, place: Place<'tcx>, variant_index: VariantIdx, state: &mut State<Self::Value>, )98     fn handle_set_discriminant(
99         &self,
100         place: Place<'tcx>,
101         variant_index: VariantIdx,
102         state: &mut State<Self::Value>,
103     ) {
104         self.super_set_discriminant(place, variant_index, state)
105     }
106 
super_set_discriminant( &self, place: Place<'tcx>, _variant_index: VariantIdx, state: &mut State<Self::Value>, )107     fn super_set_discriminant(
108         &self,
109         place: Place<'tcx>,
110         _variant_index: VariantIdx,
111         state: &mut State<Self::Value>,
112     ) {
113         state.flood_discr(place.as_ref(), self.map());
114     }
115 
handle_intrinsic( &self, intrinsic: &NonDivergingIntrinsic<'tcx>, state: &mut State<Self::Value>, )116     fn handle_intrinsic(
117         &self,
118         intrinsic: &NonDivergingIntrinsic<'tcx>,
119         state: &mut State<Self::Value>,
120     ) {
121         self.super_intrinsic(intrinsic, state);
122     }
123 
super_intrinsic( &self, intrinsic: &NonDivergingIntrinsic<'tcx>, _state: &mut State<Self::Value>, )124     fn super_intrinsic(
125         &self,
126         intrinsic: &NonDivergingIntrinsic<'tcx>,
127         _state: &mut State<Self::Value>,
128     ) {
129         match intrinsic {
130             NonDivergingIntrinsic::Assume(..) => {
131                 // Could use this, but ignoring it is sound.
132             }
133             NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
134                 dst: _,
135                 src: _,
136                 count: _,
137             }) => {
138                 // This statement represents `*dst = *src`, `count` times.
139             }
140         }
141     }
142 
handle_assign( &self, target: Place<'tcx>, rvalue: &Rvalue<'tcx>, state: &mut State<Self::Value>, )143     fn handle_assign(
144         &self,
145         target: Place<'tcx>,
146         rvalue: &Rvalue<'tcx>,
147         state: &mut State<Self::Value>,
148     ) {
149         self.super_assign(target, rvalue, state)
150     }
151 
super_assign( &self, target: Place<'tcx>, rvalue: &Rvalue<'tcx>, state: &mut State<Self::Value>, )152     fn super_assign(
153         &self,
154         target: Place<'tcx>,
155         rvalue: &Rvalue<'tcx>,
156         state: &mut State<Self::Value>,
157     ) {
158         let result = self.handle_rvalue(rvalue, state);
159         state.assign(target.as_ref(), result, self.map());
160     }
161 
handle_rvalue( &self, rvalue: &Rvalue<'tcx>, state: &mut State<Self::Value>, ) -> ValueOrPlace<Self::Value>162     fn handle_rvalue(
163         &self,
164         rvalue: &Rvalue<'tcx>,
165         state: &mut State<Self::Value>,
166     ) -> ValueOrPlace<Self::Value> {
167         self.super_rvalue(rvalue, state)
168     }
169 
super_rvalue( &self, rvalue: &Rvalue<'tcx>, state: &mut State<Self::Value>, ) -> ValueOrPlace<Self::Value>170     fn super_rvalue(
171         &self,
172         rvalue: &Rvalue<'tcx>,
173         state: &mut State<Self::Value>,
174     ) -> ValueOrPlace<Self::Value> {
175         match rvalue {
176             Rvalue::Use(operand) => self.handle_operand(operand, state),
177             Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state),
178             Rvalue::Ref(..) | Rvalue::AddressOf(..) => {
179                 // We don't track such places.
180                 ValueOrPlace::TOP
181             }
182             Rvalue::Repeat(..)
183             | Rvalue::ThreadLocalRef(..)
184             | Rvalue::Len(..)
185             | Rvalue::Cast(..)
186             | Rvalue::BinaryOp(..)
187             | Rvalue::CheckedBinaryOp(..)
188             | Rvalue::NullaryOp(..)
189             | Rvalue::UnaryOp(..)
190             | Rvalue::Discriminant(..)
191             | Rvalue::Aggregate(..)
192             | Rvalue::ShallowInitBox(..) => {
193                 // No modification is possible through these r-values.
194                 ValueOrPlace::TOP
195             }
196         }
197     }
198 
handle_operand( &self, operand: &Operand<'tcx>, state: &mut State<Self::Value>, ) -> ValueOrPlace<Self::Value>199     fn handle_operand(
200         &self,
201         operand: &Operand<'tcx>,
202         state: &mut State<Self::Value>,
203     ) -> ValueOrPlace<Self::Value> {
204         self.super_operand(operand, state)
205     }
206 
super_operand( &self, operand: &Operand<'tcx>, state: &mut State<Self::Value>, ) -> ValueOrPlace<Self::Value>207     fn super_operand(
208         &self,
209         operand: &Operand<'tcx>,
210         state: &mut State<Self::Value>,
211     ) -> ValueOrPlace<Self::Value> {
212         match operand {
213             Operand::Constant(box constant) => {
214                 ValueOrPlace::Value(self.handle_constant(constant, state))
215             }
216             Operand::Copy(place) | Operand::Move(place) => {
217                 // On move, we would ideally flood the place with bottom. But with the current
218                 // framework this is not possible (similar to `InterpCx::eval_operand`).
219                 self.map()
220                     .find(place.as_ref())
221                     .map(ValueOrPlace::Place)
222                     .unwrap_or(ValueOrPlace::TOP)
223             }
224         }
225     }
226 
handle_constant( &self, constant: &Constant<'tcx>, state: &mut State<Self::Value>, ) -> Self::Value227     fn handle_constant(
228         &self,
229         constant: &Constant<'tcx>,
230         state: &mut State<Self::Value>,
231     ) -> Self::Value {
232         self.super_constant(constant, state)
233     }
234 
super_constant( &self, _constant: &Constant<'tcx>, _state: &mut State<Self::Value>, ) -> Self::Value235     fn super_constant(
236         &self,
237         _constant: &Constant<'tcx>,
238         _state: &mut State<Self::Value>,
239     ) -> Self::Value {
240         Self::Value::TOP
241     }
242 
243     /// The effect of a successful function call return should not be
244     /// applied here, see [`Analysis::apply_terminator_effect`].
handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>)245     fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
246         self.super_terminator(terminator, state)
247     }
248 
super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>)249     fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
250         match &terminator.kind {
251             TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
252                 // Effect is applied by `handle_call_return`.
253             }
254             TerminatorKind::Drop { place, .. } => {
255                 state.flood_with(place.as_ref(), self.map(), Self::Value::BOTTOM);
256             }
257             TerminatorKind::Yield { .. } => {
258                 // They would have an effect, but are not allowed in this phase.
259                 bug!("encountered disallowed terminator");
260             }
261             TerminatorKind::Goto { .. }
262             | TerminatorKind::SwitchInt { .. }
263             | TerminatorKind::Resume
264             | TerminatorKind::Terminate
265             | TerminatorKind::Return
266             | TerminatorKind::Unreachable
267             | TerminatorKind::Assert { .. }
268             | TerminatorKind::GeneratorDrop
269             | TerminatorKind::FalseEdge { .. }
270             | TerminatorKind::FalseUnwind { .. } => {
271                 // These terminators have no effect on the analysis.
272             }
273         }
274     }
275 
handle_call_return( &self, return_places: CallReturnPlaces<'_, 'tcx>, state: &mut State<Self::Value>, )276     fn handle_call_return(
277         &self,
278         return_places: CallReturnPlaces<'_, 'tcx>,
279         state: &mut State<Self::Value>,
280     ) {
281         self.super_call_return(return_places, state)
282     }
283 
super_call_return( &self, return_places: CallReturnPlaces<'_, 'tcx>, state: &mut State<Self::Value>, )284     fn super_call_return(
285         &self,
286         return_places: CallReturnPlaces<'_, 'tcx>,
287         state: &mut State<Self::Value>,
288     ) {
289         return_places.for_each(|place| {
290             state.flood(place.as_ref(), self.map());
291         })
292     }
293 
handle_switch_int( &self, discr: &Operand<'tcx>, apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>, )294     fn handle_switch_int(
295         &self,
296         discr: &Operand<'tcx>,
297         apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
298     ) {
299         self.super_switch_int(discr, apply_edge_effects)
300     }
301 
super_switch_int( &self, _discr: &Operand<'tcx>, _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>, )302     fn super_switch_int(
303         &self,
304         _discr: &Operand<'tcx>,
305         _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
306     ) {
307     }
308 
wrap(self) -> ValueAnalysisWrapper<Self> where Self: Sized,309     fn wrap(self) -> ValueAnalysisWrapper<Self>
310     where
311         Self: Sized,
312     {
313         ValueAnalysisWrapper(self)
314     }
315 }
316 
317 pub struct ValueAnalysisWrapper<T>(pub T);
318 
319 impl<'tcx, T: ValueAnalysis<'tcx>> AnalysisDomain<'tcx> for ValueAnalysisWrapper<T> {
320     type Domain = State<T::Value>;
321 
322     type Direction = crate::Forward;
323 
324     const NAME: &'static str = T::NAME;
325 
bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain326     fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
327         State(StateData::Unreachable)
328     }
329 
initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain)330     fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
331         // The initial state maps all tracked places of argument projections to ⊤ and the rest to ⊥.
332         assert!(matches!(state.0, StateData::Unreachable));
333         let values = IndexVec::from_elem_n(T::Value::BOTTOM, self.0.map().value_count);
334         *state = State(StateData::Reachable(values));
335         for arg in body.args_iter() {
336             state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map());
337         }
338     }
339 }
340 
341 impl<'tcx, T> Analysis<'tcx> for ValueAnalysisWrapper<T>
342 where
343     T: ValueAnalysis<'tcx>,
344 {
apply_statement_effect( &mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, _location: Location, )345     fn apply_statement_effect(
346         &mut self,
347         state: &mut Self::Domain,
348         statement: &Statement<'tcx>,
349         _location: Location,
350     ) {
351         if state.is_reachable() {
352             self.0.handle_statement(statement, state);
353         }
354     }
355 
apply_terminator_effect( &mut self, state: &mut Self::Domain, terminator: &Terminator<'tcx>, _location: Location, )356     fn apply_terminator_effect(
357         &mut self,
358         state: &mut Self::Domain,
359         terminator: &Terminator<'tcx>,
360         _location: Location,
361     ) {
362         if state.is_reachable() {
363             self.0.handle_terminator(terminator, state);
364         }
365     }
366 
apply_call_return_effect( &mut self, state: &mut Self::Domain, _block: BasicBlock, return_places: crate::CallReturnPlaces<'_, 'tcx>, )367     fn apply_call_return_effect(
368         &mut self,
369         state: &mut Self::Domain,
370         _block: BasicBlock,
371         return_places: crate::CallReturnPlaces<'_, 'tcx>,
372     ) {
373         if state.is_reachable() {
374             self.0.handle_call_return(return_places, state)
375         }
376     }
377 
apply_switch_int_edge_effects( &mut self, _block: BasicBlock, discr: &Operand<'tcx>, apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>, )378     fn apply_switch_int_edge_effects(
379         &mut self,
380         _block: BasicBlock,
381         discr: &Operand<'tcx>,
382         apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
383     ) {
384         // FIXME: Dataflow framework provides no access to current state here.
385         self.0.handle_switch_int(discr, apply_edge_effects)
386     }
387 }
388 
389 rustc_index::newtype_index!(
390     /// This index uniquely identifies a place.
391     ///
392     /// Not every place has a `PlaceIndex`, and not every `PlaceIndex` corresponds to a tracked
393     /// place. However, every tracked place and all places along its projection have a `PlaceIndex`.
394     pub struct PlaceIndex {}
395 );
396 
397 rustc_index::newtype_index!(
398     /// This index uniquely identifies a tracked place and therefore a slot in [`State`].
399     ///
400     /// It is an implementation detail of this module.
401     struct ValueIndex {}
402 );
403 
404 /// See [`State`].
405 #[derive(PartialEq, Eq, Debug)]
406 enum StateData<V> {
407     Reachable(IndexVec<ValueIndex, V>),
408     Unreachable,
409 }
410 
411 impl<V: Clone> Clone for StateData<V> {
clone(&self) -> Self412     fn clone(&self) -> Self {
413         match self {
414             Self::Reachable(x) => Self::Reachable(x.clone()),
415             Self::Unreachable => Self::Unreachable,
416         }
417     }
418 
clone_from(&mut self, source: &Self)419     fn clone_from(&mut self, source: &Self) {
420         match (&mut *self, source) {
421             (Self::Reachable(x), Self::Reachable(y)) => {
422                 // We go through `raw` here, because `IndexVec` currently has a naive `clone_from`.
423                 x.raw.clone_from(&y.raw);
424             }
425             _ => *self = source.clone(),
426         }
427     }
428 }
429 
430 /// The dataflow state for an instance of [`ValueAnalysis`].
431 ///
432 /// Every instance specifies a lattice that represents the possible values of a single tracked
433 /// place. If we call this lattice `V` and set of tracked places `P`, then a [`State`] is an
434 /// element of `{unreachable} ∪ (P -> V)`. This again forms a lattice, where the bottom element is
435 /// `unreachable` and the top element is the mapping `p ↦ ⊤`. Note that the mapping `p ↦ ⊥` is not
436 /// the bottom element (because joining an unreachable and any other reachable state yields a
437 /// reachable state). All operations on unreachable states are ignored.
438 ///
439 /// Flooding means assigning a value (by default `⊤`) to all tracked projections of a given place.
440 #[derive(PartialEq, Eq, Debug)]
441 pub struct State<V>(StateData<V>);
442 
443 impl<V: Clone> Clone for State<V> {
clone(&self) -> Self444     fn clone(&self) -> Self {
445         Self(self.0.clone())
446     }
447 
clone_from(&mut self, source: &Self)448     fn clone_from(&mut self, source: &Self) {
449         self.0.clone_from(&source.0);
450     }
451 }
452 
453 impl<V: Clone + HasTop + HasBottom> State<V> {
is_reachable(&self) -> bool454     pub fn is_reachable(&self) -> bool {
455         matches!(&self.0, StateData::Reachable(_))
456     }
457 
mark_unreachable(&mut self)458     pub fn mark_unreachable(&mut self) {
459         self.0 = StateData::Unreachable;
460     }
461 
flood_all(&mut self)462     pub fn flood_all(&mut self) {
463         self.flood_all_with(V::TOP)
464     }
465 
flood_all_with(&mut self, value: V)466     pub fn flood_all_with(&mut self, value: V) {
467         let StateData::Reachable(values) = &mut self.0 else { return };
468         values.raw.fill(value);
469     }
470 
flood_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V)471     pub fn flood_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
472         let StateData::Reachable(values) = &mut self.0 else { return };
473         map.for_each_aliasing_place(place, None, &mut |vi| {
474             values[vi] = value.clone();
475         });
476     }
477 
flood(&mut self, place: PlaceRef<'_>, map: &Map)478     pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map) {
479         self.flood_with(place, map, V::TOP)
480     }
481 
flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V)482     pub fn flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
483         let StateData::Reachable(values) = &mut self.0 else { return };
484         map.for_each_aliasing_place(place, Some(TrackElem::Discriminant), &mut |vi| {
485             values[vi] = value.clone();
486         });
487     }
488 
flood_discr(&mut self, place: PlaceRef<'_>, map: &Map)489     pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map) {
490         self.flood_discr_with(place, map, V::TOP)
491     }
492 
493     /// Low-level method that assigns to a place.
494     /// This does nothing if the place is not tracked.
495     ///
496     /// The target place must have been flooded before calling this method.
insert_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map)497     pub fn insert_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
498         match result {
499             ValueOrPlace::Value(value) => self.insert_value_idx(target, value, map),
500             ValueOrPlace::Place(source) => self.insert_place_idx(target, source, map),
501         }
502     }
503 
504     /// Low-level method that assigns a value to a place.
505     /// This does nothing if the place is not tracked.
506     ///
507     /// The target place must have been flooded before calling this method.
insert_value_idx(&mut self, target: PlaceIndex, value: V, map: &Map)508     pub fn insert_value_idx(&mut self, target: PlaceIndex, value: V, map: &Map) {
509         let StateData::Reachable(values) = &mut self.0 else { return };
510         if let Some(value_index) = map.places[target].value_index {
511             values[value_index] = value;
512         }
513     }
514 
515     /// Copies `source` to `target`, including all tracked places beneath.
516     ///
517     /// If `target` contains a place that is not contained in `source`, it will be overwritten with
518     /// Top. Also, because this will copy all entries one after another, it may only be used for
519     /// places that are non-overlapping or identical.
520     ///
521     /// The target place must have been flooded before calling this method.
insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map)522     fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
523         let StateData::Reachable(values) = &mut self.0 else { return };
524 
525         // If both places are tracked, we copy the value to the target.
526         // If the target is tracked, but the source is not, we do nothing, as invalidation has
527         // already been performed.
528         if let Some(target_value) = map.places[target].value_index {
529             if let Some(source_value) = map.places[source].value_index {
530                 values[target_value] = values[source_value].clone();
531             }
532         }
533         for target_child in map.children(target) {
534             // Try to find corresponding child and recurse. Reasoning is similar as above.
535             let projection = map.places[target_child].proj_elem.unwrap();
536             if let Some(source_child) = map.projections.get(&(source, projection)) {
537                 self.insert_place_idx(target_child, *source_child, map);
538             }
539         }
540     }
541 
542     /// Helper method to interpret `target = result`.
assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)543     pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
544         self.flood(target, map);
545         if let Some(target) = map.find(target) {
546             self.insert_idx(target, result, map);
547         }
548     }
549 
550     /// Helper method for assignments to a discriminant.
assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)551     pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
552         self.flood_discr(target, map);
553         if let Some(target) = map.find_discr(target) {
554             self.insert_idx(target, result, map);
555         }
556     }
557 
558     /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
get(&self, place: PlaceRef<'_>, map: &Map) -> V559     pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V {
560         map.find(place).map(|place| self.get_idx(place, map)).unwrap_or(V::TOP)
561     }
562 
563     /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
get_discr(&self, place: PlaceRef<'_>, map: &Map) -> V564     pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map) -> V {
565         match map.find_discr(place) {
566             Some(place) => self.get_idx(place, map),
567             None => V::TOP,
568         }
569     }
570 
571     /// Retrieve the value stored for a place index, or ⊤ if it is not tracked.
get_idx(&self, place: PlaceIndex, map: &Map) -> V572     pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V {
573         match &self.0 {
574             StateData::Reachable(values) => {
575                 map.places[place].value_index.map(|v| values[v].clone()).unwrap_or(V::TOP)
576             }
577             StateData::Unreachable => {
578                 // Because this is unreachable, we can return any value we want.
579                 V::BOTTOM
580             }
581         }
582     }
583 }
584 
585 impl<V: JoinSemiLattice + Clone> JoinSemiLattice for State<V> {
join(&mut self, other: &Self) -> bool586     fn join(&mut self, other: &Self) -> bool {
587         match (&mut self.0, &other.0) {
588             (_, StateData::Unreachable) => false,
589             (StateData::Unreachable, _) => {
590                 *self = other.clone();
591                 true
592             }
593             (StateData::Reachable(this), StateData::Reachable(other)) => this.join(other),
594         }
595     }
596 }
597 
598 /// Partial mapping from [`Place`] to [`PlaceIndex`], where some places also have a [`ValueIndex`].
599 ///
600 /// This data structure essentially maintains a tree of places and their projections. Some
601 /// additional bookkeeping is done, to speed up traversal over this tree:
602 /// - For iteration, every [`PlaceInfo`] contains an intrusive linked list of its children.
603 /// - To directly get the child for a specific projection, there is a `projections` map.
604 #[derive(Debug)]
605 pub struct Map {
606     locals: IndexVec<Local, Option<PlaceIndex>>,
607     projections: FxHashMap<(PlaceIndex, TrackElem), PlaceIndex>,
608     places: IndexVec<PlaceIndex, PlaceInfo>,
609     value_count: usize,
610     // The Range corresponds to a slice into `inner_values_buffer`.
611     inner_values: IndexVec<PlaceIndex, Range<usize>>,
612     inner_values_buffer: Vec<ValueIndex>,
613 }
614 
615 impl Map {
new() -> Self616     fn new() -> Self {
617         Self {
618             locals: IndexVec::new(),
619             projections: FxHashMap::default(),
620             places: IndexVec::new(),
621             value_count: 0,
622             inner_values: IndexVec::new(),
623             inner_values_buffer: Vec::new(),
624         }
625     }
626 
627     /// Returns a map that only tracks places whose type passes the filter.
628     ///
629     /// This is currently the only way to create a [`Map`]. The way in which the tracked places are
630     /// chosen is an implementation detail and may not be relied upon (other than that their type
631     /// passes the filter).
from_filter<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, filter: impl Fn(Ty<'tcx>) -> bool, value_limit: Option<usize>, ) -> Self632     pub fn from_filter<'tcx>(
633         tcx: TyCtxt<'tcx>,
634         body: &Body<'tcx>,
635         filter: impl Fn(Ty<'tcx>) -> bool,
636         value_limit: Option<usize>,
637     ) -> Self {
638         let mut map = Self::new();
639         let exclude = excluded_locals(body);
640         map.register_with_filter(tcx, body, filter, exclude, value_limit);
641         debug!("registered {} places ({} nodes in total)", map.value_count, map.places.len());
642         map
643     }
644 
645     /// Register all non-excluded places that pass the filter.
register_with_filter<'tcx>( &mut self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>, filter: impl Fn(Ty<'tcx>) -> bool, exclude: BitSet<Local>, value_limit: Option<usize>, )646     fn register_with_filter<'tcx>(
647         &mut self,
648         tcx: TyCtxt<'tcx>,
649         body: &Body<'tcx>,
650         filter: impl Fn(Ty<'tcx>) -> bool,
651         exclude: BitSet<Local>,
652         value_limit: Option<usize>,
653     ) {
654         let mut worklist = VecDeque::with_capacity(value_limit.unwrap_or(body.local_decls.len()));
655 
656         // Start by constructing the places for each bare local.
657         self.locals = IndexVec::from_elem(None, &body.local_decls);
658         for (local, decl) in body.local_decls.iter_enumerated() {
659             if exclude.contains(local) {
660                 continue;
661             }
662 
663             // Create a place for the local.
664             debug_assert!(self.locals[local].is_none());
665             let place = self.places.push(PlaceInfo::new(None));
666             self.locals[local] = Some(place);
667 
668             // And push the eventual children places to the worklist.
669             self.register_children(tcx, place, decl.ty, &filter, &mut worklist);
670         }
671 
672         // `place.elem1.elem2` with type `ty`.
673         // `elem1` is either `Some(Variant(i))` or `None`.
674         while let Some((mut place, elem1, elem2, ty)) = worklist.pop_front() {
675             // The user requires a bound on the number of created values.
676             if let Some(value_limit) = value_limit && self.value_count >= value_limit {
677                 break
678             }
679 
680             // Create a place for this projection.
681             for elem in [elem1, Some(elem2)].into_iter().flatten() {
682                 place = *self.projections.entry((place, elem)).or_insert_with(|| {
683                     // Prepend new child to the linked list.
684                     let next = self.places.push(PlaceInfo::new(Some(elem)));
685                     self.places[next].next_sibling = self.places[place].first_child;
686                     self.places[place].first_child = Some(next);
687                     next
688                 });
689             }
690 
691             // And push the eventual children places to the worklist.
692             self.register_children(tcx, place, ty, &filter, &mut worklist);
693         }
694 
695         // Pre-compute the tree of ValueIndex nested in each PlaceIndex.
696         // `inner_values_buffer[inner_values[place]]` is the set of all the values
697         // reachable by projecting `place`.
698         self.inner_values_buffer = Vec::with_capacity(self.value_count);
699         self.inner_values = IndexVec::from_elem(0..0, &self.places);
700         for local in body.local_decls.indices() {
701             if let Some(place) = self.locals[local] {
702                 self.cache_preorder_invoke(place);
703             }
704         }
705 
706         // Trim useless places.
707         for opt_place in self.locals.iter_mut() {
708             if let Some(place) = *opt_place && self.inner_values[place].is_empty() {
709                 *opt_place = None;
710             }
711         }
712         #[allow(rustc::potential_query_instability)]
713         self.projections.retain(|_, child| !self.inner_values[*child].is_empty());
714     }
715 
716     /// Potentially register the (local, projection) place and its fields, recursively.
717     ///
718     /// Invariant: The projection must only contain trackable elements.
register_children<'tcx>( &mut self, tcx: TyCtxt<'tcx>, place: PlaceIndex, ty: Ty<'tcx>, filter: &impl Fn(Ty<'tcx>) -> bool, worklist: &mut VecDeque<(PlaceIndex, Option<TrackElem>, TrackElem, Ty<'tcx>)>, )719     fn register_children<'tcx>(
720         &mut self,
721         tcx: TyCtxt<'tcx>,
722         place: PlaceIndex,
723         ty: Ty<'tcx>,
724         filter: &impl Fn(Ty<'tcx>) -> bool,
725         worklist: &mut VecDeque<(PlaceIndex, Option<TrackElem>, TrackElem, Ty<'tcx>)>,
726     ) {
727         // Allocate a value slot if it doesn't have one, and the user requested one.
728         if self.places[place].value_index.is_none() && filter(ty) {
729             self.places[place].value_index = Some(self.value_count.into());
730             self.value_count += 1;
731         }
732 
733         // For enums, directly create the `Discriminant`, as that's their main use.
734         if ty.is_enum() {
735             let discr_ty = ty.discriminant_ty(tcx);
736             if filter(discr_ty) {
737                 let discr = *self
738                     .projections
739                     .entry((place, TrackElem::Discriminant))
740                     .or_insert_with(|| {
741                         // Prepend new child to the linked list.
742                         let next = self.places.push(PlaceInfo::new(Some(TrackElem::Discriminant)));
743                         self.places[next].next_sibling = self.places[place].first_child;
744                         self.places[place].first_child = Some(next);
745                         next
746                     });
747 
748                 // Allocate a value slot if it doesn't have one.
749                 if self.places[discr].value_index.is_none() {
750                     self.places[discr].value_index = Some(self.value_count.into());
751                     self.value_count += 1;
752                 }
753             }
754         }
755 
756         // Recurse with all fields of this place.
757         iter_fields(ty, tcx, ty::ParamEnv::reveal_all(), |variant, field, ty| {
758             worklist.push_back((
759                 place,
760                 variant.map(TrackElem::Variant),
761                 TrackElem::Field(field),
762                 ty,
763             ))
764         });
765     }
766 
767     /// Precompute the list of values inside `root` and store it inside
768     /// as a slice within `inner_values_buffer`.
cache_preorder_invoke(&mut self, root: PlaceIndex)769     fn cache_preorder_invoke(&mut self, root: PlaceIndex) {
770         let start = self.inner_values_buffer.len();
771         if let Some(vi) = self.places[root].value_index {
772             self.inner_values_buffer.push(vi);
773         }
774 
775         // We manually iterate instead of using `children` as we need to mutate `self`.
776         let mut next_child = self.places[root].first_child;
777         while let Some(child) = next_child {
778             ensure_sufficient_stack(|| self.cache_preorder_invoke(child));
779             next_child = self.places[child].next_sibling;
780         }
781 
782         let end = self.inner_values_buffer.len();
783         self.inner_values[root] = start..end;
784     }
785 
786     /// Returns the number of tracked places, i.e., those for which a value can be stored.
tracked_places(&self) -> usize787     pub fn tracked_places(&self) -> usize {
788         self.value_count
789     }
790 
791     /// Applies a single projection element, yielding the corresponding child.
apply(&self, place: PlaceIndex, elem: TrackElem) -> Option<PlaceIndex>792     pub fn apply(&self, place: PlaceIndex, elem: TrackElem) -> Option<PlaceIndex> {
793         self.projections.get(&(place, elem)).copied()
794     }
795 
796     /// Locates the given place, if it exists in the tree.
find_extra( &self, place: PlaceRef<'_>, extra: impl IntoIterator<Item = TrackElem>, ) -> Option<PlaceIndex>797     pub fn find_extra(
798         &self,
799         place: PlaceRef<'_>,
800         extra: impl IntoIterator<Item = TrackElem>,
801     ) -> Option<PlaceIndex> {
802         let mut index = *self.locals[place.local].as_ref()?;
803 
804         for &elem in place.projection {
805             index = self.apply(index, elem.try_into().ok()?)?;
806         }
807         for elem in extra {
808             index = self.apply(index, elem)?;
809         }
810 
811         Some(index)
812     }
813 
814     /// Locates the given place, if it exists in the tree.
find(&self, place: PlaceRef<'_>) -> Option<PlaceIndex>815     pub fn find(&self, place: PlaceRef<'_>) -> Option<PlaceIndex> {
816         self.find_extra(place, [])
817     }
818 
819     /// Locates the given place and applies `Discriminant`, if it exists in the tree.
find_discr(&self, place: PlaceRef<'_>) -> Option<PlaceIndex>820     pub fn find_discr(&self, place: PlaceRef<'_>) -> Option<PlaceIndex> {
821         self.find_extra(place, [TrackElem::Discriminant])
822     }
823 
824     /// Iterate over all direct children.
children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_825     pub fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
826         Children::new(self, parent)
827     }
828 
829     /// Invoke a function on the given place and all places that may alias it.
830     ///
831     /// In particular, when the given place has a variant downcast, we invoke the function on all
832     /// the other variants.
833     ///
834     /// `tail_elem` allows to support discriminants that are not a place in MIR, but that we track
835     /// as such.
for_each_aliasing_place( &self, place: PlaceRef<'_>, tail_elem: Option<TrackElem>, f: &mut impl FnMut(ValueIndex), )836     fn for_each_aliasing_place(
837         &self,
838         place: PlaceRef<'_>,
839         tail_elem: Option<TrackElem>,
840         f: &mut impl FnMut(ValueIndex),
841     ) {
842         if place.has_deref() {
843             // We do not track indirect places.
844             return;
845         }
846         let Some(mut index) = self.locals[place.local] else {
847             // The local is not tracked at all, so it does not alias anything.
848             return;
849         };
850         let elems = place
851             .projection
852             .iter()
853             .map(|&elem| elem.try_into())
854             .chain(tail_elem.map(Ok).into_iter());
855         for elem in elems {
856             // A field aliases the parent place.
857             if let Some(vi) = self.places[index].value_index {
858                 f(vi);
859             }
860 
861             let Ok(elem) = elem else { return };
862             let sub = self.apply(index, elem);
863             if let TrackElem::Variant(..) | TrackElem::Discriminant = elem {
864                 // Enum variant fields and enum discriminants alias each another.
865                 self.for_each_variant_sibling(index, sub, f);
866             }
867             if let Some(sub) = sub {
868                 index = sub
869             } else {
870                 return;
871             }
872         }
873         self.for_each_value_inside(index, f);
874     }
875 
876     /// Invoke the given function on all the descendants of the given place, except one branch.
for_each_variant_sibling( &self, parent: PlaceIndex, preserved_child: Option<PlaceIndex>, f: &mut impl FnMut(ValueIndex), )877     fn for_each_variant_sibling(
878         &self,
879         parent: PlaceIndex,
880         preserved_child: Option<PlaceIndex>,
881         f: &mut impl FnMut(ValueIndex),
882     ) {
883         for sibling in self.children(parent) {
884             let elem = self.places[sibling].proj_elem;
885             // Only invalidate variants and discriminant. Fields (for generators) are not
886             // invalidated by assignment to a variant.
887             if let Some(TrackElem::Variant(..) | TrackElem::Discriminant) = elem
888                 // Only invalidate the other variants, the current one is fine.
889                 && Some(sibling) != preserved_child
890             {
891                 self.for_each_value_inside(sibling, f);
892             }
893         }
894     }
895 
896     /// Invoke a function on each value in the given place and all descendants.
for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex))897     fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) {
898         let range = self.inner_values[root].clone();
899         let values = &self.inner_values_buffer[range];
900         for &v in values {
901             f(v)
902         }
903     }
904 }
905 
906 /// This is the information tracked for every [`PlaceIndex`] and is stored by [`Map`].
907 ///
908 /// Together, `first_child` and `next_sibling` form an intrusive linked list, which is used to
909 /// model a tree structure (a replacement for a member like `children: Vec<PlaceIndex>`).
910 #[derive(Debug)]
911 struct PlaceInfo {
912     /// We store a [`ValueIndex`] if and only if the placed is tracked by the analysis.
913     value_index: Option<ValueIndex>,
914 
915     /// The projection used to go from parent to this node (only None for root).
916     proj_elem: Option<TrackElem>,
917 
918     /// The left-most child.
919     first_child: Option<PlaceIndex>,
920 
921     /// Index of the sibling to the right of this node.
922     next_sibling: Option<PlaceIndex>,
923 }
924 
925 impl PlaceInfo {
new(proj_elem: Option<TrackElem>) -> Self926     fn new(proj_elem: Option<TrackElem>) -> Self {
927         Self { next_sibling: None, first_child: None, proj_elem, value_index: None }
928     }
929 }
930 
931 struct Children<'a> {
932     map: &'a Map,
933     next: Option<PlaceIndex>,
934 }
935 
936 impl<'a> Children<'a> {
new(map: &'a Map, parent: PlaceIndex) -> Self937     fn new(map: &'a Map, parent: PlaceIndex) -> Self {
938         Self { map, next: map.places[parent].first_child }
939     }
940 }
941 
942 impl<'a> Iterator for Children<'a> {
943     type Item = PlaceIndex;
944 
next(&mut self) -> Option<Self::Item>945     fn next(&mut self) -> Option<Self::Item> {
946         match self.next {
947             Some(child) => {
948                 self.next = self.map.places[child].next_sibling;
949                 Some(child)
950             }
951             None => None,
952         }
953     }
954 }
955 
956 /// Used as the result of an operand or r-value.
957 #[derive(Debug)]
958 pub enum ValueOrPlace<V> {
959     Value(V),
960     Place(PlaceIndex),
961 }
962 
963 impl<V: HasTop> ValueOrPlace<V> {
964     pub const TOP: Self = ValueOrPlace::Value(V::TOP);
965 }
966 
967 /// The set of projection elements that can be used by a tracked place.
968 ///
969 /// Although only field projections are currently allowed, this could change in the future.
970 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
971 pub enum TrackElem {
972     Field(FieldIdx),
973     Variant(VariantIdx),
974     Discriminant,
975 }
976 
977 impl<V, T> TryFrom<ProjectionElem<V, T>> for TrackElem {
978     type Error = ();
979 
try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error>980     fn try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error> {
981         match value {
982             ProjectionElem::Field(field, _) => Ok(TrackElem::Field(field)),
983             ProjectionElem::Downcast(_, idx) => Ok(TrackElem::Variant(idx)),
984             _ => Err(()),
985         }
986     }
987 }
988 
989 /// Invokes `f` on all direct fields of `ty`.
iter_fields<'tcx>( ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, mut f: impl FnMut(Option<VariantIdx>, FieldIdx, Ty<'tcx>), )990 pub fn iter_fields<'tcx>(
991     ty: Ty<'tcx>,
992     tcx: TyCtxt<'tcx>,
993     param_env: ty::ParamEnv<'tcx>,
994     mut f: impl FnMut(Option<VariantIdx>, FieldIdx, Ty<'tcx>),
995 ) {
996     match ty.kind() {
997         ty::Tuple(list) => {
998             for (field, ty) in list.iter().enumerate() {
999                 f(None, field.into(), ty);
1000             }
1001         }
1002         ty::Adt(def, substs) => {
1003             if def.is_union() {
1004                 return;
1005             }
1006             for (v_index, v_def) in def.variants().iter_enumerated() {
1007                 let variant = if def.is_struct() { None } else { Some(v_index) };
1008                 for (f_index, f_def) in v_def.fields.iter().enumerate() {
1009                     let field_ty = f_def.ty(tcx, substs);
1010                     let field_ty = tcx
1011                         .try_normalize_erasing_regions(param_env, field_ty)
1012                         .unwrap_or_else(|_| tcx.erase_regions(field_ty));
1013                     f(variant, f_index.into(), field_ty);
1014                 }
1015             }
1016         }
1017         ty::Closure(_, substs) => {
1018             iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, param_env, f);
1019         }
1020         _ => (),
1021     }
1022 }
1023 
1024 /// Returns all locals with projections that have their reference or address taken.
excluded_locals(body: &Body<'_>) -> BitSet<Local>1025 pub fn excluded_locals(body: &Body<'_>) -> BitSet<Local> {
1026     struct Collector {
1027         result: BitSet<Local>,
1028     }
1029 
1030     impl<'tcx> Visitor<'tcx> for Collector {
1031         fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
1032             if (context.is_borrow()
1033                 || context.is_address_of()
1034                 || context.is_drop()
1035                 || context == PlaceContext::MutatingUse(MutatingUseContext::AsmOutput))
1036                 && !place.is_indirect()
1037             {
1038                 // A pointer to a place could be used to access other places with the same local,
1039                 // hence we have to exclude the local completely.
1040                 self.result.insert(place.local);
1041             }
1042         }
1043     }
1044 
1045     let mut collector = Collector { result: BitSet::new_empty(body.local_decls.len()) };
1046     collector.visit_body(body);
1047     collector.result
1048 }
1049 
1050 /// This is used to visualize the dataflow analysis.
1051 impl<'tcx, T> DebugWithContext<ValueAnalysisWrapper<T>> for State<T::Value>
1052 where
1053     T: ValueAnalysis<'tcx>,
1054     T::Value: Debug,
1055 {
fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result1056     fn fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result {
1057         match &self.0 {
1058             StateData::Reachable(values) => debug_with_context(values, None, ctxt.0.map(), f),
1059             StateData::Unreachable => write!(f, "unreachable"),
1060         }
1061     }
1062 
fmt_diff_with( &self, old: &Self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>, ) -> std::fmt::Result1063     fn fmt_diff_with(
1064         &self,
1065         old: &Self,
1066         ctxt: &ValueAnalysisWrapper<T>,
1067         f: &mut Formatter<'_>,
1068     ) -> std::fmt::Result {
1069         match (&self.0, &old.0) {
1070             (StateData::Reachable(this), StateData::Reachable(old)) => {
1071                 debug_with_context(this, Some(old), ctxt.0.map(), f)
1072             }
1073             _ => Ok(()), // Consider printing something here.
1074         }
1075     }
1076 }
1077 
debug_with_context_rec<V: Debug + Eq>( place: PlaceIndex, place_str: &str, new: &IndexSlice<ValueIndex, V>, old: Option<&IndexSlice<ValueIndex, V>>, map: &Map, f: &mut Formatter<'_>, ) -> std::fmt::Result1078 fn debug_with_context_rec<V: Debug + Eq>(
1079     place: PlaceIndex,
1080     place_str: &str,
1081     new: &IndexSlice<ValueIndex, V>,
1082     old: Option<&IndexSlice<ValueIndex, V>>,
1083     map: &Map,
1084     f: &mut Formatter<'_>,
1085 ) -> std::fmt::Result {
1086     if let Some(value) = map.places[place].value_index {
1087         match old {
1088             None => writeln!(f, "{}: {:?}", place_str, new[value])?,
1089             Some(old) => {
1090                 if new[value] != old[value] {
1091                     writeln!(f, "\u{001f}-{}: {:?}", place_str, old[value])?;
1092                     writeln!(f, "\u{001f}+{}: {:?}", place_str, new[value])?;
1093                 }
1094             }
1095         }
1096     }
1097 
1098     for child in map.children(place) {
1099         let info_elem = map.places[child].proj_elem.unwrap();
1100         let child_place_str = match info_elem {
1101             TrackElem::Discriminant => {
1102                 format!("discriminant({})", place_str)
1103             }
1104             TrackElem::Variant(idx) => {
1105                 format!("({} as {:?})", place_str, idx)
1106             }
1107             TrackElem::Field(field) => {
1108                 if place_str.starts_with('*') {
1109                     format!("({}).{}", place_str, field.index())
1110                 } else {
1111                     format!("{}.{}", place_str, field.index())
1112                 }
1113             }
1114         };
1115         debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
1116     }
1117 
1118     Ok(())
1119 }
1120 
debug_with_context<V: Debug + Eq>( new: &IndexSlice<ValueIndex, V>, old: Option<&IndexSlice<ValueIndex, V>>, map: &Map, f: &mut Formatter<'_>, ) -> std::fmt::Result1121 fn debug_with_context<V: Debug + Eq>(
1122     new: &IndexSlice<ValueIndex, V>,
1123     old: Option<&IndexSlice<ValueIndex, V>>,
1124     map: &Map,
1125     f: &mut Formatter<'_>,
1126 ) -> std::fmt::Result {
1127     for (local, place) in map.locals.iter_enumerated() {
1128         if let Some(place) = place {
1129             debug_with_context_rec(*place, &format!("{local:?}"), new, old, map, f)?;
1130         }
1131     }
1132     Ok(())
1133 }
1134