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 graph_builder;
34mod pointer_scan;
35mod range_map;
36mod shared_detector;
37mod slice_detector;
38
39pub use clone_detector::{detect_clones, CloneConfig};
40pub use graph_builder::{GraphBuilderConfig, RelationGraphBuilder};
41pub use pointer_scan::{detect_owner, InferenceRecord};
42pub use range_map::RangeMap;
43pub use shared_detector::detect_shared;
44pub use slice_detector::detect_slice;
45
46/// A relationship between two allocations in the graph.
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub enum Relation {
49    /// A owns or points into B (e.g., Vec metadata → heap buffer).
50    Owner,
51    /// A is a view into a sub-region of B (e.g., &[T] into Vec).
52    Slice,
53    /// A is a copy of B (same type, size, stack, content).
54    Clone,
55    /// A and B share ownership of the same Arc/Rc inner data.
56    Shared,
57}
58
59impl std::fmt::Display for Relation {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Relation::Owner => write!(f, "Owner"),
63            Relation::Slice => write!(f, "Slice"),
64            Relation::Clone => write!(f, "Clone"),
65            Relation::Shared => write!(f, "Shared"),
66        }
67    }
68}
69
70/// An edge in the relation graph.
71#[derive(Debug, Clone)]
72pub struct RelationEdge {
73    /// Source allocation ID (index into the allocations list).
74    pub from: usize,
75    /// Target allocation ID.
76    pub to: usize,
77    /// The type of relationship.
78    pub relation: Relation,
79}
80
81/// A relation graph connecting allocations.
82#[derive(Debug, Default, Clone)]
83pub struct RelationGraph {
84    /// All edges in the graph.
85    pub edges: Vec<RelationEdge>,
86}
87
88impl RelationGraph {
89    /// Create a new empty relation graph.
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Add a single edge to the graph.
95    ///
96    /// Does not add duplicate edges (same from/to/relation).
97    pub fn add_edge(&mut self, from: usize, to: usize, relation: Relation) {
98        // Skip self-edges.
99        if from == to {
100            return;
101        }
102        // Skip duplicates.
103        let exists = self
104            .edges
105            .iter()
106            .any(|e| e.from == from && e.to == to && e.relation == relation);
107        if !exists {
108            self.edges.push(RelationEdge { from, to, relation });
109        }
110    }
111
112    /// Add multiple edges at once.
113    pub fn add_edges(&mut self, edges: Vec<RelationEdge>) {
114        for edge in edges {
115            self.add_edge(edge.from, edge.to, edge.relation);
116        }
117    }
118
119    /// Get all inbound edges to a given node.
120    pub fn get_inbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
121        self.edges.iter().filter(|e| e.to == node).collect()
122    }
123
124    /// Get all outbound edges from a given node.
125    pub fn get_outbound_edges(&self, node: usize) -> Vec<&RelationEdge> {
126        self.edges.iter().filter(|e| e.from == node).collect()
127    }
128
129    /// Get the number of edges in the graph.
130    pub fn edge_count(&self) -> usize {
131        self.edges.len()
132    }
133
134    /// Get all unique node IDs referenced in the graph.
135    pub fn all_nodes(&self) -> Vec<usize> {
136        let mut nodes: Vec<usize> = self.edges.iter().flat_map(|e| [e.from, e.to]).collect();
137        nodes.sort();
138        nodes.dedup();
139        nodes
140    }
141
142    /// Detect cycles in the relation graph using DFS.
143    ///
144    /// Returns a list of cycle edges as `(from, to, relation)` tuples.
145    /// Empty result means no cycles detected.
146    pub fn detect_cycles(&self) -> Vec<(usize, usize, Relation)> {
147        if self.edges.is_empty() {
148            return Vec::new();
149        }
150
151        use crate::analysis::relationship_cycle_detector::detect_cycles_direct;
152
153        let relationships: Vec<(usize, usize)> =
154            self.edges.iter().map(|e| (e.from, e.to)).collect();
155
156        let cycle_indices = detect_cycles_direct(&relationships);
157
158        cycle_indices
159            .into_iter()
160            .filter_map(|(from_idx, to_idx)| {
161                self.edges.iter().find_map(|e| {
162                    if e.from == from_idx && e.to == to_idx {
163                        Some((e.from, e.to, e.relation.clone()))
164                    } else {
165                        None
166                    }
167                })
168            })
169            .collect()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_graph_add_edge() {
179        let mut graph = RelationGraph::new();
180        graph.add_edge(0, 1, Relation::Owner);
181
182        assert_eq!(graph.edge_count(), 1);
183        assert_eq!(graph.edges[0].from, 0);
184        assert_eq!(graph.edges[0].to, 1);
185        assert_eq!(graph.edges[0].relation, Relation::Owner);
186    }
187
188    #[test]
189    fn test_graph_no_self_edges() {
190        let mut graph = RelationGraph::new();
191        graph.add_edge(0, 0, Relation::Owner);
192        assert_eq!(graph.edge_count(), 0);
193    }
194
195    #[test]
196    fn test_graph_no_duplicate_edges() {
197        let mut graph = RelationGraph::new();
198        graph.add_edge(0, 1, Relation::Owner);
199        graph.add_edge(0, 1, Relation::Owner);
200        assert_eq!(graph.edge_count(), 1);
201    }
202
203    #[test]
204    fn test_graph_different_relations_allowed() {
205        let mut graph = RelationGraph::new();
206        graph.add_edge(0, 1, Relation::Owner);
207        graph.add_edge(0, 1, Relation::Clone);
208        assert_eq!(graph.edge_count(), 2);
209    }
210
211    #[test]
212    fn test_graph_inbound_outbound_edges() {
213        let mut graph = RelationGraph::new();
214        graph.add_edge(0, 2, Relation::Owner);
215        graph.add_edge(1, 2, Relation::Owner);
216        graph.add_edge(2, 3, Relation::Slice);
217
218        let inbound_to_2 = graph.get_inbound_edges(2);
219        assert_eq!(inbound_to_2.len(), 2);
220
221        let outbound_from_2 = graph.get_outbound_edges(2);
222        assert_eq!(outbound_from_2.len(), 1);
223    }
224
225    #[test]
226    fn test_graph_all_nodes() {
227        let mut graph = RelationGraph::new();
228        graph.add_edge(3, 1, Relation::Owner);
229        graph.add_edge(1, 2, Relation::Slice);
230
231        let nodes = graph.all_nodes();
232        assert_eq!(nodes, vec![1, 2, 3]);
233    }
234
235    #[test]
236    fn test_graph_add_edges_batch() {
237        let mut graph = RelationGraph::new();
238        graph.add_edges(vec![
239            RelationEdge {
240                from: 0,
241                to: 1,
242                relation: Relation::Owner,
243            },
244            RelationEdge {
245                from: 1,
246                to: 2,
247                relation: Relation::Slice,
248            },
249        ]);
250        assert_eq!(graph.edge_count(), 2);
251    }
252
253    #[test]
254    fn test_graph_detect_cycles_none() {
255        // Linear chain: 0 → 1 → 2 → no cycles.
256        let mut graph = RelationGraph::new();
257        graph.add_edge(0, 1, Relation::Owner);
258        graph.add_edge(1, 2, Relation::Slice);
259
260        let cycles = graph.detect_cycles();
261        assert!(cycles.is_empty());
262    }
263
264    #[test]
265    fn test_graph_detect_cycles_simple() {
266        // Cycle: 0 → 1 → 0
267        let mut graph = RelationGraph::new();
268        graph.add_edge(0, 1, Relation::Owner);
269        graph.add_edge(1, 0, Relation::Owner);
270
271        let cycles = graph.detect_cycles();
272        // Should detect at least one cycle edge.
273        assert!(!cycles.is_empty());
274        // Both edges should be part of the cycle.
275        let edge_pairs: Vec<_> = cycles.iter().map(|(f, t, _)| (*f, *t)).collect();
276        assert!(edge_pairs.contains(&(0, 1)) || edge_pairs.contains(&(1, 0)));
277    }
278
279    #[test]
280    fn test_graph_detect_cycles_longer() {
281        // Cycle: 0 → 1 → 2 → 0
282        let mut graph = RelationGraph::new();
283        graph.add_edge(0, 1, Relation::Owner);
284        graph.add_edge(1, 2, Relation::Slice);
285        graph.add_edge(2, 0, Relation::Clone);
286
287        let cycles = graph.detect_cycles();
288        assert!(!cycles.is_empty());
289        // Verify the cycle includes the back-edge (2 → 0).
290        let has_back_edge = cycles.iter().any(|(f, t, _)| *f == 2 && *t == 0);
291        assert!(has_back_edge, "cycle should contain back-edge (2, 0)");
292    }
293
294    #[test]
295    fn test_graph_detect_cycles_empty() {
296        let graph = RelationGraph::new();
297        let cycles = graph.detect_cycles();
298        assert!(cycles.is_empty());
299    }
300
301    #[test]
302    fn test_graph_detect_cycles_self_loop_blocked() {
303        // Self-edges are blocked by add_edge, so no cycle possible.
304        let mut graph = RelationGraph::new();
305        graph.add_edge(0, 0, Relation::Owner);
306        assert_eq!(graph.edge_count(), 0);
307
308        let cycles = graph.detect_cycles();
309        assert!(cycles.is_empty());
310    }
311
312    #[test]
313    fn test_graph_detect_cycles_diamond() {
314        // Diamond: 0 → 1, 0 → 2, 1 → 3, 2 → 3, 3 → 0
315        // Cycles: 0→1→3→0 and 0→2→3→0
316        let mut graph = RelationGraph::new();
317        graph.add_edge(0, 1, Relation::Owner);
318        graph.add_edge(0, 2, Relation::Owner);
319        graph.add_edge(1, 3, Relation::Slice);
320        graph.add_edge(2, 3, Relation::Slice);
321        graph.add_edge(3, 0, Relation::Clone);
322
323        let cycles = graph.detect_cycles();
324        assert!(!cycles.is_empty());
325    }
326}