Skip to main content

rapx/analysis/points_to/
graph.rs

1use std::collections::VecDeque;
2
3use crate::compat::{FxHashMap, FxHashSet};
4use crate::helpers::def_use::{PlaceBaseKey, PlaceKey};
5
6use super::slot::{AbstractLoc, Slot};
7
8use crate::analysis::alias::default::types::ValueKind;
9
10pub const MAX_VALUES_PER_PATH: usize = 1000;
11
12/// Unified points-to and value-flow graph.
13///
14/// Maintains two directed relationship types between slots:
15///
16/// * **points_to**: the slot holds a pointer/reference *into* another slot.
17///   Created by `&_x`, `&raw _x`, etc.
18/// * **value_flow**: the slot's *value* is a copy of another slot's value.
19///   Created by `_a = _b` (Copy/Move), `_a = _b as *const T` (Cast), etc.
20///
21/// Alias queries (`may_alias`) combine:
22/// * **Alias partition**: value-equivalence through assignments (union-find)
23/// * **Points-to intersection**: pointer-level aliasing through references
24#[derive(Clone, Debug)]
25pub struct PtsGraph {
26    points_to: Vec<FxHashSet<AbstractLoc>>,
27    value_flow: Vec<FxHashSet<usize>>,
28    slots: Vec<Slot>,
29    slot_index: FxHashMap<Slot, usize>,
30    may_drop: Vec<bool>,
31    need_drop: Vec<bool>,
32    /// Type classification per slot (RawPtr, Ref, Adt, etc.).
33    slot_kind: Vec<ValueKind>,
34
35    /// Alias partition: which slots are value-equivalent (union-find).
36    /// `alias_parent[i]` is the representative of i's partition,
37    /// or `i` itself if i is the root. `None` means uninitialized (singleton).
38    alias_parent: Vec<usize>,
39}
40
41impl PtsGraph {
42    pub fn new() -> Self {
43        PtsGraph {
44            points_to: Vec::new(),
45            value_flow: Vec::new(),
46            slots: Vec::new(),
47            slot_index: FxHashMap::default(),
48            may_drop: Vec::new(),
49            need_drop: Vec::new(),
50            slot_kind: Vec::new(),
51            alias_parent: Vec::new(),
52        }
53    }
54
55    pub fn slot_count(&self) -> usize {
56        self.slots.len()
57    }
58
59    pub fn get_slot(&self, idx: usize) -> Option<&Slot> {
60        self.slots.get(idx)
61    }
62
63    pub fn get_slot_idx(&self, slot: &Slot) -> Option<usize> {
64        self.slot_index.get(slot).copied()
65    }
66
67    pub fn may_drop(&self, idx: usize) -> bool {
68        self.may_drop.get(idx).copied().unwrap_or(false)
69    }
70
71    pub fn need_drop(&self, idx: usize) -> bool {
72        self.need_drop.get(idx).copied().unwrap_or(false)
73    }
74
75    // ── Slot registration ──────────────────────────────────────────
76
77    pub fn ensure_slot(
78        &mut self,
79        slot: Slot,
80        may_drop: bool,
81        need_drop: bool,
82    ) -> usize {
83        if let Some(&idx) = self.slot_index.get(&slot) {
84            return idx;
85        }
86        if self.slots.len() >= MAX_VALUES_PER_PATH {
87            return 0;
88        }
89        let idx = self.slots.len();
90        self.slots.push(slot.clone());
91        self.slot_index.insert(slot, idx);
92        self.points_to.push(FxHashSet::default());
93        self.value_flow.push(FxHashSet::default());
94        self.may_drop.push(may_drop);
95        self.need_drop.push(need_drop);
96        self.slot_kind.push(ValueKind::Adt);
97        self.alias_parent.push(idx);  // singleton: points to itself
98        idx
99    }
100
101    pub fn set_slot_kind(&mut self, idx: usize, kind: ValueKind) {
102        if idx < self.slot_kind.len() {
103            self.slot_kind[idx] = kind;
104        }
105    }
106
107    pub fn slot_kind(&self, idx: usize) -> ValueKind {
108        self.slot_kind.get(idx).copied().unwrap_or(ValueKind::Adt)
109    }
110
111    pub fn slot_is_ptr(&self, idx: usize) -> bool {
112        matches!(self.slot_kind(idx), ValueKind::RawPtr | ValueKind::Ref)
113    }
114
115    pub fn slot_is_ref_count(&self, idx: usize) -> bool {
116        matches!(self.slot_kind(idx), ValueKind::SpecialPtr)
117    }
118
119    // ── Value-flow updates ─────────────────────────────────────────
120
121    /// Return the direct pointee targets for a slot (non-transitive).
122    pub fn direct_pointees(&self, idx: usize) -> impl Iterator<Item = &AbstractLoc> {
123        self.points_to[idx].iter()
124    }
125
126    /// Record that `dest` points to `target`.
127    /// Strong update: clears old points-to info for `dest`.
128    pub fn assign_pointee(&mut self, dest_idx: usize, target: AbstractLoc) {
129        self.points_to[dest_idx].clear();
130        self.points_to[dest_idx].insert(target);
131    }
132
133    /// Record that `dest` has the same VALUE as `src` (Copy/Move/Cast).
134    /// This is a strong update:
135    /// - Remove `dest` from its old alias partition (other members stay)
136    /// - Put `dest` into `src`'s alias partition
137    /// - Also propagate to field slots.
138    pub fn assign_value(&mut self, dest_idx: usize, src_idx: usize) {
139        self.value_flow[dest_idx].clear();
140        self.value_flow[dest_idx].insert(src_idx);
141
142        // ── Alias partition: strong update ──
143        self.alias_move_to_partition(dest_idx, src_idx);
144
145        // ── Field-level propagation ──
146        if dest_idx < self.slots.len() && src_idx < self.slots.len() {
147            let dest_slot = self.slots[dest_idx].clone();
148            let src_slot = self.slots[src_idx].clone();
149
150            // Propagate to sub-fields: for every slot that extends dest
151            // (same local, additional field projections), find the
152            // corresponding slot that extends src and connect them.
153            let dest_prefix = &dest_slot.fields;
154            let mut field_pairs: Vec<(usize, usize)> = Vec::new();
155            for (cand, cand_s) in self.slots.iter().enumerate() {
156                if cand_s.local != dest_slot.local {
157                    continue;
158                }
159                if cand_s.fields.len() <= dest_prefix.len() {
160                    continue;
161                }
162                if cand_s.fields[..dest_prefix.len()] != *dest_prefix {
163                    continue;
164                }
165                // cand_s is a sub-field of dest (e.g., dest=_0.0, cand=_0.0.0)
166                let suffix = &cand_s.fields[dest_prefix.len()..];
167                let mut src_sub_slot = Slot::new(src_slot.local);
168                src_sub_slot.fields = src_slot.fields.clone();
169                src_sub_slot.fields.extend_from_slice(suffix);
170                if let Some(&src_sub_idx) = self.slot_index.get(&src_sub_slot) {
171                    field_pairs.push((cand, src_sub_idx));
172                }
173            }
174            for (dest_cand, src_field_idx) in field_pairs {
175                self.value_flow[dest_cand].clear();
176                self.value_flow[dest_cand].insert(src_field_idx);
177                self.alias_move_to_partition(dest_cand, src_field_idx);
178            }
179        }
180    }
181
182    /// Merge equivalence: the two slots may hold the same pointer.
183    /// Both inherit the union of each other's points-to set.
184    /// This is used for inter-procedural aliasing and branch join points.
185    /// Also propagates to father slots so SafeDrop can detect aliasing
186    /// through the base local (e.g. `_v.0` alias `ptr` → `_v` alias `s`).
187    pub fn merge_equivalence(&mut self, a_idx: usize, b_idx: usize) {
188        if a_idx == b_idx {
189            return;
190        }
191        // Merge points-to sets
192        let a_pts: Vec<_> = self.points_to[a_idx].iter().cloned().collect();
193        for loc in a_pts {
194            self.points_to[b_idx].insert(loc);
195        }
196        let b_pts: Vec<_> = self.points_to[b_idx].iter().cloned().collect();
197        for loc in b_pts {
198            self.points_to[a_idx].insert(loc);
199        }
200
201        // Merge alias partitions
202        self.alias_union(a_idx, b_idx);
203
204        // Propagate one level upward so SafeDrop's value-level queries
205        // can find field-level aliases (e.g. _b2.0 aliases p → _b2 aliases p).
206        self.propagate_to_father(a_idx, b_idx);
207    }
208
209    fn propagate_to_father(&mut self, a_idx: usize, b_idx: usize) {
210        let fa = self.father_of(a_idx);
211        let fb = self.father_of(b_idx);
212        let ra = fa.unwrap_or(a_idx);
213        let rb = fb.unwrap_or(b_idx);
214        if self.alias_find(ra) != self.alias_find(rb) {
215            self.alias_union(ra, rb);
216        }
217    }
218
219    fn father_of(&self, idx: usize) -> Option<usize> {
220        let slot = &self.slots[idx];
221        if slot.fields.is_empty() {
222            return None;
223        }
224        let father_slot = Slot {
225            local: slot.local,
226            fields: slot.fields[..slot.fields.len() - 1].to_vec(),
227        };
228        self.slot_index.get(&father_slot).copied()
229    }
230
231    /// Conservative merge for unknown-function calls: all pointer-typed
232    /// args may alias each other and the return value.
233    pub fn conservative_call_merge(&mut self, arg_slots: &[usize]) {
234        let mut pointer_args: Vec<usize> = Vec::new();
235        for &idx in arg_slots {
236            if !self.points_to[idx].is_empty() {
237                pointer_args.push(idx);
238            } else if self.may_drop(idx) {
239                pointer_args.push(idx);
240            }
241        }
242        for i in 0..pointer_args.len() {
243            for j in (i + 1)..pointer_args.len() {
244                self.merge_equivalence(pointer_args[i], pointer_args[j]);
245            }
246        }
247    }
248
249    // ── Queries ────────────────────────────────────────────────────
250
251    /// Transitive points-to set: follow value_flow + points_to until
252    /// fixpoint.  Returns all AbstractLoc reachable from `start_idx`.
253    pub fn pts(&self, start_idx: usize) -> FxHashSet<AbstractLoc> {
254        let mut result = FxHashSet::default();
255        let mut visited = FxHashSet::default();
256        let mut queue = VecDeque::new();
257        queue.push_back(Start::Pointee(start_idx));
258        visited.insert(Visit::Pointee(start_idx));
259
260        while let Some(current) = queue.pop_front() {
261            match current {
262                Start::Pointee(idx) => {
263                    for loc in &self.points_to[idx] {
264                        if !matches!(loc, AbstractLoc::Null) {
265                            result.insert(loc.clone());
266                        }
267                    }
268                    for &src in &self.value_flow[idx] {
269                        if visited.insert(Visit::Pointee(src)) {
270                            queue.push_back(Start::Pointee(src));
271                        }
272                    }
273                }
274            }
275        }
276        result
277    }
278
279    /// May-alias check: do the pointed-to memories of `a` and `b` overlap?
280    /// Combines:
281    /// 1. Alias partition check (value-equivalence via assignments)
282    /// 2. Points-to intersection (pointer-level aliasing)
283    pub fn may_alias(&self, a_idx: usize, b_idx: usize) -> bool {
284        // Check alias partition (value-equivalence)
285        if self.alias_find(a_idx) == self.alias_find(b_idx) {
286            return true;
287        }
288        // Check points-to intersection
289        let pta = self.pts(a_idx);
290        if pta.is_empty() {
291            return false;
292        }
293        let ptb = self.pts(b_idx);
294        pta.intersection(&ptb).next().is_some()
295    }
296
297    // ── Inter-procedural ───────────────────────────────────────────
298
299    /// Apply callee's FnAliasPairs to the graph at a call site.
300    /// `callee_arg_slots`: [ret_dest_idx, arg₀_idx, arg₁_idx, ...]
301    pub fn apply_callee_summary(
302        &mut self,
303        callee_pairs: &crate::analysis::alias::FnAliasPairs,
304        callee_arg_slots: &[usize],
305    ) {
306        for alias in callee_pairs.aliases() {
307            let left_idx = alias.left_local();
308            let right_idx = alias.right_local();
309
310            if left_idx >= callee_arg_slots.len() || right_idx >= callee_arg_slots.len() {
311                continue;
312            }
313
314            let mut lv = callee_arg_slots[left_idx];
315            let mut rv = callee_arg_slots[right_idx];
316
317            for &field_idx in alias.lhs_fields() {
318                let field_slot = self.slots[lv].project(field_idx);
319                if let Some(idx) = self.slot_index.get(&field_slot) {
320                    lv = *idx;
321                } else {
322                    let idx = self.ensure_slot(
323                        field_slot,
324                        self.may_drop[lv],
325                        self.need_drop[lv],
326                    );
327                    lv = idx;
328                }
329            }
330            for &field_idx in alias.rhs_fields() {
331                let field_slot = self.slots[rv].project(field_idx);
332                if let Some(idx) = self.slot_index.get(&field_slot) {
333                    rv = *idx;
334                } else {
335                    let idx = self.ensure_slot(
336                        field_slot,
337                        self.may_drop[rv],
338                        self.need_drop[rv],
339                    );
340                    rv = idx;
341                }
342            }
343
344            if self.may_drop(lv) && self.may_drop(rv) {
345                self.merge_equivalence(lv, rv);
346            }
347        }
348    }
349
350    // ── FnAliasPairs extraction ────────────────────────────────────
351
352    /// Compute field-sensitive alias pairs among args (1..=arg_count) + return
353    /// value (0).  For each pair, checks `may_alias()` and if true, emits an
354    /// `AliasPair` with the truncated single-level field paths.
355    pub fn fn_alias_pairs(
356        &self,
357        arg_count: usize,
358    ) -> crate::analysis::alias::FnAliasPairs {
359        let mut pairs = crate::analysis::alias::FnAliasPairs::new(arg_count);
360
361        let local_ids: Vec<usize> = (0..=arg_count).collect();
362
363        // Map each local -> its base slot index (the slot with empty fields).
364        let mut local_to_base_slot: FxHashMap<usize, usize> = FxHashMap::default();
365        for (slot_idx, s) in self.slots.iter().enumerate() {
366            if s.fields.is_empty() && s.local <= arg_count {
367                local_to_base_slot.entry(s.local).or_insert(slot_idx);
368            }
369        }
370
371        // Base-level alias check.
372        for i in 0..local_ids.len() {
373            for j in (i + 1)..local_ids.len() {
374                let li = local_ids[i];
375                let lj = local_ids[j];
376                let Some(&slot_i) = local_to_base_slot.get(&li) else { continue; };
377                let Some(&slot_j) = local_to_base_slot.get(&lj) else { continue; };
378                if self.may_alias(slot_i, slot_j) {
379                    let mut pair =
380                        crate::analysis::alias::AliasPair::new(li, lj);
381                    pair.lhs_fields = vec![];
382                    pair.rhs_fields = vec![];
383                    pairs.add_alias(pair);
384                }
385            }
386        }
387
388        // Field-level alias checks.
389        let field_slots: Vec<(usize, Vec<usize>)> = self
390            .slots
391            .iter()
392            .enumerate()
393            .filter_map(|(idx, slot)| {
394                if !slot.fields.is_empty() && slot.local <= arg_count {
395                    Some((idx, slot.fields.clone()))
396                } else {
397                    None
398                }
399            })
400            .collect();
401
402        for (idx_a, fields_a) in &field_slots {
403            let slot_a = &self.slots[*idx_a];
404            // Field ↔ Field
405            for (idx_b, fields_b) in &field_slots {
406                if idx_a == idx_b { continue; }
407                let slot_b = &self.slots[*idx_b];
408                if slot_a.local == slot_b.local { continue; }
409                if self.may_alias(*idx_a, *idx_b) {
410                    let mut pair = crate::analysis::alias::AliasPair::new(slot_a.local, slot_b.local);
411                    pair.lhs_fields = fields_a.clone();
412                    pair.rhs_fields = fields_b.clone();
413                    pairs.add_alias(pair);
414                }
415            }
416            // Field ↔ Base (cross-level)
417            for &base_local in &local_ids {
418                if slot_a.local == base_local { continue; }
419                let Some(&base_slot_idx) = local_to_base_slot.get(&base_local) else { continue; };
420                if self.may_alias(*idx_a, base_slot_idx) {
421                    let mut pair = crate::analysis::alias::AliasPair::new(slot_a.local, base_local);
422                    pair.lhs_fields = fields_a.clone();
423                    pair.rhs_fields = vec![];
424                    pairs.add_alias(pair);
425                }
426            }
427        }
428
429        // Compress field paths: truncate each side to its first element,
430        // matching the old MoP alias analysis behavior.
431        pairs.compress_fields();
432
433        pairs.sort_alias_index();
434        pairs
435    }
436
437    // ── Alias partition (Union-Find for value-equivalence) ──────────
438
439    /// Find the representative of `idx`'s alias partition.
440    fn alias_find(&self, idx: usize) -> usize {
441        if idx >= self.alias_parent.len() {
442            return idx;
443        }
444        let mut cur = idx;
445        while self.alias_parent[cur] != cur {
446            cur = self.alias_parent[cur];
447        }
448        cur
449    }
450
451    /// Union two alias partitions.
452    fn alias_union(&mut self, a: usize, b: usize) {
453        let ra = self.alias_find(a);
454        let rb = self.alias_find(b);
455        if ra != rb {
456            self.alias_parent[ra] = rb;
457        }
458    }
459
460    /// Move `slot_idx` from its current partition to `target_idx`'s partition.
461    /// This implements the strong-update semantics of MoP's `assign_alias`:
462    /// the moved slot leaves its old partition behind.
463    fn alias_move_to_partition(&mut self, slot_idx: usize, target_idx: usize) {
464        if slot_idx >= self.alias_parent.len() {
465            return;
466        }
467        // Point slot_idx directly to target's root
468        let target_root = self.alias_find(target_idx);
469        self.alias_parent[slot_idx] = target_root;
470    }
471
472    /// Strong-update: put all slots in `slot_idx`'s partition into their
473    /// own singleton partitions, breaking all alias-equivalence for the
474    /// entire partition. Used when a call produces a fresh value that
475    /// must not retain any old alias relationships.
476    pub fn reset_partition(&mut self, slot_idx: usize) {
477        if slot_idx >= self.alias_parent.len() {
478            return;
479        }
480        let root = self.alias_find(slot_idx);
481        for i in 0..self.alias_parent.len() {
482            if self.alias_find(i) == root {
483                self.alias_parent[i] = i;
484            }
485        }
486    }
487
488    // ── PlaceKey-oriented adapter methods ──────────────────────────
489
490    /// Record that `pointer` place was derived from `source` place.
491    /// Strong-update semantics: clears old points-to info for the pointer.
492    pub fn insert_place_edge(&mut self, pointer: &PlaceKey, source: &PlaceKey) {
493        let ptr_slot = Self::place_key_to_slot(pointer);
494        let src_slot = Self::place_key_to_slot(source);
495        let ptr_idx = self.ensure_slot(ptr_slot, false, false);
496        self.ensure_slot(src_slot.clone(), false, false);
497        self.assign_pointee(ptr_idx, AbstractLoc::Slot(src_slot));
498    }
499
500    /// Single-step points-to lookup (non-transitive) with overlap semantics.
501    /// When the exact place has no edge, falls back through field-stripping.
502    pub fn get_place_source(&self, place: &PlaceKey) -> Option<PlaceKey> {
503        let mut slot = Self::place_key_to_slot(place);
504        loop {
505            if let Some(idx) = self.slot_index.get(&slot) {
506                if let Some(first_loc) =
507                    self.points_to.get(*idx).and_then(|set| set.iter().next())
508                {
509                    if let AbstractLoc::Slot(target) = first_loc {
510                        return Some(Self::slot_to_place_key(target));
511                    }
512                }
513            }
514            if slot.fields.is_empty() {
515                return None;
516            }
517            slot.fields.pop();
518        }
519    }
520
521    /// Transitive points-to resolution with overlap semantics and loop
522    /// detection.
523    pub fn resolve_place(&self, place: &PlaceKey) -> PlaceKey {
524        let mut cur = place.clone();
525        let mut seen: Vec<PlaceKey> = vec![cur.clone()];
526        loop {
527            let Some(next) = self.get_place_source(&cur) else {
528                break;
529            };
530            if seen.iter().any(|p| p == &next) {
531                break;
532            }
533            seen.push(next.clone());
534            cur = next.clone();
535        }
536        cur
537    }
538
539    /// Return all PlaceKey-based points-to edges.
540    pub fn place_edges(&self) -> Vec<(PlaceKey, PlaceKey)> {
541        let mut edges = Vec::new();
542        for (idx, targets) in self.points_to.iter().enumerate() {
543            let Some(slot) = self.slots.get(idx) else { continue };
544            let pointer = Self::slot_to_place_key(slot);
545            for target in targets {
546                if let AbstractLoc::Slot(target_slot) = target {
547                    let source = Self::slot_to_place_key(target_slot);
548                    edges.push((pointer.clone(), source));
549                }
550            }
551        }
552        edges
553    }
554
555    fn place_key_to_slot(pk: &PlaceKey) -> Slot {
556        let local = pk.local().map(|l| l.as_usize()).unwrap_or(0);
557        Slot { local, fields: pk.fields.clone() }
558    }
559
560    fn slot_to_place_key(slot: &Slot) -> PlaceKey {
561        PlaceKey {
562            base: PlaceBaseKey::Local(slot.local),
563            fields: slot.fields.clone(),
564        }
565    }
566}
567
568impl Default for PtsGraph {
569    fn default() -> Self {
570        Self::new()
571    }
572}
573
574// ── Internal helpers for transitive search ─────────────────────────
575
576#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
577enum Start {
578    Pointee(usize),
579}
580
581#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
582enum Visit {
583    Pointee(usize),
584}