rapx/verify/vm/alias_tree.rs
1//! Per-function alias *derivation* forest, used only for field-path resolution.
2//!
3//! Every pointer-bearing local is a node; an edge `parent ← child` records that
4//! the child was derived from the parent (reborrow, raw-pointer cast, field
5//! read), each edge carrying the field path (`self.node.ptr` → `[node, ptr]`).
6//! [`AliasTree::resolve_local_to_root`] walks the forest to the root local,
7//! concatenating field paths, which the escape/encapsulation analysis uses to
8//! find *which* struct field a view came from.
9//!
10//! The view-vs-view (shared-XOR-mutable) aliasing check no longer lives here —
11//! it is flow-sensitive and walks the VM's current locals + allocation `parent`
12//! chain (`flow_xor_violation` in `vm/alias.rs`), so this static forest is not
13//! consulted for live-value grouping.
14
15use rustc_hash::FxHashMap;
16use rustc_hir::def_id::DefId;
17use rustc_middle::mir::{Local, Operand, Rvalue, StatementKind};
18use rustc_middle::ty::{Ty, TyCtxt, TyKind};
19
20/// A node identifier (index into [`AliasTree::nodes`]).
21pub(crate) type TagId = usize;
22
23/// A single node in the alias forest.
24#[derive(Clone, Debug)]
25pub(crate) struct AliasNode {
26 pub parent: Option<TagId>,
27 /// Field projections (`ProjectionElem::Field` indices) from the parent's
28 /// referent down to this pointer (e.g. `self.node.ptr` → `[node, ptr]`).
29 /// Empty for whole-local pointers. Used by [`AliasTree::resolve_to_root`] to
30 /// reconstruct the nested-field origin.
31 pub fields: Vec<usize>,
32 pub local: Local,
33}
34
35/// The per-function alias derivation forest.
36#[derive(Clone, Debug)]
37pub(crate) struct AliasTree {
38 pub nodes: Vec<AliasNode>,
39 /// The tag that `local` currently names (its most recent binding).
40 tag_of_local: FxHashMap<Local, TagId>,
41}
42
43impl AliasTree {
44 /// Build the derivation forest for `def_id` by scanning its MIR.
45 ///
46 /// Parameters and owned results are roots; every `target =
47 /// <ref/cast/raw/copy> source` statement adds an edge from `source`'s tag to
48 /// a new (or shared) tag for `target`. `StorageDead`/moves are *not* applied
49 /// here — the forest is a static, block-order approximation used only for
50 /// field-path resolution.
51 pub(crate) fn build<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self {
52 let body = tcx.optimized_mir(def_id);
53 let mut tree = AliasTree {
54 nodes: Vec::new(),
55 tag_of_local: FxHashMap::default(),
56 };
57
58 for local_index in 1..=body.arg_count {
59 let local = Local::from_usize(local_index);
60 let ty = body.local_decls[local].ty;
61 if classify(ty).is_some() {
62 tree.add(None, Vec::new(), local);
63 }
64 }
65
66 for block in body.basic_blocks.iter() {
67 for statement in &block.statements {
68 let StatementKind::Assign(assign) = &statement.kind else {
69 continue;
70 };
71 let (target, rvalue) = assign.as_ref();
72 match rvalue {
73 // Copy/move of a *whole* pointer is the same tag, not a new
74 // node. A copy through a field projection (`_2 = (*self).next`)
75 // is a *derivation* (reads the raw field), so it falls through
76 // to the derivation arm below.
77 Rvalue::Use(Operand::Copy(place), ..)
78 | Rvalue::Use(Operand::Move(place), ..)
79 | Rvalue::CopyForDeref(place)
80 if field_projection(place).is_empty() =>
81 {
82 if let Some(tag) = tree.tag_of_local.get(&place.local).copied() {
83 tree.tag_of_local.insert(target.local, tag);
84 }
85 }
86 // True derivation: reborrow (`&mut x`), raw-pointer creation
87 // (`addr_of!(x)`), cast (`&mut → *mut`, `*mut → *const`) and
88 // field read (`(*self).next`).
89 _ => {
90 if let Some(place) = crate::helpers::mir_utils::rvalue_source_place(rvalue)
91 && let Some(parent) = tree.tag_of_local.get(&place.local).copied()
92 {
93 let ty = body.local_decls[target.local].ty;
94 if classify(ty).is_some() {
95 tree.add(Some(parent), field_projection(place), target.local);
96 }
97 }
98 }
99 }
100 }
101
102 // Call destinations: `p = v.as_mut_ptr()` / `into_raw` /
103 // `from_raw_parts` — the returned pointer derives from the first
104 // argument (the receiver/pointer). An *owned* result (`Box::new`,
105 // `Vec::new`, `Box::from_raw`) is a fresh owner, so it becomes a root
106 // regardless of whether its first argument is a place.
107 if let rustc_middle::mir::TerminatorKind::Call {
108 args, destination, ..
109 } = &block.terminator().kind
110 {
111 let dest_local = destination.local;
112 let dest_ty = body.local_decls[dest_local].ty;
113 if let Some(is_owned) = classify(dest_ty) {
114 if is_owned {
115 tree.add(None, Vec::new(), dest_local);
116 } else if let Some(first_arg) = args.first()
117 && let Some(place) = first_arg.node.place()
118 && let Some(parent) = tree.tag_of_local.get(&place.local).copied()
119 {
120 tree.add(Some(parent), field_projection(&place), dest_local);
121 }
122 }
123 }
124 }
125
126 tree
127 }
128
129 fn add(&mut self, parent: Option<TagId>, fields: Vec<usize>, local: Local) -> TagId {
130 let tag = self.nodes.len();
131 self.nodes.push(AliasNode {
132 parent,
133 fields,
134 local,
135 });
136 self.tag_of_local.insert(local, tag);
137 tag
138 }
139
140 /// The tag bound to `local`, if any.
141 pub(crate) fn tag_of(&self, local: Local) -> Option<TagId> {
142 self.tag_of_local.get(&local).copied()
143 }
144
145 /// Walk `tag`'s parent edges to the root, concatenating the `fields` of each
146 /// hop. Returns the root local and the full field path (`self.node.ptr` →
147 /// `(self, [node, ptr])`).
148 fn resolve_to_root(&self, tag: TagId) -> (Local, Vec<usize>) {
149 let mut cur = tag;
150 let mut fields: Vec<usize> = Vec::new();
151 let mut guard = 0;
152 loop {
153 let node = &self.nodes[cur];
154 let mut combined = node.fields.clone();
155 combined.extend(fields.iter().copied());
156 fields = combined;
157 match node.parent {
158 Some(parent) => cur = parent,
159 None => return (node.local, fields),
160 }
161 guard += 1;
162 if guard > self.nodes.len() {
163 return (self.nodes[cur].local, fields);
164 }
165 }
166 }
167
168 /// Resolve a local to its root `(root_local, field_path)` via the tree. An
169 /// unmapped local resolves to itself with an empty field path.
170 pub(crate) fn resolve_local_to_root(&self, local: Local) -> (usize, Vec<usize>) {
171 match self.tag_of(local) {
172 Some(tag) => {
173 let (root, fields) = self.resolve_to_root(tag);
174 (root.as_usize(), fields)
175 }
176 None => (local.as_usize(), Vec::new()),
177 }
178 }
179}
180
181/// The `Field` projection indices of `place`, in order (`Deref`/index/etc. are
182/// skipped), matching `PlaceKey::fields`.
183fn field_projection(place: &rustc_middle::mir::Place<'_>) -> Vec<usize> {
184 place
185 .projection
186 .iter()
187 .filter_map(|p| match p {
188 rustc_middle::mir::ProjectionElem::Field(idx, _) => Some(idx.as_usize()),
189 _ => None,
190 })
191 .collect()
192}
193
194/// Whether `ty` is pointer-bearing (`Ref`/`RawPtr`/`Adt`), returning whether it
195/// is an *owned* container (`Box`/`Vec`/… — a forest root) when it is.
196fn classify(ty: Ty<'_>) -> Option<bool> {
197 match ty.kind() {
198 TyKind::Ref(_, _, _) | TyKind::RawPtr(_, _) => Some(false),
199 TyKind::Adt(_, _) => Some(true),
200 _ => None,
201 }
202}