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 Relation::ArcClone => clone_edges += 1,
132 Relation::RcClone => clone_edges += 1,
133 Relation::ImmutableBorrow => slice_edges += 1,
134 Relation::MutableBorrow => ownership_edges += 1,
135 }
136 }
137
138 RelationshipStats {
139 ownership_edges,
140 contains_edges,
141 shares_edges,
142 slice_edges,
143 clone_edges,
144 }
145 }
146
147 pub fn relationships(&mut self) -> Vec<RelationshipEdge> {
152 let allocations: Vec<crate::snapshot::types::ActiveAllocation> = self
154 .view
155 .allocations()
156 .iter()
157 .map(|a| (*a).clone())
158 .collect();
159
160 let relation_graph = self.relation_graph();
161 let edges = &relation_graph.edges;
162
163 debug!("Building relationships from {} edges", edges.len());
164
165 let mut relationships: Vec<RelationshipEdge> = edges
166 .iter()
167 .filter_map(|edge| {
168 let from_alloc = allocations.get(edge.from);
169 let to_alloc = allocations.get(edge.to);
170
171 let from_ptr = from_alloc.and_then(|a| a.ptr).unwrap_or(edge.from);
172 let to_ptr = to_alloc.and_then(|a| a.ptr).unwrap_or(edge.to);
173
174 if from_ptr == 0 && to_ptr == 0 {
176 return None;
177 }
178
179 Some(RelationshipEdge {
180 from_ptr,
181 to_ptr,
182 from_var_name: from_alloc.and_then(|a| a.var_name.clone()),
183 to_var_name: to_alloc.and_then(|a| a.var_name.clone()),
184 from_type_name: from_alloc.and_then(|a| a.type_name.clone()),
185 to_type_name: to_alloc.and_then(|a| a.type_name.clone()),
186 relation: edge.relation.clone(),
187 is_container_source: from_ptr == 0,
188 is_container_target: to_ptr == 0,
189 })
190 })
191 .collect();
192
193 if relationships.len() > 500 {
195 warn!(
196 "Relationship count ({}) exceeds limit, truncating to 500",
197 relationships.len()
198 );
199 relationships.truncate(500);
200 }
201
202 let borrow_analyzer = crate::analysis::borrow_analysis::get_global_borrow_analyzer();
204 let borrow_history = borrow_analyzer.get_borrow_history();
205
206 for event in &borrow_history {
207 let borrow_relation = match event.borrow_info.borrow_type {
208 crate::analysis::borrow_analysis::BorrowType::Immutable => {
209 crate::analysis::relation_inference::Relation::ImmutableBorrow
210 }
211 crate::analysis::borrow_analysis::BorrowType::Mutable => {
212 crate::analysis::relation_inference::Relation::MutableBorrow
213 }
214 crate::analysis::borrow_analysis::BorrowType::Shared => {
215 crate::analysis::relation_inference::Relation::Shares
216 }
217 crate::analysis::borrow_analysis::BorrowType::Weak => {
218 crate::analysis::relation_inference::Relation::Contains
219 }
220 };
221
222 relationships.push(RelationshipEdge {
223 from_ptr: event.borrow_info.ptr,
224 to_ptr: event.borrow_info.ptr,
225 from_var_name: Some(event.borrow_info.var_name.clone()),
226 to_var_name: Some(format!("borrow_{:?}", event.borrow_info.id)),
227 from_type_name: Some(format!("{:?}", event.borrow_info.borrow_type)),
228 to_type_name: Some(format!("{:?}", event.borrow_info.borrow_type)),
229 relation: borrow_relation,
230 is_container_source: false,
231 is_container_target: false,
232 });
233 }
234
235 debug!("Returning {} relationships", relationships.len());
236 relationships
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct OwnershipStats {
243 pub total_objects: usize,
245 pub total_bytes: usize,
247 pub unique_types: usize,
249}
250
251#[derive(Debug, Clone, Default)]
253pub struct RelationshipStats {
254 pub ownership_edges: usize,
256 pub contains_edges: usize,
258 pub shares_edges: usize,
260 pub slice_edges: usize,
262 pub clone_edges: usize,
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::event_store::MemoryEvent;
270
271 #[test]
272 fn test_graph_analysis() {
273 let events = vec![MemoryEvent::allocate(0x1000, 64, 1)];
274 let view = MemoryView::from_events(events);
275 let analysis = GraphAnalysis::from_view(&view);
276 let stats = analysis.ownership_stats();
277 assert_eq!(stats.total_objects, 1);
278 }
279
280 #[test]
281 fn test_cycle_detection_empty() {
282 let events: Vec<MemoryEvent> = vec![];
283 let view = MemoryView::from_events(events);
284 let mut analysis = GraphAnalysis::from_view(&view);
285 let cycles = analysis.cycles();
286 assert_eq!(cycles.cycle_count, 0);
287 }
288
289 #[test]
290 fn test_relationship_stats() {
291 use crate::core::types::TrackKind;
292 use crate::snapshot::types::ActiveAllocation;
293
294 let buf1 = vec![0u8; 64];
295 let buf2 = vec![0u8; 128];
296 let ptr1 = buf1.as_ptr() as usize;
297 let ptr2 = buf2.as_ptr() as usize;
298
299 let allocs = vec![
300 ActiveAllocation {
301 ptr: Some(ptr1),
302 size: 64,
303 kind: TrackKind::HeapOwner {
304 ptr: ptr1,
305 size: 64,
306 },
307 allocated_at: 1000,
308 var_name: None,
309 type_name: None,
310 thread_id: 1,
311 call_stack_hash: None,
312 module_path: None,
313 stack_ptr: None,
314 },
315 ActiveAllocation {
316 ptr: Some(ptr2),
317 size: 128,
318 kind: TrackKind::HeapOwner {
319 ptr: ptr2,
320 size: 128,
321 },
322 allocated_at: 1001,
323 var_name: None,
324 type_name: None,
325 thread_id: 1,
326 call_stack_hash: None,
327 module_path: None,
328 stack_ptr: None,
329 },
330 ];
331
332 let snapshot = crate::snapshot::MemorySnapshot::new();
333 let mut snapshot = snapshot;
334 for alloc in allocs {
335 snapshot
336 .active_allocations
337 .insert(alloc.ptr.unwrap(), alloc);
338 }
339
340 let view = MemoryView::new(snapshot, vec![]);
341 let mut analysis = GraphAnalysis::from_view(&view);
342 let stats = analysis.relationship_stats();
343 let _ = stats.ownership_edges;
346
347 drop(buf1);
349 drop(buf2);
350 }
351}