1use crate::capture::types::{AllocationInfo, SmartPointerInfo, SmartPointerType};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct CircularReference {
13 pub cycle_path: Vec<CircularReferenceNode>,
15
16 pub suggested_weak_positions: Vec<usize>,
18
19 pub estimated_leaked_memory: usize,
21
22 pub severity: CircularReferenceSeverity,
24
25 pub cycle_type: CircularReferenceType,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct CircularReferenceNode {
32 pub ptr: usize,
34
35 pub data_ptr: usize,
37
38 pub var_name: Option<String>,
40
41 pub type_name: Option<String>,
43
44 pub pointer_type: SmartPointerType,
46
47 pub ref_count: usize,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub enum CircularReferenceSeverity {
54 Low,
56 Medium,
58 High,
60 Critical,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub enum CircularReferenceType {
67 Simple,
69 Complex,
71 SelfReference,
73 Nested,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct CircularReferenceAnalysis {
80 pub circular_references: Vec<CircularReference>,
82
83 pub total_smart_pointers: usize,
85
86 pub pointers_in_cycles: usize,
88
89 pub total_leaked_memory: usize,
91
92 pub statistics: CircularReferenceStatistics,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CircularReferenceStatistics {
99 pub by_severity: HashMap<String, usize>,
101
102 pub by_type: HashMap<String, usize>,
104
105 pub by_pointer_type: HashMap<String, usize>,
107
108 pub average_cycle_length: f64,
110
111 pub largest_cycle_size: usize,
113}
114
115#[derive(Debug)]
117struct ReferenceGraph {
118 adjacency: HashMap<usize, Vec<usize>>,
120
121 reverse_refs: HashMap<usize, Vec<usize>>,
123
124 smart_pointers: HashMap<usize, SmartPointerInfo>,
126
127 allocations: HashMap<usize, AllocationInfo>,
129}
130
131impl ReferenceGraph {
132 fn new(allocations: &[AllocationInfo]) -> Self {
134 let mut graph = ReferenceGraph {
135 adjacency: HashMap::new(),
136 reverse_refs: HashMap::new(),
137 smart_pointers: HashMap::new(),
138 allocations: HashMap::new(),
139 };
140
141 for allocation in allocations {
143 if let Some(ref smart_info) = allocation.smart_pointer_info {
144 if smart_info.is_weak_reference {
146 continue;
147 }
148
149 graph
150 .smart_pointers
151 .insert(allocation.ptr, smart_info.clone());
152 graph.allocations.insert(allocation.ptr, allocation.clone());
153
154 graph.adjacency.entry(allocation.ptr).or_default();
156
157 graph
159 .reverse_refs
160 .entry(smart_info.data_ptr)
161 .or_default()
162 .push(allocation.ptr);
163
164 for &clone_ptr in &smart_info.clones {
166 graph
167 .adjacency
168 .entry(allocation.ptr)
169 .or_default()
170 .push(clone_ptr);
171 }
172
173 if let Some(source_ptr) = smart_info.cloned_from {
175 graph
176 .adjacency
177 .entry(source_ptr)
178 .or_default()
179 .push(allocation.ptr);
180 }
181 }
182 }
183
184 graph
185 }
186
187 fn detect_cycles(&self) -> Vec<Vec<usize>> {
189 let mut cycles = Vec::new();
190 let mut visited = HashSet::new();
191 let mut rec_stack = HashSet::new();
192 let mut path = Vec::new();
193
194 for &ptr in self.smart_pointers.keys() {
195 if !visited.contains(&ptr) {
196 self.dfs_detect_cycles(ptr, &mut visited, &mut rec_stack, &mut path, &mut cycles);
197 }
198 }
199
200 cycles
201 }
202
203 fn dfs_detect_cycles(
205 &self,
206 ptr: usize,
207 visited: &mut HashSet<usize>,
208 rec_stack: &mut HashSet<usize>,
209 path: &mut Vec<usize>,
210 cycles: &mut Vec<Vec<usize>>,
211 ) {
212 visited.insert(ptr);
213 rec_stack.insert(ptr);
214 path.push(ptr);
215
216 if let Some(neighbors) = self.adjacency.get(&ptr) {
217 for &neighbor in neighbors {
218 if neighbor == ptr {
220 cycles.push(vec![ptr]);
222 tracing::trace!("Self-loop detected at ptr=0x{:x}", ptr);
223 continue;
224 }
225
226 if !visited.contains(&neighbor) {
227 self.dfs_detect_cycles(neighbor, visited, rec_stack, path, cycles);
228 } else if rec_stack.contains(&neighbor) {
229 if let Some(cycle_start) = path.iter().position(|&x| x == neighbor) {
231 let cycle = path[cycle_start..].to_vec();
232 cycles.push(cycle);
233 }
234 }
235 }
236 }
237
238 path.pop();
239 rec_stack.remove(&ptr);
240 }
241}
242
243pub fn detect_circular_references(allocations: &[AllocationInfo]) -> CircularReferenceAnalysis {
245 let graph = ReferenceGraph::new(allocations);
246 let raw_cycles = graph.detect_cycles();
247
248 let mut circular_references = Vec::new();
249 let mut total_leaked_memory = 0;
250 let mut pointers_in_cycles = HashSet::new();
251
252 for cycle_path in raw_cycles {
254 if cycle_path.len() < 2 {
255 continue; }
257
258 let circular_ref = analyze_cycle(&cycle_path, &graph);
259 total_leaked_memory += circular_ref.estimated_leaked_memory;
260
261 for node in &circular_ref.cycle_path {
262 pointers_in_cycles.insert(node.ptr);
263 }
264
265 circular_references.push(circular_ref);
266 }
267
268 let statistics = generate_statistics(&circular_references);
270
271 CircularReferenceAnalysis {
272 circular_references,
273 total_smart_pointers: graph.smart_pointers.len(),
274 pointers_in_cycles: pointers_in_cycles.len(),
275 total_leaked_memory,
276 statistics,
277 }
278}
279
280fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
282 let mut nodes = Vec::new();
283 let mut total_memory = 0;
284
285 for &ptr in cycle_path {
287 if let (Some(smart_info), Some(allocation)) =
288 (graph.smart_pointers.get(&ptr), graph.allocations.get(&ptr))
289 {
290 let node = CircularReferenceNode {
291 ptr,
292 data_ptr: smart_info.data_ptr,
293 var_name: allocation.var_name.clone(),
294 type_name: allocation.type_name.clone(),
295 pointer_type: smart_info.pointer_type.clone(),
296 ref_count: smart_info
297 .latest_ref_counts()
298 .map(|snapshot| snapshot.strong_count)
299 .unwrap_or(1),
300 };
301
302 total_memory += allocation.size;
303 nodes.push(node);
304 }
305 }
306
307 let cycle_type = if cycle_path.len() == 1 {
309 CircularReferenceType::SelfReference
310 } else if cycle_path.len() == 2 {
311 CircularReferenceType::Simple
312 } else {
313 CircularReferenceType::Complex
314 };
315
316 let severity = if total_memory > 1024 * 1024 {
318 CircularReferenceSeverity::Critical
320 } else if total_memory > 64 * 1024 {
321 CircularReferenceSeverity::High
323 } else if total_memory > 4 * 1024 {
324 CircularReferenceSeverity::Medium
326 } else {
327 CircularReferenceSeverity::Low
328 };
329
330 let suggested_weak_positions = suggest_weak_positions(&nodes);
332
333 CircularReference {
334 cycle_path: nodes,
335 suggested_weak_positions,
336 estimated_leaked_memory: total_memory,
337 severity,
338 cycle_type,
339 }
340}
341
342fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
344 if let Some((index, _)) = nodes
347 .iter()
348 .enumerate()
349 .max_by_key(|(_, node)| node.ref_count)
350 {
351 vec![index]
352 } else {
353 vec![0] }
355}
356
357fn generate_statistics(circular_references: &[CircularReference]) -> CircularReferenceStatistics {
359 let mut by_severity = HashMap::new();
360 let mut by_type = HashMap::new();
361 let mut by_pointer_type = HashMap::new();
362 let mut total_cycle_length = 0;
363 let mut largest_cycle_size = 0;
364
365 for circular_ref in circular_references {
366 let severity_key = format!("{:?}", circular_ref.severity);
368 *by_severity.entry(severity_key).or_insert(0) += 1;
369
370 let type_key = format!("{:?}", circular_ref.cycle_type);
372 *by_type.entry(type_key).or_insert(0) += 1;
373
374 for node in &circular_ref.cycle_path {
376 let pointer_type_key = format!("{:?}", node.pointer_type);
377 *by_pointer_type.entry(pointer_type_key).or_insert(0) += 1;
378 }
379
380 let cycle_length = circular_ref.cycle_path.len();
382 total_cycle_length += cycle_length;
383 largest_cycle_size = largest_cycle_size.max(cycle_length);
384 }
385
386 let average_cycle_length = if circular_references.is_empty() {
387 0.0
388 } else {
389 total_cycle_length as f64 / circular_references.len() as f64
390 };
391
392 CircularReferenceStatistics {
393 by_severity,
394 by_type,
395 by_pointer_type,
396 average_cycle_length,
397 largest_cycle_size,
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo, SmartPointerType};
405 use crate::utils::current_thread_id_u64;
406
407 use std::thread;
408
409 #[test]
410 fn test_circular_reference_node_creation() {
411 let node = CircularReferenceNode {
412 ptr: 0x1000,
413 data_ptr: 0x2000,
414 var_name: Some("test_node".to_string()),
415 type_name: Some("Rc<RefCell<Node>>".to_string()),
416 pointer_type: SmartPointerType::Rc,
417 ref_count: 1,
418 };
419
420 assert_eq!(node.ptr, 0x1000);
421 assert_eq!(node.data_ptr, 0x2000);
422 assert_eq!(node.var_name, Some("test_node".to_string()));
423 assert_eq!(node.type_name, Some("Rc<RefCell<Node>>".to_string()));
424 assert_eq!(node.pointer_type, SmartPointerType::Rc);
425 assert_eq!(node.ref_count, 1);
426 }
427
428 #[test]
429 fn test_circular_reference_creation() {
430 let cycle_path = vec![
431 CircularReferenceNode {
432 ptr: 0x1000,
433 data_ptr: 0x2000,
434 var_name: Some("node_a".to_string()),
435 type_name: Some("Rc<RefCell<Node>>".to_string()),
436 pointer_type: SmartPointerType::Rc,
437 ref_count: 1,
438 },
439 CircularReferenceNode {
440 ptr: 0x2000,
441 data_ptr: 0x1000,
442 var_name: Some("node_b".to_string()),
443 type_name: Some("Rc<RefCell<Node>>".to_string()),
444 pointer_type: SmartPointerType::Rc,
445 ref_count: 1,
446 },
447 ];
448
449 let circular_ref = CircularReference {
450 cycle_path: cycle_path.clone(),
451 suggested_weak_positions: vec![1],
452 estimated_leaked_memory: 1024,
453 severity: CircularReferenceSeverity::Medium,
454 cycle_type: CircularReferenceType::Simple,
455 };
456
457 assert_eq!(circular_ref.cycle_path.len(), 2);
458 assert_eq!(circular_ref.suggested_weak_positions, vec![1]);
459 assert_eq!(circular_ref.estimated_leaked_memory, 1024);
460 assert_eq!(circular_ref.severity, CircularReferenceSeverity::Medium);
461 assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
462 }
463
464 #[test]
465 fn test_circular_reference_severity_variants() {
466 let severities = [
467 CircularReferenceSeverity::Low,
468 CircularReferenceSeverity::Medium,
469 CircularReferenceSeverity::High,
470 CircularReferenceSeverity::Critical,
471 ];
472
473 assert_eq!(severities[0], CircularReferenceSeverity::Low);
475 assert_eq!(severities[1], CircularReferenceSeverity::Medium);
476 assert_eq!(severities[2], CircularReferenceSeverity::High);
477 assert_eq!(severities[3], CircularReferenceSeverity::Critical);
478 }
479
480 #[test]
481 fn test_circular_reference_type_variants() {
482 let types = [
483 CircularReferenceType::Simple,
484 CircularReferenceType::Complex,
485 CircularReferenceType::SelfReference,
486 CircularReferenceType::Nested,
487 ];
488
489 assert_eq!(types[0], CircularReferenceType::Simple);
491 assert_eq!(types[1], CircularReferenceType::Complex);
492 assert_eq!(types[2], CircularReferenceType::SelfReference);
493 assert_eq!(types[3], CircularReferenceType::Nested);
494 }
495
496 #[test]
497 fn test_circular_reference_statistics_generation() {
498 let empty_cycles = vec![];
500 let stats = generate_statistics(&empty_cycles);
501
502 assert_eq!(stats.average_cycle_length, 0.0);
503 assert_eq!(stats.largest_cycle_size, 0);
504 assert!(stats.by_severity.is_empty());
505 assert!(stats.by_type.is_empty());
506 assert!(stats.by_pointer_type.is_empty());
507
508 let cycles = vec![
510 CircularReference {
511 cycle_path: vec![CircularReferenceNode {
512 ptr: 0x1000,
513 data_ptr: 0x2000,
514 var_name: Some("node_a".to_string()),
515 type_name: Some("Rc<Node>".to_string()),
516 pointer_type: SmartPointerType::Rc,
517 ref_count: 2,
518 }],
519 suggested_weak_positions: vec![0],
520 estimated_leaked_memory: 1024,
521 severity: CircularReferenceSeverity::Low,
522 cycle_type: CircularReferenceType::SelfReference,
523 },
524 CircularReference {
525 cycle_path: vec![
526 CircularReferenceNode {
527 ptr: 0x2000,
528 data_ptr: 0x3000,
529 var_name: Some("node_b".to_string()),
530 type_name: Some("Arc<Node>".to_string()),
531 pointer_type: SmartPointerType::Arc,
532 ref_count: 3,
533 },
534 CircularReferenceNode {
535 ptr: 0x3000,
536 data_ptr: 0x2000,
537 var_name: Some("node_c".to_string()),
538 type_name: Some("Arc<Node>".to_string()),
539 pointer_type: SmartPointerType::Arc,
540 ref_count: 1,
541 },
542 ],
543 suggested_weak_positions: vec![0],
544 estimated_leaked_memory: 2048,
545 severity: CircularReferenceSeverity::Medium,
546 cycle_type: CircularReferenceType::Simple,
547 },
548 ];
549
550 let stats = generate_statistics(&cycles);
551
552 assert_eq!(stats.average_cycle_length, 1.5); assert_eq!(stats.largest_cycle_size, 2);
554 assert!(!stats.by_severity.is_empty());
555 assert!(!stats.by_type.is_empty());
556 assert!(!stats.by_pointer_type.is_empty());
557
558 assert_eq!(*stats.by_severity.get("Low").unwrap_or(&0), 1);
560 assert_eq!(*stats.by_severity.get("Medium").unwrap_or(&0), 1);
561 assert_eq!(*stats.by_type.get("SelfReference").unwrap_or(&0), 1);
562 assert_eq!(*stats.by_type.get("Simple").unwrap_or(&0), 1);
563 assert_eq!(*stats.by_pointer_type.get("Rc").unwrap_or(&0), 1);
564 assert_eq!(*stats.by_pointer_type.get("Arc").unwrap_or(&0), 2);
565 }
566
567 #[test]
568 fn test_suggest_weak_positions() {
569 let empty_nodes = vec![];
571 let positions = suggest_weak_positions(&empty_nodes);
572 assert_eq!(positions, vec![0]); let single_node = vec![CircularReferenceNode {
576 ptr: 0x1000,
577 data_ptr: 0x2000,
578 var_name: Some("node".to_string()),
579 type_name: Some("Rc<Node>".to_string()),
580 pointer_type: SmartPointerType::Rc,
581 ref_count: 1,
582 }];
583 let positions = suggest_weak_positions(&single_node);
584 assert_eq!(positions, vec![0]);
585
586 let multiple_nodes = vec![
588 CircularReferenceNode {
589 ptr: 0x1000,
590 data_ptr: 0x2000,
591 var_name: Some("node_a".to_string()),
592 type_name: Some("Rc<Node>".to_string()),
593 pointer_type: SmartPointerType::Rc,
594 ref_count: 1,
595 },
596 CircularReferenceNode {
597 ptr: 0x2000,
598 data_ptr: 0x3000,
599 var_name: Some("node_b".to_string()),
600 type_name: Some("Rc<Node>".to_string()),
601 pointer_type: SmartPointerType::Rc,
602 ref_count: 3, },
604 CircularReferenceNode {
605 ptr: 0x3000,
606 data_ptr: 0x1000,
607 var_name: Some("node_c".to_string()),
608 type_name: Some("Rc<Node>".to_string()),
609 pointer_type: SmartPointerType::Rc,
610 ref_count: 2,
611 },
612 ];
613 let positions = suggest_weak_positions(&multiple_nodes);
614 assert_eq!(positions, vec![1]); }
616
617 #[test]
618 fn test_analyze_cycle() {
619 let mut graph = ReferenceGraph {
621 adjacency: HashMap::new(),
622 reverse_refs: HashMap::new(),
623 smart_pointers: HashMap::new(),
624 allocations: HashMap::new(),
625 };
626
627 let smart_info_a = SmartPointerInfo {
629 data_ptr: 0x2000,
630 pointer_type: SmartPointerType::Rc,
631 is_weak_reference: false,
632 clones: vec![],
633 cloned_from: None,
634 ref_count_history: vec![RefCountSnapshot {
635 strong_count: 1,
636 weak_count: 0,
637 timestamp: 0,
638 }],
639 weak_count: None,
640 is_data_owner: true,
641 is_implicitly_deallocated: false,
642 };
643
644 let smart_info_b = SmartPointerInfo {
645 data_ptr: 0x1000,
646 pointer_type: SmartPointerType::Rc,
647 is_weak_reference: false,
648 clones: vec![],
649 cloned_from: None,
650 ref_count_history: vec![RefCountSnapshot {
651 strong_count: 1,
652 weak_count: 0,
653 timestamp: 0,
654 }],
655 weak_count: None,
656 is_data_owner: true,
657 is_implicitly_deallocated: false,
658 };
659
660 let allocation_a = AllocationInfo {
661 ptr: 0x1000,
662 size: 1024,
663 var_name: Some("node_a".to_string()),
664 type_name: Some("Rc<Node>".to_string()),
665 smart_pointer_info: Some(smart_info_a.clone()),
666 scope_name: None,
667 timestamp_alloc: 0,
668 timestamp_dealloc: None,
669 thread_id: std::thread::current().id(),
670 thread_id_u64: current_thread_id_u64(),
671 borrow_count: 0,
672 stack_trace: None,
673 is_leaked: false,
674 lifetime_ms: None,
675 borrow_info: None,
676 clone_info: None,
677 ownership_history_available: false,
678 memory_layout: None,
679 generic_info: None,
680 dynamic_type_info: None,
681 runtime_state: None,
682 stack_allocation: None,
683 temporary_object: None,
684 fragmentation_analysis: None,
685 generic_instantiation: None,
686 type_relationships: None,
687 type_usage: None,
688 function_call_tracking: None,
689 lifecycle_tracking: None,
690 access_tracking: None,
691 drop_chain_analysis: None,
692 };
693
694 let allocation_b = AllocationInfo {
695 ptr: 0x2000,
696 size: 2048,
697 var_name: Some("node_b".to_string()),
698 type_name: Some("Rc<Node>".to_string()),
699 smart_pointer_info: Some(smart_info_b.clone()),
700 scope_name: None,
701 timestamp_alloc: 0,
702 timestamp_dealloc: None,
703 thread_id: std::thread::current().id(),
704 thread_id_u64: current_thread_id_u64(),
705 borrow_count: 0,
706 stack_trace: None,
707 is_leaked: false,
708 lifetime_ms: None,
709 borrow_info: None,
710 clone_info: None,
711 ownership_history_available: false,
712 memory_layout: None,
713 generic_info: None,
714 dynamic_type_info: None,
715 runtime_state: None,
716 stack_allocation: None,
717 temporary_object: None,
718 fragmentation_analysis: None,
719 generic_instantiation: None,
720 type_relationships: None,
721 type_usage: None,
722 function_call_tracking: None,
723 lifecycle_tracking: None,
724 access_tracking: None,
725 drop_chain_analysis: None,
726 };
727
728 graph.smart_pointers.insert(0x1000, smart_info_a);
729 graph.smart_pointers.insert(0x2000, smart_info_b);
730 graph.allocations.insert(0x1000, allocation_a);
731 graph.allocations.insert(0x2000, allocation_b);
732
733 let cycle_path = vec![0x1000, 0x2000];
735 let circular_ref = analyze_cycle(&cycle_path, &graph);
736
737 assert_eq!(circular_ref.cycle_path.len(), 2);
738 assert_eq!(circular_ref.estimated_leaked_memory, 3072); assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
740 assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); assert!(!circular_ref.suggested_weak_positions.is_empty());
742 }
743
744 #[test]
745 fn test_reference_graph_creation() {
746 let empty_allocations = vec![];
748 let graph = ReferenceGraph::new(&empty_allocations);
749
750 assert!(graph.adjacency.is_empty());
751 assert!(graph.reverse_refs.is_empty());
752 assert!(graph.smart_pointers.is_empty());
753 assert!(graph.allocations.is_empty());
754
755 let allocations_without_smart = vec![AllocationInfo {
757 ptr: 0x1000,
758 size: 1024,
759 scope_name: None,
760 timestamp_alloc: 0,
761 timestamp_dealloc: None,
762 thread_id: std::thread::current().id(),
763 thread_id_u64: {
764 use std::hash::{Hash, Hasher};
765 let mut hasher = std::collections::hash_map::DefaultHasher::new();
766 std::thread::current().id().hash(&mut hasher);
767 hasher.finish()
768 },
769 borrow_count: 0,
770 stack_trace: None,
771 is_leaked: false,
772 lifetime_ms: None,
773 var_name: None,
774 type_name: None,
775 borrow_info: None,
776 clone_info: None,
777 ownership_history_available: false,
778 smart_pointer_info: None,
779 memory_layout: None,
780 generic_info: None,
781 dynamic_type_info: None,
782 runtime_state: None,
783 stack_allocation: None,
784 temporary_object: None,
785 fragmentation_analysis: None,
786 generic_instantiation: None,
787 type_relationships: None,
788 type_usage: None,
789 function_call_tracking: None,
790 lifecycle_tracking: None,
791 access_tracking: None,
792 drop_chain_analysis: None,
793 }];
794 let graph = ReferenceGraph::new(&allocations_without_smart);
795
796 assert!(graph.adjacency.is_empty());
797 assert!(graph.reverse_refs.is_empty());
798 assert!(graph.smart_pointers.is_empty());
799 assert!(graph.allocations.is_empty());
800
801 let smart_info = SmartPointerInfo {
803 data_ptr: 0x2000,
804 pointer_type: SmartPointerType::Rc,
805 is_weak_reference: false,
806 clones: vec![],
807 cloned_from: None,
808 ref_count_history: vec![RefCountSnapshot {
809 strong_count: 1,
810 weak_count: 0,
811 timestamp: 0,
812 }],
813 weak_count: None,
814 is_data_owner: true,
815 is_implicitly_deallocated: false,
816 };
817
818 let allocations_with_smart = vec![AllocationInfo {
819 ptr: 0x1000,
820 size: 1024,
821 var_name: None,
822 type_name: None,
823 scope_name: None,
824 timestamp_alloc: 0,
825 timestamp_dealloc: None,
826 thread_id: thread::current().id(),
827 thread_id_u64: {
828 use std::hash::{Hash, Hasher};
829 let mut hasher = std::collections::hash_map::DefaultHasher::new();
830 thread::current().id().hash(&mut hasher);
831 hasher.finish()
832 },
833 borrow_count: 0,
834 stack_trace: None,
835 is_leaked: false,
836 lifetime_ms: None,
837 borrow_info: None,
838 clone_info: None,
839 ownership_history_available: false,
840 smart_pointer_info: Some(smart_info.clone()),
841 memory_layout: None,
842 generic_info: None,
843 dynamic_type_info: None,
844 runtime_state: None,
845 stack_allocation: None,
846 temporary_object: None,
847 fragmentation_analysis: None,
848 generic_instantiation: None,
849 type_relationships: None,
850 type_usage: None,
851 function_call_tracking: None,
852 lifecycle_tracking: None,
853 access_tracking: None,
854 drop_chain_analysis: None,
855 }];
856
857 let graph = ReferenceGraph::new(&allocations_with_smart);
858
859 assert!(!graph.adjacency.is_empty());
860 assert!(!graph.reverse_refs.is_empty());
861 assert!(!graph.smart_pointers.is_empty());
862 assert!(!graph.allocations.is_empty());
863
864 assert!(graph.adjacency.contains_key(&0x1000));
865 assert!(graph.reverse_refs.contains_key(&0x2000));
866 assert!(graph.smart_pointers.contains_key(&0x1000));
867 assert!(graph.allocations.contains_key(&0x1000));
868 }
869
870 #[test]
871 fn test_reference_graph_with_weak_references() {
872 let weak_smart_info = SmartPointerInfo {
874 data_ptr: 0x2000,
875 pointer_type: SmartPointerType::Rc,
876 is_weak_reference: true, clones: vec![],
878 cloned_from: None,
879 ref_count_history: vec![RefCountSnapshot {
880 strong_count: 1,
881 weak_count: 1,
882 timestamp: 0,
883 }],
884 weak_count: Some(1),
885 is_data_owner: false,
886 is_implicitly_deallocated: false,
887 };
888
889 let allocations_with_weak = vec![AllocationInfo {
890 ptr: 0x1000,
891 size: 1024,
892 var_name: None,
893 type_name: None,
894 scope_name: None,
895 timestamp_alloc: 0,
896 timestamp_dealloc: None,
897 thread_id: thread::current().id(),
898 thread_id_u64: {
899 use std::hash::{Hash, Hasher};
900 let mut hasher = std::collections::hash_map::DefaultHasher::new();
901 thread::current().id().hash(&mut hasher);
902 hasher.finish()
903 },
904 borrow_count: 0,
905 stack_trace: None,
906 is_leaked: false,
907 lifetime_ms: None,
908 borrow_info: None,
909 clone_info: None,
910 ownership_history_available: false,
911 smart_pointer_info: Some(weak_smart_info),
912 memory_layout: None,
913 generic_info: None,
914 dynamic_type_info: None,
915 runtime_state: None,
916 stack_allocation: None,
917 temporary_object: None,
918 fragmentation_analysis: None,
919 generic_instantiation: None,
920 type_relationships: None,
921 type_usage: None,
922 function_call_tracking: None,
923 lifecycle_tracking: None,
924 access_tracking: None,
925 drop_chain_analysis: None,
926 }];
927
928 let graph = ReferenceGraph::new(&allocations_with_weak);
929
930 assert!(graph.adjacency.is_empty());
932 assert!(graph.reverse_refs.is_empty());
933 assert!(graph.smart_pointers.is_empty());
934 assert!(graph.allocations.is_empty());
935 }
936
937 #[test]
938 fn test_detect_circular_references_empty() {
939 let empty_allocations = vec![];
940 let analysis = detect_circular_references(&empty_allocations);
941
942 assert_eq!(analysis.circular_references.len(), 0);
943 assert_eq!(analysis.total_smart_pointers, 0);
944 assert_eq!(analysis.pointers_in_cycles, 0);
945 assert_eq!(analysis.total_leaked_memory, 0);
946
947 assert_eq!(analysis.statistics.average_cycle_length, 0.0);
949 assert_eq!(analysis.statistics.largest_cycle_size, 0);
950 }
951
952 #[test]
953 fn test_circular_reference_analysis_structure() {
954 let analysis = CircularReferenceAnalysis {
955 circular_references: vec![],
956 total_smart_pointers: 10,
957 pointers_in_cycles: 5,
958 total_leaked_memory: 10240,
959 statistics: CircularReferenceStatistics {
960 by_severity: HashMap::new(),
961 by_type: HashMap::new(),
962 by_pointer_type: HashMap::new(),
963 average_cycle_length: 0.0,
964 largest_cycle_size: 0,
965 },
966 };
967
968 assert_eq!(analysis.total_smart_pointers, 10);
969 assert_eq!(analysis.pointers_in_cycles, 5);
970 assert_eq!(analysis.total_leaked_memory, 10240);
971 }
972
973 #[test]
974 fn test_circular_reference_severity_determination() {
975 let memory_size = 1024; let low_severity = if memory_size > 1024 * 1024 {
978 CircularReferenceSeverity::Critical
980 } else if memory_size > 64 * 1024 {
981 CircularReferenceSeverity::High
983 } else if memory_size > 4 * 1024 {
984 CircularReferenceSeverity::Medium
986 } else {
987 CircularReferenceSeverity::Low
988 };
989 assert_eq!(low_severity, CircularReferenceSeverity::Low);
990
991 let memory_size = 5000; let medium_severity = if memory_size > 1024 * 1024 {
994 CircularReferenceSeverity::Critical
996 } else if memory_size > 64 * 1024 {
997 CircularReferenceSeverity::High
999 } else if memory_size > 4 * 1024 {
1000 CircularReferenceSeverity::Medium
1002 } else {
1003 CircularReferenceSeverity::Low
1004 };
1005 assert_eq!(medium_severity, CircularReferenceSeverity::Medium);
1006
1007 let memory_size = 70000; let high_severity = if memory_size > 1024 * 1024 {
1010 CircularReferenceSeverity::Critical
1012 } else if memory_size > 64 * 1024 {
1013 CircularReferenceSeverity::High
1015 } else if memory_size > 4 * 1024 {
1016 CircularReferenceSeverity::Medium
1018 } else {
1019 CircularReferenceSeverity::Low
1020 };
1021 assert_eq!(high_severity, CircularReferenceSeverity::High);
1022
1023 let memory_size = 2000000; let critical_severity = if memory_size > 1024 * 1024 {
1026 CircularReferenceSeverity::Critical
1028 } else if memory_size > 64 * 1024 {
1029 CircularReferenceSeverity::High
1031 } else if memory_size > 4 * 1024 {
1032 CircularReferenceSeverity::Medium
1034 } else {
1035 CircularReferenceSeverity::Low
1036 };
1037 assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
1038 }
1039
1040 #[test]
1041 fn test_circular_reference_type_determination() {
1042 let cycle_length = 1;
1044 let self_ref_type = if cycle_length == 1 {
1045 CircularReferenceType::SelfReference
1046 } else if cycle_length == 2 {
1047 CircularReferenceType::Simple
1048 } else {
1049 CircularReferenceType::Complex
1050 };
1051 assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
1052
1053 let cycle_length = 2;
1055 let simple_type = if cycle_length == 1 {
1056 CircularReferenceType::SelfReference
1057 } else if cycle_length == 2 {
1058 CircularReferenceType::Simple
1059 } else {
1060 CircularReferenceType::Complex
1061 };
1062 assert_eq!(simple_type, CircularReferenceType::Simple);
1063
1064 let cycle_length = 5;
1066 let complex_type = if cycle_length == 1 {
1067 CircularReferenceType::SelfReference
1068 } else if cycle_length == 2 {
1069 CircularReferenceType::Simple
1070 } else {
1071 CircularReferenceType::Complex
1072 };
1073 assert_eq!(complex_type, CircularReferenceType::Complex);
1074 }
1075}