1 //! This module provides a framework on top of the normal MIR dataflow framework to simplify the
2 //! implementation of analyses that track the values stored in places of interest.
4 //! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`)
5 //! provide some behavior that should be valid for all abstract domains that are based only on the
6 //! value stored in a certain place. On top of these default rules, an implementation should
7 //! override some of the `handle_` methods. For an example, see `ConstAnalysis`.
9 //! An implementation must also provide a [`Map`]. Before the anaylsis begins, all places that
10 //! should be tracked during the analysis must be registered. Currently, the projections of these
11 //! places may only contain derefs, fields and downcasts (otherwise registration fails). During the
12 //! analysis, no new places can be registered.
14 //! Note that if you want to track values behind references, you have to register the dereferenced
15 //! place. For example: Assume `let x = (0, 0)` and that we want to propagate values from `x.0` and
16 //! `x.1` also through the assignment `let y = &x`. In this case, we should register `x.0`, `x.1`,
17 //! `(*y).0` and `(*y).1`.
19 use std::fmt::{Debug, Formatter};
21 use rustc_data_structures::fx::FxHashMap;
22 use rustc_index::vec::IndexVec;
23 use rustc_middle::mir::*;
24 use rustc_middle::ty::{self, Ty, TyCtxt};
25 use rustc_target::abi::VariantIdx;
28 fmt::DebugWithContext, lattice::FlatSet, Analysis, AnalysisDomain, CallReturnPlaces,
29 JoinSemiLattice, SwitchIntEdgeEffects,
32 pub trait ValueAnalysis<'tcx> {
33 /// For each place of interest, the analysis tracks a value of the given type.
34 type Value: Clone + JoinSemiLattice + HasBottom + HasTop;
36 const NAME: &'static str;
38 fn map(&self) -> ⤅
40 fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
41 self.super_statement(statement, state)
44 fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
45 match &statement.kind {
46 StatementKind::Assign(box (place, rvalue)) => {
47 self.handle_assign(*place, rvalue, state);
49 StatementKind::SetDiscriminant { .. } => {
50 // Could treat this as writing a constant to a pseudo-place.
52 StatementKind::CopyNonOverlapping(..) => {
53 // FIXME: What to do here?
55 StatementKind::StorageLive(..)
56 | StatementKind::StorageDead(..)
57 | StatementKind::Deinit(_) => {
58 // Could perhaps use these.
61 | StatementKind::Retag(..)
62 | StatementKind::FakeRead(..)
63 | StatementKind::Coverage(..)
64 | StatementKind::AscribeUserType(..) => (),
71 rvalue: &Rvalue<'tcx>,
72 state: &mut State<Self::Value>,
74 self.super_assign(target, rvalue, state)
80 rvalue: &Rvalue<'tcx>,
81 state: &mut State<Self::Value>,
84 Rvalue::Ref(_, BorrowKind::Shared, place) => {
85 let target_deref = self
87 .find(target.as_ref())
88 .and_then(|target| self.map().apply_elem(target, ProjElem::Deref));
89 let place = self.map().find(place.as_ref());
90 match (target_deref, place) {
91 (Some(target_deref), Some(place)) => {
92 state.assign_idx(target_deref, ValueOrPlace::Place(place), self.map())
97 Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
98 state.flood(place.as_ref(), self.map(), Self::Value::top());
101 let result = self.handle_rvalue(rvalue, state);
102 state.assign(target.as_ref(), result, self.map());
109 rvalue: &Rvalue<'tcx>,
110 state: &mut State<Self::Value>,
111 ) -> ValueOrPlace<Self::Value> {
112 self.super_rvalue(rvalue, state)
117 rvalue: &Rvalue<'tcx>,
118 state: &mut State<Self::Value>,
119 ) -> ValueOrPlace<Self::Value> {
121 Rvalue::Use(operand) => self.handle_operand(operand, state),
122 Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state),
123 Rvalue::Ref(..) | Rvalue::AddressOf(..) => {
124 bug!("this rvalue must be handled by handle_assign() or super_assign()")
127 // FIXME: Check that other Rvalues really have no side-effect.
128 ValueOrPlace::Unknown
135 operand: &Operand<'tcx>,
136 state: &mut State<Self::Value>,
137 ) -> ValueOrPlace<Self::Value> {
138 self.super_operand(operand, state)
143 operand: &Operand<'tcx>,
144 state: &mut State<Self::Value>,
145 ) -> ValueOrPlace<Self::Value> {
147 Operand::Constant(box constant) => {
148 ValueOrPlace::Value(self.handle_constant(constant, state))
150 Operand::Copy(place) | Operand::Move(place) => {
151 // Do want want to handle moves different? Could flood place with bottom.
153 .find(place.as_ref())
154 .map(ValueOrPlace::Place)
155 .unwrap_or(ValueOrPlace::Unknown)
162 constant: &Constant<'tcx>,
163 state: &mut State<Self::Value>,
165 self.super_constant(constant, state)
170 _constant: &Constant<'tcx>,
171 _state: &mut State<Self::Value>,
176 fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
177 self.super_terminator(terminator, state)
180 fn super_terminator(&self, _terminator: &Terminator<'tcx>, _state: &mut State<Self::Value>) {}
182 fn handle_call_return(
184 return_places: CallReturnPlaces<'_, 'tcx>,
185 state: &mut State<Self::Value>,
187 self.super_call_return(return_places, state)
190 fn super_call_return(
192 return_places: CallReturnPlaces<'_, 'tcx>,
193 state: &mut State<Self::Value>,
195 return_places.for_each(|place| {
196 state.flood(place.as_ref(), self.map(), Self::Value::top());
200 fn handle_switch_int(
202 discr: &Operand<'tcx>,
203 apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
205 self.super_switch_int(discr, apply_edge_effects)
210 _discr: &Operand<'tcx>,
211 _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
215 fn wrap(self) -> ValueAnalysisWrapper<Self>
219 ValueAnalysisWrapper(self)
223 pub struct ValueAnalysisWrapper<T>(pub T);
225 impl<'tcx, T: ValueAnalysis<'tcx>> AnalysisDomain<'tcx> for ValueAnalysisWrapper<T> {
226 type Domain = State<T::Value>;
228 type Direction = crate::Forward;
230 const NAME: &'static str = T::NAME;
232 fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
233 State(IndexVec::from_elem_n(T::Value::bottom(), self.0.map().value_count))
236 fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
237 for arg in body.args_iter() {
238 state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map(), T::Value::top());
243 impl<'tcx, T> Analysis<'tcx> for ValueAnalysisWrapper<T>
245 T: ValueAnalysis<'tcx>,
247 fn apply_statement_effect(
249 state: &mut Self::Domain,
250 statement: &Statement<'tcx>,
253 self.0.handle_statement(statement, state);
256 fn apply_terminator_effect(
258 state: &mut Self::Domain,
259 terminator: &Terminator<'tcx>,
262 self.0.handle_terminator(terminator, state);
265 fn apply_call_return_effect(
267 state: &mut Self::Domain,
269 return_places: crate::CallReturnPlaces<'_, 'tcx>,
271 self.0.handle_call_return(return_places, state)
274 fn apply_switch_int_edge_effects(
277 discr: &Operand<'tcx>,
278 apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
280 self.0.handle_switch_int(discr, apply_edge_effects)
284 rustc_index::newtype_index!(
285 pub struct PlaceIndex {}
288 rustc_index::newtype_index!(
292 #[derive(PartialEq, Eq, Clone, Debug)]
293 pub struct State<V>(IndexVec<ValueIndex, V>);
295 impl<V: Clone + HasTop> State<V> {
296 pub fn flood_all(&mut self, value: V) {
297 self.0.raw.fill(value);
300 pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
301 if let Some(root) = map.find(place) {
302 self.flood_idx(root, map, value);
306 pub fn flood_idx(&mut self, place: PlaceIndex, map: &Map, value: V) {
307 map.preorder_invoke(place, &mut |place| {
308 if let Some(vi) = map.places[place].value_index {
309 self.0[vi] = value.clone();
314 pub fn assign_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
315 if let Some(target_value) = map.places[target].value_index {
316 if let Some(source_value) = map.places[source].value_index {
317 self.0[target_value] = self.0[source_value].clone();
319 self.0[target_value] = V::top();
322 for target_child in map.children(target) {
323 // Try to find corresponding child in source.
324 let projection = map.places[target_child].proj_elem.unwrap();
325 if let Some(source_child) = map.projections.get(&(source, projection)) {
326 self.assign_place_idx(target_child, *source_child, map);
328 self.flood_idx(target_child, map, V::top());
333 pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
334 if let Some(target) = map.find(target) {
335 self.assign_idx(target, result, map);
339 pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
341 ValueOrPlace::Value(value) => {
342 // FIXME: What if not all tracked projections are overwritten? Can this happen?
343 if let Some(value_index) = map.places[target].value_index {
344 self.0[value_index] = value;
347 ValueOrPlace::Place(source) => self.assign_place_idx(target, source, map),
348 ValueOrPlace::Unknown => {
349 self.flood_idx(target, map, V::top());
354 pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V {
355 map.find(place).map(|place| self.get_idx(place, map)).unwrap_or(V::top())
358 pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V {
359 map.places[place].value_index.map(|v| self.0[v].clone()).unwrap_or(V::top())
363 impl<V: JoinSemiLattice> JoinSemiLattice for State<V> {
364 fn join(&mut self, other: &Self) -> bool {
365 self.0.join(&other.0)
371 locals: IndexVec<Local, Option<PlaceIndex>>,
372 projections: FxHashMap<(PlaceIndex, ProjElem), PlaceIndex>,
373 places: IndexVec<PlaceIndex, PlaceInfo>,
378 pub fn new() -> Self {
380 locals: IndexVec::new(),
381 projections: FxHashMap::default(),
382 places: IndexVec::new(),
387 /// Register all places with suitable types up to a certain derefence depth (to prevent cycles).
388 pub fn register_with_filter<'tcx>(
391 source: &impl HasLocalDecls<'tcx>,
393 mut filter: impl FnMut(Ty<'tcx>) -> bool,
395 let mut projection = Vec::new();
396 for (local, decl) in source.local_decls().iter_enumerated() {
397 self.register_with_filter_rec(
408 fn register_with_filter_rec<'tcx>(
413 projection: &mut Vec<PlaceElem<'tcx>>,
415 filter: &mut impl FnMut(Ty<'tcx>) -> bool,
418 self.register(local, projection)
419 .expect("projection should only contain convertible elements");
422 if let Some(ty::TypeAndMut { ty, .. }) = ty.builtin_deref(false) {
423 projection.push(PlaceElem::Deref);
424 self.register_with_filter_rec(tcx, max_derefs - 1, local, projection, ty, filter);
428 iter_fields(ty, tcx, |variant, field, ty| {
429 if let Some(variant) = variant {
430 projection.push(PlaceElem::Downcast(None, variant));
432 projection.push(PlaceElem::Field(field, ty));
433 self.register_with_filter_rec(tcx, max_derefs, local, projection, ty, filter);
435 if variant.is_some() {
441 pub fn register<'tcx>(
444 projection: &[PlaceElem<'tcx>],
445 ) -> Result<(), ()> {
446 // Get the base index of the local.
448 *self.locals.get_or_insert_with(local, || self.places.push(PlaceInfo::new(None)));
450 // Apply the projection.
451 for &elem in projection {
452 let elem = elem.try_into()?;
453 index = *self.projections.entry((index, elem)).or_insert_with(|| {
454 // Prepend new child to the linked list.
455 let next = self.places.push(PlaceInfo::new(Some(elem)));
456 self.places[next].next_sibling = self.places[index].first_child;
457 self.places[index].first_child = Some(next);
462 // Allocate a value slot if it doesn't have one.
463 if self.places[index].value_index.is_none() {
464 self.places[index].value_index = Some(self.value_count.into());
465 self.value_count += 1;
471 pub fn apply_elem(&self, place: PlaceIndex, elem: ProjElem) -> Option<PlaceIndex> {
472 self.projections.get(&(place, elem)).copied()
475 pub fn find(&self, place: PlaceRef<'_>) -> Option<PlaceIndex> {
476 let mut index = *self.locals.get(place.local)?.as_ref()?;
478 for &elem in place.projection {
479 index = self.apply_elem(index, elem.try_into().ok()?)?;
485 pub fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
486 Children::new(self, parent)
489 pub fn preorder_invoke(&self, root: PlaceIndex, f: &mut impl FnMut(PlaceIndex)) {
491 for child in self.children(root) {
492 self.preorder_invoke(child, f);
499 next_sibling: Option<PlaceIndex>,
500 first_child: Option<PlaceIndex>,
501 /// The projection used to go from parent to this node (only None for root).
502 proj_elem: Option<ProjElem>,
503 value_index: Option<ValueIndex>,
507 fn new(proj_elem: Option<ProjElem>) -> Self {
508 Self { next_sibling: None, first_child: None, proj_elem, value_index: None }
512 struct Children<'a> {
514 next: Option<PlaceIndex>,
517 impl<'a> Children<'a> {
518 fn new(map: &'a Map, parent: PlaceIndex) -> Self {
519 Self { map, next: map.places[parent].first_child }
523 impl<'a> Iterator for Children<'a> {
524 type Item = PlaceIndex;
526 fn next(&mut self) -> Option<Self::Item> {
529 self.next = self.map.places[child].next_sibling;
537 // FIXME: See if we can get rid of `Unknown`.
538 pub enum ValueOrPlace<V> {
544 pub trait HasBottom {
552 impl<V> HasBottom for FlatSet<V> {
553 fn bottom() -> Self {
558 impl<V> HasTop for FlatSet<V> {
564 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
568 Downcast(VariantIdx),
571 impl<V, T> TryFrom<ProjectionElem<V, T>> for ProjElem {
574 fn try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error> {
576 ProjectionElem::Deref => Ok(ProjElem::Deref),
577 ProjectionElem::Field(field, _) => Ok(ProjElem::Field(field)),
578 ProjectionElem::Downcast(_, variant) => Ok(ProjElem::Downcast(variant)),
584 fn iter_fields<'tcx>(
587 mut f: impl FnMut(Option<VariantIdx>, Field, Ty<'tcx>),
591 for (field, ty) in list.iter().enumerate() {
592 f(None, field.into(), ty);
595 ty::Adt(def, substs) => {
596 for (v_index, v_def) in def.variants().iter_enumerated() {
597 for (f_index, f_def) in v_def.fields.iter().enumerate() {
598 let field_ty = f_def.ty(tcx, substs);
600 .try_normalize_erasing_regions(ty::ParamEnv::reveal_all(), field_ty)
601 .unwrap_or(field_ty);
602 f(Some(v_index), f_index.into(), field_ty);
606 ty::Closure(_, substs) => {
607 iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, f);
613 fn debug_with_context_rec<V: Debug + Eq>(
617 old: Option<&State<V>>,
619 f: &mut Formatter<'_>,
620 ) -> std::fmt::Result {
621 if let Some(value) = map.places[place].value_index {
623 None => writeln!(f, "{}: {:?}", place_str, new.0[value])?,
625 if new.0[value] != old.0[value] {
626 writeln!(f, "\u{001f}-{}: {:?}", place_str, old.0[value])?;
627 writeln!(f, "\u{001f}+{}: {:?}", place_str, new.0[value])?;
633 for child in map.children(place) {
634 let info_elem = map.places[child].proj_elem.unwrap();
635 let child_place_str = match info_elem {
636 ProjElem::Deref => format!("*{}", place_str),
637 ProjElem::Field(field) => {
638 if place_str.starts_with("*") {
639 format!("({}).{}", place_str, field.index())
641 format!("{}.{}", place_str, field.index())
644 ProjElem::Downcast(variant) => format!("({} as #{})", place_str, variant.index()),
646 debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
652 fn debug_with_context<V: Debug + Eq>(
654 old: Option<&State<V>>,
656 f: &mut Formatter<'_>,
657 ) -> std::fmt::Result {
658 for (local, place) in map.locals.iter_enumerated() {
659 if let Some(place) = place {
660 debug_with_context_rec(*place, &format!("{:?}", local), new, old, map, f)?;
666 impl<'tcx, T> DebugWithContext<ValueAnalysisWrapper<T>> for State<T::Value>
668 T: ValueAnalysis<'tcx>,
671 fn fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result {
672 debug_with_context(self, None, ctxt.0.map(), f)
678 ctxt: &ValueAnalysisWrapper<T>,
679 f: &mut Formatter<'_>,
680 ) -> std::fmt::Result {
681 debug_with_context(self, Some(old), ctxt.0.map(), f)