Skip to main content

memscope_rs/analysis/relation_inference/
mod.rs

1//! Relation Inference - Memory relationship graph construction.
2//!
3//! Infers relationships between heap allocations by analyzing pointer patterns,
4//! content similarity, and allocation metadata.
5//!
6//! # Supported Relations
7//!
8//! | Relation | Meaning | Signal |
9//! |----------|---------|--------|
10//! | **Owner** | A holds a pointer into B | Pointer scan + RangeMap |
11//! | **Slice** | A is a sub-region of B | Pointer falls inside B (not at start) |
12//! | **Clone** | A is a copy of B | Same type/size/stack + content match |
13//!
14//! # Architecture
15//!
16//! ```text
17//! ActiveAllocation + MemoryView
18//!        │
19//!        ▼
20//!   UTI Engine (TypeGuess)
21//!        │
22//!        ▼
23//!   RangeMap (address → alloc index)
24//!        │
25//!        ▼
26//!   Relation Engine (Owner / Slice / Clone)
27//!        │
28//!        ▼
29//!   RelationGraph
30//! ```
31
32mod clone_detector;
33mod container_detector;
34mod graph_builder;
35mod pointer_scan;
36mod range_map;
37mod shared_detector;
38mod slice_detector;
39
40pub use clone_detector::{detect_clones, CloneConfig};
41pub use container_detector::{detect_containers, ContainerConfig};
42pub use graph_builder::{GraphBuilderConfig, RelationGraphBuilder};
43pub use pointer_scan::{detect_owner, InferenceRecord};
44pub use range_map::RangeMap;
45pub use shared_detector::detect_shared;
46pub use slice_detector::detect_slice;
47
48/// A relationship between two allocations in the graph.
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub enum Relation {
51    /// A owns heap memory (HeapOwner → heap).
52    Owns,
53    /// A contains B (Container → HeapOwner).
54    Contains,
55    /// A and B share ownership (Arc/Rc).
56    Shares,
57    /// A is a view into a sub-region of B (e.g., &[T] into Vec).
58    Slice,
59    /// A is a copy of B (same type, size, stack, content).
60    Clone,
61    /// A is a later evolution of B (same variable, later allocation, e.g., Vec reallocation).
62    Evolution,
63    /// A is an Arc clone of B (StackOwner-based Arc clone detection).
64    ArcClone,
65    /// A is an Rc clone of B (StackOwner-based Rc clone detection).
66    RcClone,
67    /// A is an immutable borrow of B (&T).
68    ImmutableBorrow,
69    /// A is a mutable borrow of B (&mut T).
70    MutableBorrow,
71}
72
73impl std::fmt::Display for Relation {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            Relation::Owns => write!(f, "Owns"),
77            Relation::Contains => write!(f, "Contains"),
78            Relation::Shares => write!(f, "Shares"),
79            Relation::Slice => write!(f, "Slice"),
80            Relation::Clone => write!(f, "Clone"),
81            Relation::Evolution => write!(f, "Evolution"),
82            Relation::ArcClone => write!(f, "ArcClone"),
83            Relation::RcClone => write!(f, "RcClone"),
84            Relation::ImmutableBorrow => write!(f, "ImmutableBorrow"),
85            Relation::MutableBorrow => write!(f, "MutableBorrow"),
86        }
87    }
88}
89
90/// An edge in the relation graph.
91#[derive(Debug, Clone)]
92pub struct RelationEdge {
93    /// Source allocation ID (index into the allocations list).
94    pub from: usize,
95    /// Target allocation ID.
96    pub to: usize,
97    /// The type of relationship.
98    pub relation: Relation,
99}
100
101/// A relation graph connecting allocations.
102#[derive(Debug, Default, Clone)]
103pub struct RelationGraph {
104    /// All edges in the graph.
105    pub edges: Vec<RelationEdge>,
106}
107
108impl RelationGraph {
109    /// Create a new empty relation graph.
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Build relation graph from MemoryView.
115    ///
116    /// Creates a relation graph from the allocations in a MemoryView.
117    /// This is a convenience method for the unified analyzer API.
118    pub fn from_view(view: &crate::view::MemoryView) -> Self {
119        use crate::snapshot::types::ActiveAllocation;
120
121        let allocations: Vec<ActiveAllocation> =
122            view.allocations().iter().map(|a| (*a).clone()).collect();
123
124        RelationGraphBuilder::build(&allocations, None)
125    }
126
127    /// Add a single edge to the graph.
128    ///
129    /// Does not add duplicate edges (same from/to/relation).
130    pub fn add_edge(&mut self, from: usize, to: usize, relation: Relation) {
131        // Skip self-edges.
132        if from == to {
133            return;
134        }
135        // Skip duplicates.
136        let exists = self
137            .edges
138            .iter()
139            .any(|e| e.from == from && e.to == to && e.relation == relation);
140        if !exists {
141            self.edges.push(RelationEdge { from, to, relation });
142        }
143    }
144
145    /// Add multiple edges at once.
146    pub fn add_edges(&mut self, edges: Vec<RelationEdge>) {
147        for edge in edges {
148            self.add_edge(edge.from, edge.to, edge.relation);
149        }
150    }
151
152    /// Get all inbound edges to a given node.
153    pub fn get_inbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
154        self.edges.iter().filter(|e| e.to == node).collect()
155    }
156
157    /// Get all outbound edges from a given node.
158    pub fn get_outbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
159        self.edges.iter().filter(|e| e.from == node).collect()
160    }
161
162    /// Get the number of edges in the graph.
163    pub fn edge_count(&self) -> usize {
164        self.edges.len()
165    }
166
167    /// Get all unique node IDs referenced in the graph.
168    pub fn all_nodes(&self) -> Vec<usize> {
169        let mut nodes: Vec<usize> = self.edges.iter().flat_map(|e| [e.from, e.to]).collect();
170        nodes.sort();
171        nodes.dedup();
172        nodes
173    }
174
175    /// Detect cycles in the relation graph using DFS.
176    ///
177    /// Returns a list of cycle edges as `(from, to, relation)` tuples.
178    /// Empty result means no cycles detected.
179    pub fn detect_cycles(&self) -> Vec<(usize, usize, Relation)> {
180        if self.edges.is_empty() {
181            return Vec::new();
182        }
183
184        use crate::analysis::relationship_cycle_detector::detect_cycles_direct;
185
186        let relationships: Vec<(usize, usize)> =
187            self.edges.iter().map(|e| (e.from, e.to)).collect();
188
189        let cycle_indices = detect_cycles_direct(&relationships);
190
191        cycle_indices
192            .into_iter()
193            .filter_map(|(from_idx, to_idx)| {
194                self.edges.iter().find_map(|e| {
195                    if e.from == from_idx && e.to == to_idx {
196                        Some((e.from, e.to, e.relation.clone()))
197                    } else {
198                        None
199                    }
200                })
201            })
202            .collect()
203    }
204}
205
206mod tests {
207    #[allow(unused_imports)]
208    use crate::{Relation, RelationEdge, RelationGraph};
209
210    #[test]
211    fn test_graph_add_edge() {
212        let mut graph = RelationGraph::new();
213
214        graph.add_edge(0, 1, Relation::Owns);
215
216        assert_eq!(graph.edge_count(), 1);
217
218        assert_eq!(graph.edges[0].from, 0);
219
220        assert_eq!(graph.edges[0].to, 1);
221
222        assert_eq!(graph.edges[0].relation, Relation::Owns);
223    }
224
225    #[test]
226
227    fn test_graph_no_self_edges() {
228        let mut graph = RelationGraph::new();
229
230        graph.add_edge(0, 0, Relation::Owns);
231
232        assert_eq!(graph.edge_count(), 0);
233    }
234
235    #[test]
236
237    fn test_graph_no_duplicate_edges() {
238        let mut graph = RelationGraph::new();
239
240        graph.add_edge(0, 1, Relation::Owns);
241
242        graph.add_edge(0, 1, Relation::Owns);
243
244        assert_eq!(graph.edge_count(), 1);
245    }
246
247    #[test]
248
249    fn test_graph_different_relations_allowed() {
250        let mut graph = RelationGraph::new();
251
252        graph.add_edge(0, 1, Relation::Owns);
253
254        graph.add_edge(0, 1, Relation::Clone);
255
256        assert_eq!(graph.edge_count(), 2);
257    }
258
259    #[test]
260
261    fn test_graph_inbound_outbound_edges() {
262        let mut graph = RelationGraph::new();
263
264        graph.add_edge(0, 2, Relation::Owns);
265
266        graph.add_edge(1, 2, Relation::Owns);
267
268        graph.add_edge(2, 3, Relation::Slice);
269
270        let inbound_to_2 = graph.get_inbound_edges(2);
271
272        assert_eq!(inbound_to_2.len(), 2);
273
274        let outbound_from_2 = graph.get_outbound_edges(2);
275
276        assert_eq!(outbound_from_2.len(), 1);
277    }
278
279    #[test]
280
281    fn test_graph_all_nodes() {
282        let mut graph = RelationGraph::new();
283
284        graph.add_edge(3, 1, Relation::Owns);
285
286        graph.add_edge(1, 2, Relation::Slice);
287
288        let nodes = graph.all_nodes();
289
290        assert_eq!(nodes, vec![1, 2, 3]);
291    }
292
293    #[test]
294
295    fn test_graph_add_edges_batch() {
296        let mut graph = RelationGraph::new();
297
298        graph.add_edges(vec![
299            RelationEdge {
300                from: 0,
301
302                to: 1,
303
304                relation: Relation::Owns,
305            },
306            RelationEdge {
307                from: 1,
308
309                to: 2,
310
311                relation: Relation::Slice,
312            },
313        ]);
314
315        assert_eq!(graph.edge_count(), 2);
316    }
317
318    #[test]
319
320    fn test_graph_detect_cycles_none() {
321        // Linear chain: 0 → 1 → 2 → no cycles.
322
323        let mut graph = RelationGraph::new();
324
325        graph.add_edge(0, 1, Relation::Owns);
326
327        graph.add_edge(1, 2, Relation::Slice);
328
329        let cycles = graph.detect_cycles();
330
331        assert!(cycles.is_empty());
332    }
333
334    #[test]
335
336    fn test_graph_detect_cycles_simple() {
337        // Cycle: 0 → 1 → 0
338
339        let mut graph = RelationGraph::new();
340
341        graph.add_edge(0, 1, Relation::Owns);
342
343        graph.add_edge(1, 0, Relation::Owns);
344
345        let cycles = graph.detect_cycles();
346
347        // Should detect at least one cycle edge.
348
349        assert!(!cycles.is_empty());
350
351        // Both edges should be part of the cycle.
352
353        let edge_pairs: Vec<_> = cycles.iter().map(|(f, t, _)| (*f, *t)).collect();
354
355        assert!(edge_pairs.contains(&(0, 1)) || edge_pairs.contains(&(1, 0)));
356    }
357
358    #[test]
359
360    fn test_graph_detect_cycles_longer() {
361        // Cycle: 0 → 1 → 2 → 0
362
363        let mut graph = RelationGraph::new();
364
365        graph.add_edge(0, 1, Relation::Owns);
366
367        graph.add_edge(1, 2, Relation::Slice);
368
369        graph.add_edge(2, 0, Relation::Clone);
370
371        let cycles = graph.detect_cycles();
372
373        assert!(!cycles.is_empty());
374
375        // Verify the cycle includes the back-edge (2 → 0).
376
377        let has_back_edge = cycles.iter().any(|(f, t, _)| *f == 2 && *t == 0);
378
379        assert!(has_back_edge, "cycle should contain back-edge (2, 0)");
380    }
381
382    #[test]
383
384    fn test_graph_detect_cycles_empty() {
385        let graph = RelationGraph::new();
386
387        let cycles = graph.detect_cycles();
388
389        assert!(cycles.is_empty());
390    }
391
392    #[test]
393
394    fn test_graph_detect_cycles_self_loop_blocked() {
395        // Self-edges are blocked by add_edge, so no cycle possible.
396
397        let mut graph = RelationGraph::new();
398
399        graph.add_edge(0, 0, Relation::Owns);
400
401        assert_eq!(graph.edge_count(), 0);
402
403        let cycles = graph.detect_cycles();
404
405        assert!(cycles.is_empty());
406    }
407
408    #[test]
409
410    fn test_graph_detect_cycles_diamond() {
411        // Diamond: 0 → 1, 0 → 2, 1 → 3, 2 → 3, 3 → 0
412
413        // Cycles: 0→1→3→0 and 0→2→3→0
414
415        let mut graph = RelationGraph::new();
416
417        graph.add_edge(0, 1, Relation::Owns);
418
419        graph.add_edge(0, 2, Relation::Owns);
420
421        graph.add_edge(1, 3, Relation::Slice);
422
423        graph.add_edge(2, 3, Relation::Slice);
424
425        graph.add_edge(3, 0, Relation::Clone);
426
427        let cycles = graph.detect_cycles();
428
429        assert!(!cycles.is_empty());
430    }
431}