1use crate::analysis::relation_inference::{Relation, RelationGraph};
4use crate::analyzer::report::{CycleInfo, CycleReport};
5use crate::view::MemoryView;
6use std::collections::HashSet;
7use tracing::{debug, info, warn};
8
9#[derive(Debug, Clone)]
11pub struct RelationshipEdge {
12 pub from_ptr: usize,
14 pub to_ptr: usize,
16 pub from_var_name: Option<String>,
18 pub to_var_name: Option<String>,
20 pub from_type_name: Option<String>,
22 pub to_type_name: Option<String>,
24 pub relation: Relation,
26 pub is_container_source: bool,
28 pub is_container_target: bool,
30}
31
32pub struct GraphAnalysis {
36 view: MemoryView,
37 relation_graph: Option<RelationGraph>,
38}
39
40impl GraphAnalysis {
41 pub fn from_view(view: &MemoryView) -> Self {
43 debug!("Creating GraphAnalysis with {} allocations", view.len());
44 Self {
45 view: view.clone(),
46 relation_graph: None,
47 }
48 }
49
50 fn relation_graph(&mut self) -> &RelationGraph {
52 if self.relation_graph.is_none() {
53 info!("Building relation graph from view");
54 self.relation_graph = Some(RelationGraph::from_view(&self.view));
55 }
56 self.relation_graph.as_ref().unwrap_or_else(|| {
57 unreachable!("RelationGraph should be initialized after lazy initialization")
58 })
59 }
60
61 pub fn cycles(&mut self) -> CycleReport {
69 let allocations = self.view.allocations();
70 if allocations.is_empty() {
71 debug!("Cycle detection: no allocations, returning empty report");
72 return CycleReport::empty();
73 }
74
75 let relation_graph = self.relation_graph();
76 let cycle_edges = relation_graph.detect_cycles();
77
78 if cycle_edges.is_empty() {
79 debug!("Cycle detection: no cycles found");
80 return CycleReport::empty();
81 }
82
83 info!("Cycle detection: found {} cycles", cycle_edges.len());
84
85 let cycles: Vec<CycleInfo> = cycle_edges
86 .iter()
87 .map(|(from, to, _)| CycleInfo {
88 nodes: vec![*from as u64, *to as u64],
89 })
90 .collect();
91
92 CycleReport {
93 cycle_count: cycles.len(),
94 cycles,
95 }
96 }
97
98 pub fn ownership_stats(&self) -> OwnershipStats {
100 let allocations = self.view.allocations();
101 OwnershipStats {
102 total_objects: allocations.len(),
103 total_bytes: allocations.iter().map(|a| a.size).sum(),
104 unique_types: allocations
105 .iter()
106 .filter_map(|a| a.type_name.as_ref())
107 .collect::<HashSet<_>>()
108 .len(),
109 }
110 }
111
112 pub fn relationship_stats(&mut self) -> RelationshipStats {
114 let relation_graph = self.relation_graph();
115 let edges = &relation_graph.edges;
116
117 let mut ownership_edges = 0usize;
118 let mut contains_edges = 0usize;
119 let mut shares_edges = 0usize;
120 let mut slice_edges = 0usize;
121 let mut clone_edges = 0usize;
122
123 for edge in edges {
124 match edge.relation {
125 Relation::Owns => ownership_edges += 1,
126 Relation::Contains => contains_edges += 1,
127 Relation::Shares => shares_edges += 1,
128 Relation::Slice => slice_edges += 1,
129 Relation::Clone => clone_edges += 1,
130 Relation::Evolution => {}
131 }
132 }
133
134 RelationshipStats {
135 ownership_edges,
136 contains_edges,
137 shares_edges,
138 slice_edges,
139 clone_edges,
140 }
141 }
142
143 pub fn relationships(&mut self) -> Vec<RelationshipEdge> {
148 let allocations: Vec<crate::snapshot::types::ActiveAllocation> = self
150 .view
151 .allocations()
152 .iter()
153 .map(|a| (*a).clone())
154 .collect();
155
156 let relation_graph = self.relation_graph();
157 let edges = &relation_graph.edges;
158
159 debug!("Building relationships from {} edges", edges.len());
160
161 let mut relationships: Vec<RelationshipEdge> = edges
162 .iter()
163 .filter_map(|edge| {
164 let from_alloc = allocations.get(edge.from);
165 let to_alloc = allocations.get(edge.to);
166
167 let from_ptr = from_alloc.and_then(|a| a.ptr).unwrap_or(edge.from);
168 let to_ptr = to_alloc.and_then(|a| a.ptr).unwrap_or(edge.to);
169
170 if from_ptr == 0 && to_ptr == 0 {
172 return None;
173 }
174
175 Some(RelationshipEdge {
176 from_ptr,
177 to_ptr,
178 from_var_name: from_alloc.and_then(|a| a.var_name.clone()),
179 to_var_name: to_alloc.and_then(|a| a.var_name.clone()),
180 from_type_name: from_alloc.and_then(|a| a.type_name.clone()),
181 to_type_name: to_alloc.and_then(|a| a.type_name.clone()),
182 relation: edge.relation.clone(),
183 is_container_source: from_ptr == 0,
184 is_container_target: to_ptr == 0,
185 })
186 })
187 .collect();
188
189 if relationships.len() > 500 {
191 warn!(
192 "Relationship count ({}) exceeds limit, truncating to 500",
193 relationships.len()
194 );
195 relationships.truncate(500);
196 }
197
198 debug!("Returning {} relationships", relationships.len());
199 relationships
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct OwnershipStats {
206 pub total_objects: usize,
208 pub total_bytes: usize,
210 pub unique_types: usize,
212}
213
214#[derive(Debug, Clone, Default)]
216pub struct RelationshipStats {
217 pub ownership_edges: usize,
219 pub contains_edges: usize,
221 pub shares_edges: usize,
223 pub slice_edges: usize,
225 pub clone_edges: usize,
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::event_store::MemoryEvent;
233
234 #[test]
235 fn test_graph_analysis() {
236 let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
237 let view = MemoryView::from_events(events);
238 let analysis = GraphAnalysis::from_view(&view);
239 let stats = analysis.ownership_stats();
240 assert_eq!(stats.total_objects, 1);
241 }
242
243 #[test]
244 fn test_cycle_detection_empty() {
245 let events: Vec<MemoryEvent> = vec![];
246 let view = MemoryView::from_events(events);
247 let mut analysis = GraphAnalysis::from_view(&view);
248 let cycles = analysis.cycles();
249 assert_eq!(cycles.cycle_count, 0);
250 }
251
252 #[test]
253 fn test_relationship_stats() {
254 use crate::core::types::TrackKind;
255 use crate::snapshot::types::ActiveAllocation;
256
257 let buf1 = vec![0u8; 64];
258 let buf2 = vec![0u8; 128];
259 let ptr1 = buf1.as_ptr() as usize;
260 let ptr2 = buf2.as_ptr() as usize;
261
262 let allocs = vec![
263 ActiveAllocation {
264 ptr: Some(ptr1),
265 size: 64,
266 kind: TrackKind::HeapOwner {
267 ptr: ptr1,
268 size: 64,
269 },
270 allocated_at: 1000,
271 var_name: None,
272 type_name: None,
273 thread_id: 1,
274 call_stack_hash: None,
275 },
276 ActiveAllocation {
277 ptr: Some(ptr2),
278 size: 128,
279 kind: TrackKind::HeapOwner {
280 ptr: ptr2,
281 size: 128,
282 },
283 allocated_at: 1001,
284 var_name: None,
285 type_name: None,
286 thread_id: 1,
287 call_stack_hash: None,
288 },
289 ];
290
291 let snapshot = crate::snapshot::MemorySnapshot::new();
292 let mut snapshot = snapshot;
293 for alloc in allocs {
294 snapshot
295 .active_allocations
296 .insert(alloc.ptr.unwrap(), alloc);
297 }
298
299 let view = MemoryView::new(snapshot, vec![]);
300 let mut analysis = GraphAnalysis::from_view(&view);
301 let stats = analysis.relationship_stats();
302 let _ = stats.ownership_edges;
305
306 drop(buf1);
308 drop(buf2);
309 }
310}