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    /// Uses only snapshot metadata: historical addresses do not grant permission
118    /// to read current process memory. Live scanning is a separate builder operation.
119    pub fn from_view(view: &crate::view::MemoryView) -> Self {
120        use crate::snapshot::types::ActiveAllocation;
121
122        let allocations: Vec<ActiveAllocation> =
123            view.allocations().iter().map(|a| (*a).clone()).collect();
124
125        RelationGraphBuilder::from_observations(&allocations)
126    }
127
128    /// Add a single edge to the graph.
129    ///
130    /// Does not add duplicate edges (same from/to/relation).
131    pub fn add_edge(&mut self, from: usize, to: usize, relation: Relation) {
132        // Skip self-edges.
133        if from == to {
134            return;
135        }
136        // Skip duplicates.
137        let exists = self
138            .edges
139            .iter()
140            .any(|e| e.from == from && e.to == to && e.relation == relation);
141        if !exists {
142            self.edges.push(RelationEdge { from, to, relation });
143        }
144    }
145
146    /// Add multiple edges at once.
147    pub fn add_edges(&mut self, edges: Vec<RelationEdge>) {
148        for edge in edges {
149            self.add_edge(edge.from, edge.to, edge.relation);
150        }
151    }
152
153    /// Get all inbound edges to a given node.
154    pub fn get_inbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
155        self.edges.iter().filter(|e| e.to == node).collect()
156    }
157
158    /// Get all outbound edges from a given node.
159    pub fn get_outbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
160        self.edges.iter().filter(|e| e.from == node).collect()
161    }
162
163    /// Get the number of edges in the graph.
164    pub fn edge_count(&self) -> usize {
165        self.edges.len()
166    }
167
168    /// Get all unique node IDs referenced in the graph.
169    pub fn all_nodes(&self) -> Vec<usize> {
170        let mut nodes: Vec<usize> = self.edges.iter().flat_map(|e| [e.from, e.to]).collect();
171        nodes.sort();
172        nodes.dedup();
173        nodes
174    }
175
176    /// Detect cycles in the relation graph using DFS.
177    ///
178    /// Returns a list of cycle edges as `(from, to, relation)` tuples.
179    /// Empty result means no cycles detected.
180    pub fn detect_cycles(&self) -> Vec<(usize, usize, Relation)> {
181        if self.edges.is_empty() {
182            return Vec::new();
183        }
184
185        use crate::analysis::relationship_cycle_detector::detect_cycles_direct;
186
187        let relationships: Vec<(usize, usize)> =
188            self.edges.iter().map(|e| (e.from, e.to)).collect();
189
190        let cycle_indices = detect_cycles_direct(&relationships);
191
192        cycle_indices
193            .into_iter()
194            .filter_map(|(from_idx, to_idx)| {
195                self.edges.iter().find_map(|e| {
196                    if e.from == from_idx && e.to == to_idx {
197                        Some((e.from, e.to, e.relation.clone()))
198                    } else {
199                        None
200                    }
201                })
202            })
203            .collect()
204    }
205}
206
207mod tests {
208    #[allow(unused_imports)]
209    use crate::{Relation, RelationEdge, RelationGraph};
210
211    #[test]
212    fn test_graph_add_edge() {
213        let mut graph = RelationGraph::new();
214
215        graph.add_edge(0, 1, Relation::Owns);
216
217        assert_eq!(graph.edge_count(), 1);
218
219        assert_eq!(graph.edges[0].from, 0);
220
221        assert_eq!(graph.edges[0].to, 1);
222
223        assert_eq!(graph.edges[0].relation, Relation::Owns);
224    }
225
226    #[test]
227
228    fn test_graph_no_self_edges() {
229        let mut graph = RelationGraph::new();
230
231        graph.add_edge(0, 0, Relation::Owns);
232
233        assert_eq!(graph.edge_count(), 0);
234    }
235
236    #[test]
237
238    fn test_graph_no_duplicate_edges() {
239        let mut graph = RelationGraph::new();
240
241        graph.add_edge(0, 1, Relation::Owns);
242
243        graph.add_edge(0, 1, Relation::Owns);
244
245        assert_eq!(graph.edge_count(), 1);
246    }
247
248    #[test]
249
250    fn test_graph_different_relations_allowed() {
251        let mut graph = RelationGraph::new();
252
253        graph.add_edge(0, 1, Relation::Owns);
254
255        graph.add_edge(0, 1, Relation::Clone);
256
257        assert_eq!(graph.edge_count(), 2);
258    }
259
260    #[test]
261
262    fn test_graph_inbound_outbound_edges() {
263        let mut graph = RelationGraph::new();
264
265        graph.add_edge(0, 2, Relation::Owns);
266
267        graph.add_edge(1, 2, Relation::Owns);
268
269        graph.add_edge(2, 3, Relation::Slice);
270
271        let inbound_to_2 = graph.get_inbound_edges(2);
272
273        assert_eq!(inbound_to_2.len(), 2);
274
275        let outbound_from_2 = graph.get_outbound_edges(2);
276
277        assert_eq!(outbound_from_2.len(), 1);
278    }
279
280    #[test]
281
282    fn test_graph_all_nodes() {
283        let mut graph = RelationGraph::new();
284
285        graph.add_edge(3, 1, Relation::Owns);
286
287        graph.add_edge(1, 2, Relation::Slice);
288
289        let nodes = graph.all_nodes();
290
291        assert_eq!(nodes, vec![1, 2, 3]);
292    }
293
294    #[test]
295
296    fn test_graph_add_edges_batch() {
297        let mut graph = RelationGraph::new();
298
299        graph.add_edges(vec![
300            RelationEdge {
301                from: 0,
302
303                to: 1,
304
305                relation: Relation::Owns,
306            },
307            RelationEdge {
308                from: 1,
309
310                to: 2,
311
312                relation: Relation::Slice,
313            },
314        ]);
315
316        assert_eq!(graph.edge_count(), 2);
317    }
318
319    #[test]
320
321    fn test_graph_detect_cycles_none() {
322        // Linear chain: 0 → 1 → 2 → no cycles.
323
324        let mut graph = RelationGraph::new();
325
326        graph.add_edge(0, 1, Relation::Owns);
327
328        graph.add_edge(1, 2, Relation::Slice);
329
330        let cycles = graph.detect_cycles();
331
332        assert!(cycles.is_empty());
333    }
334
335    #[test]
336
337    fn test_graph_detect_cycles_simple() {
338        // Cycle: 0 → 1 → 0
339
340        let mut graph = RelationGraph::new();
341
342        graph.add_edge(0, 1, Relation::Owns);
343
344        graph.add_edge(1, 0, Relation::Owns);
345
346        let cycles = graph.detect_cycles();
347
348        // Should detect at least one cycle edge.
349
350        assert!(!cycles.is_empty());
351
352        // Both edges should be part of the cycle.
353
354        let edge_pairs: Vec<_> = cycles.iter().map(|(f, t, _)| (*f, *t)).collect();
355
356        assert!(edge_pairs.contains(&(0, 1)) || edge_pairs.contains(&(1, 0)));
357    }
358
359    #[test]
360
361    fn test_graph_detect_cycles_longer() {
362        // Cycle: 0 → 1 → 2 → 0
363
364        let mut graph = RelationGraph::new();
365
366        graph.add_edge(0, 1, Relation::Owns);
367
368        graph.add_edge(1, 2, Relation::Slice);
369
370        graph.add_edge(2, 0, Relation::Clone);
371
372        let cycles = graph.detect_cycles();
373
374        assert!(!cycles.is_empty());
375
376        // Verify the cycle includes the back-edge (2 → 0).
377
378        let has_back_edge = cycles.iter().any(|(f, t, _)| *f == 2 && *t == 0);
379
380        assert!(has_back_edge, "cycle should contain back-edge (2, 0)");
381    }
382
383    #[test]
384
385    fn test_graph_detect_cycles_empty() {
386        let graph = RelationGraph::new();
387
388        let cycles = graph.detect_cycles();
389
390        assert!(cycles.is_empty());
391    }
392
393    #[test]
394
395    fn test_graph_detect_cycles_self_loop_blocked() {
396        // Self-edges are blocked by add_edge, so no cycle possible.
397
398        let mut graph = RelationGraph::new();
399
400        graph.add_edge(0, 0, Relation::Owns);
401
402        assert_eq!(graph.edge_count(), 0);
403
404        let cycles = graph.detect_cycles();
405
406        assert!(cycles.is_empty());
407    }
408
409    #[test]
410
411    fn test_graph_detect_cycles_diamond() {
412        // Diamond: 0 → 1, 0 → 2, 1 → 3, 2 → 3, 3 → 0
413
414        // Cycles: 0→1→3→0 and 0→2→3→0
415
416        let mut graph = RelationGraph::new();
417
418        graph.add_edge(0, 1, Relation::Owns);
419
420        graph.add_edge(0, 2, Relation::Owns);
421
422        graph.add_edge(1, 3, Relation::Slice);
423
424        graph.add_edge(2, 3, Relation::Slice);
425
426        graph.add_edge(3, 0, Relation::Clone);
427
428        let cycles = graph.detect_cycles();
429
430        assert!(!cycles.is_empty());
431    }
432}