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 let mut path_index = HashMap::new();
194
195 for &ptr in self.smart_pointers.keys() {
196 if !visited.contains(&ptr) {
197 self.dfs_detect_cycles(
198 ptr,
199 &mut visited,
200 &mut rec_stack,
201 &mut path,
202 &mut path_index,
203 &mut cycles,
204 );
205 }
206 }
207
208 cycles
209 }
210
211 fn dfs_detect_cycles(
213 &self,
214 ptr: usize,
215 visited: &mut HashSet<usize>,
216 rec_stack: &mut HashSet<usize>,
217 path: &mut Vec<usize>,
218 path_index: &mut HashMap<usize, usize>,
219 cycles: &mut Vec<Vec<usize>>,
220 ) {
221 visited.insert(ptr);
222 rec_stack.insert(ptr);
223 path_index.insert(ptr, path.len());
224 path.push(ptr);
225
226 if let Some(neighbors) = self.adjacency.get(&ptr) {
227 for &neighbor in neighbors {
228 if neighbor == ptr {
230 cycles.push(vec![ptr]);
232 tracing::trace!("Self-loop detected at ptr=0x{:x}", ptr);
233 continue;
234 }
235
236 if !visited.contains(&neighbor) {
237 self.dfs_detect_cycles(neighbor, visited, rec_stack, path, path_index, cycles);
238 } else if rec_stack.contains(&neighbor) {
239 if let Some(&cycle_start) = path_index.get(&neighbor) {
242 let cycle = path[cycle_start..].to_vec();
243 cycles.push(cycle);
244 }
245 }
246 }
247 }
248
249 path.pop();
250 path_index.remove(&ptr);
251 rec_stack.remove(&ptr);
252 }
253}
254
255pub fn detect_circular_references(allocations: &[AllocationInfo]) -> CircularReferenceAnalysis {
257 let graph = ReferenceGraph::new(allocations);
258 let raw_cycles = graph.detect_cycles();
259
260 let mut circular_references = Vec::new();
261 let mut total_leaked_memory = 0;
262 let mut pointers_in_cycles = HashSet::new();
263
264 for cycle_path in raw_cycles {
266 if cycle_path.len() < 2 {
267 continue; }
269
270 let circular_ref = analyze_cycle(&cycle_path, &graph);
271 total_leaked_memory += circular_ref.estimated_leaked_memory;
272
273 for node in &circular_ref.cycle_path {
274 pointers_in_cycles.insert(node.ptr);
275 }
276
277 circular_references.push(circular_ref);
278 }
279
280 let statistics = generate_statistics(&circular_references);
282
283 CircularReferenceAnalysis {
284 circular_references,
285 total_smart_pointers: graph.smart_pointers.len(),
286 pointers_in_cycles: pointers_in_cycles.len(),
287 total_leaked_memory,
288 statistics,
289 }
290}
291
292fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
294 let mut nodes = Vec::new();
295 let mut total_memory = 0;
296
297 for &ptr in cycle_path {
299 if let (Some(smart_info), Some(allocation)) =
300 (graph.smart_pointers.get(&ptr), graph.allocations.get(&ptr))
301 {
302 let node = CircularReferenceNode {
303 ptr,
304 data_ptr: smart_info.data_ptr,
305 var_name: allocation.var_name.clone(),
306 type_name: allocation.type_name.clone(),
307 pointer_type: smart_info.pointer_type.clone(),
308 ref_count: smart_info
309 .latest_ref_counts()
310 .map(|snapshot| snapshot.strong_count)
311 .unwrap_or(1),
312 };
313
314 total_memory += allocation.size;
315 nodes.push(node);
316 }
317 }
318
319 let cycle_type = if cycle_path.len() == 1 {
321 CircularReferenceType::SelfReference
322 } else if cycle_path.len() == 2 {
323 CircularReferenceType::Simple
324 } else {
325 CircularReferenceType::Complex
326 };
327
328 let severity = if total_memory > 1024 * 1024 {
330 CircularReferenceSeverity::Critical
332 } else if total_memory > 64 * 1024 {
333 CircularReferenceSeverity::High
335 } else if total_memory > 4 * 1024 {
336 CircularReferenceSeverity::Medium
338 } else {
339 CircularReferenceSeverity::Low
340 };
341
342 let suggested_weak_positions = suggest_weak_positions(&nodes);
344
345 CircularReference {
346 cycle_path: nodes,
347 suggested_weak_positions,
348 estimated_leaked_memory: total_memory,
349 severity,
350 cycle_type,
351 }
352}
353
354fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
356 if let Some((index, _)) = nodes
359 .iter()
360 .enumerate()
361 .max_by_key(|(_, node)| node.ref_count)
362 {
363 vec![index]
364 } else {
365 vec![0] }
367}
368
369fn generate_statistics(circular_references: &[CircularReference]) -> CircularReferenceStatistics {
371 let mut by_severity = HashMap::new();
372 let mut by_type = HashMap::new();
373 let mut by_pointer_type = HashMap::new();
374 let mut total_cycle_length = 0;
375 let mut largest_cycle_size = 0;
376
377 for circular_ref in circular_references {
378 let severity_key = format!("{:?}", circular_ref.severity);
380 *by_severity.entry(severity_key).or_insert(0) += 1;
381
382 let type_key = format!("{:?}", circular_ref.cycle_type);
384 *by_type.entry(type_key).or_insert(0) += 1;
385
386 for node in &circular_ref.cycle_path {
388 let pointer_type_key = format!("{:?}", node.pointer_type);
389 *by_pointer_type.entry(pointer_type_key).or_insert(0) += 1;
390 }
391
392 let cycle_length = circular_ref.cycle_path.len();
394 total_cycle_length += cycle_length;
395 largest_cycle_size = largest_cycle_size.max(cycle_length);
396 }
397
398 let average_cycle_length = if circular_references.is_empty() {
399 0.0
400 } else {
401 total_cycle_length as f64 / circular_references.len() as f64
402 };
403
404 CircularReferenceStatistics {
405 by_severity,
406 by_type,
407 by_pointer_type,
408 average_cycle_length,
409 largest_cycle_size,
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo, SmartPointerType};
417 use crate::utils::current_thread_id_u64;
418
419 use std::thread;
420
421 #[test]
422 fn test_circular_reference_node_creation() {
423 let node = CircularReferenceNode {
424 ptr: 0x1000,
425 data_ptr: 0x2000,
426 var_name: Some("test_node".to_string()),
427 type_name: Some("Rc<RefCell<Node>>".to_string()),
428 pointer_type: SmartPointerType::Rc,
429 ref_count: 1,
430 };
431
432 assert_eq!(node.ptr, 0x1000);
433 assert_eq!(node.data_ptr, 0x2000);
434 assert_eq!(node.var_name, Some("test_node".to_string()));
435 assert_eq!(node.type_name, Some("Rc<RefCell<Node>>".to_string()));
436 assert_eq!(node.pointer_type, SmartPointerType::Rc);
437 assert_eq!(node.ref_count, 1);
438 }
439
440 #[test]
441 fn test_circular_reference_creation() {
442 let cycle_path = vec![
443 CircularReferenceNode {
444 ptr: 0x1000,
445 data_ptr: 0x2000,
446 var_name: Some("node_a".to_string()),
447 type_name: Some("Rc<RefCell<Node>>".to_string()),
448 pointer_type: SmartPointerType::Rc,
449 ref_count: 1,
450 },
451 CircularReferenceNode {
452 ptr: 0x2000,
453 data_ptr: 0x1000,
454 var_name: Some("node_b".to_string()),
455 type_name: Some("Rc<RefCell<Node>>".to_string()),
456 pointer_type: SmartPointerType::Rc,
457 ref_count: 1,
458 },
459 ];
460
461 let circular_ref = CircularReference {
462 cycle_path: cycle_path.clone(),
463 suggested_weak_positions: vec![1],
464 estimated_leaked_memory: 1024,
465 severity: CircularReferenceSeverity::Medium,
466 cycle_type: CircularReferenceType::Simple,
467 };
468
469 assert_eq!(circular_ref.cycle_path.len(), 2);
470 assert_eq!(circular_ref.suggested_weak_positions, vec![1]);
471 assert_eq!(circular_ref.estimated_leaked_memory, 1024);
472 assert_eq!(circular_ref.severity, CircularReferenceSeverity::Medium);
473 assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
474 }
475
476 #[test]
477 fn test_circular_reference_severity_variants() {
478 let severities = [
479 CircularReferenceSeverity::Low,
480 CircularReferenceSeverity::Medium,
481 CircularReferenceSeverity::High,
482 CircularReferenceSeverity::Critical,
483 ];
484
485 assert_eq!(severities[0], CircularReferenceSeverity::Low);
487 assert_eq!(severities[1], CircularReferenceSeverity::Medium);
488 assert_eq!(severities[2], CircularReferenceSeverity::High);
489 assert_eq!(severities[3], CircularReferenceSeverity::Critical);
490 }
491
492 #[test]
493 fn test_circular_reference_type_variants() {
494 let types = [
495 CircularReferenceType::Simple,
496 CircularReferenceType::Complex,
497 CircularReferenceType::SelfReference,
498 CircularReferenceType::Nested,
499 ];
500
501 assert_eq!(types[0], CircularReferenceType::Simple);
503 assert_eq!(types[1], CircularReferenceType::Complex);
504 assert_eq!(types[2], CircularReferenceType::SelfReference);
505 assert_eq!(types[3], CircularReferenceType::Nested);
506 }
507
508 #[test]
509 fn test_circular_reference_statistics_generation() {
510 let empty_cycles = vec![];
512 let stats = generate_statistics(&empty_cycles);
513
514 assert_eq!(stats.average_cycle_length, 0.0);
515 assert_eq!(stats.largest_cycle_size, 0);
516 assert!(stats.by_severity.is_empty());
517 assert!(stats.by_type.is_empty());
518 assert!(stats.by_pointer_type.is_empty());
519
520 let cycles = vec![
522 CircularReference {
523 cycle_path: vec![CircularReferenceNode {
524 ptr: 0x1000,
525 data_ptr: 0x2000,
526 var_name: Some("node_a".to_string()),
527 type_name: Some("Rc<Node>".to_string()),
528 pointer_type: SmartPointerType::Rc,
529 ref_count: 2,
530 }],
531 suggested_weak_positions: vec![0],
532 estimated_leaked_memory: 1024,
533 severity: CircularReferenceSeverity::Low,
534 cycle_type: CircularReferenceType::SelfReference,
535 },
536 CircularReference {
537 cycle_path: vec![
538 CircularReferenceNode {
539 ptr: 0x2000,
540 data_ptr: 0x3000,
541 var_name: Some("node_b".to_string()),
542 type_name: Some("Arc<Node>".to_string()),
543 pointer_type: SmartPointerType::Arc,
544 ref_count: 3,
545 },
546 CircularReferenceNode {
547 ptr: 0x3000,
548 data_ptr: 0x2000,
549 var_name: Some("node_c".to_string()),
550 type_name: Some("Arc<Node>".to_string()),
551 pointer_type: SmartPointerType::Arc,
552 ref_count: 1,
553 },
554 ],
555 suggested_weak_positions: vec![0],
556 estimated_leaked_memory: 2048,
557 severity: CircularReferenceSeverity::Medium,
558 cycle_type: CircularReferenceType::Simple,
559 },
560 ];
561
562 let stats = generate_statistics(&cycles);
563
564 assert_eq!(stats.average_cycle_length, 1.5); assert_eq!(stats.largest_cycle_size, 2);
566 assert!(!stats.by_severity.is_empty());
567 assert!(!stats.by_type.is_empty());
568 assert!(!stats.by_pointer_type.is_empty());
569
570 assert_eq!(*stats.by_severity.get("Low").unwrap_or(&0), 1);
572 assert_eq!(*stats.by_severity.get("Medium").unwrap_or(&0), 1);
573 assert_eq!(*stats.by_type.get("SelfReference").unwrap_or(&0), 1);
574 assert_eq!(*stats.by_type.get("Simple").unwrap_or(&0), 1);
575 assert_eq!(*stats.by_pointer_type.get("Rc").unwrap_or(&0), 1);
576 assert_eq!(*stats.by_pointer_type.get("Arc").unwrap_or(&0), 2);
577 }
578
579 #[test]
580 fn test_suggest_weak_positions() {
581 let empty_nodes = vec![];
583 let positions = suggest_weak_positions(&empty_nodes);
584 assert_eq!(positions, vec![0]); let single_node = vec![CircularReferenceNode {
588 ptr: 0x1000,
589 data_ptr: 0x2000,
590 var_name: Some("node".to_string()),
591 type_name: Some("Rc<Node>".to_string()),
592 pointer_type: SmartPointerType::Rc,
593 ref_count: 1,
594 }];
595 let positions = suggest_weak_positions(&single_node);
596 assert_eq!(positions, vec![0]);
597
598 let multiple_nodes = vec![
600 CircularReferenceNode {
601 ptr: 0x1000,
602 data_ptr: 0x2000,
603 var_name: Some("node_a".to_string()),
604 type_name: Some("Rc<Node>".to_string()),
605 pointer_type: SmartPointerType::Rc,
606 ref_count: 1,
607 },
608 CircularReferenceNode {
609 ptr: 0x2000,
610 data_ptr: 0x3000,
611 var_name: Some("node_b".to_string()),
612 type_name: Some("Rc<Node>".to_string()),
613 pointer_type: SmartPointerType::Rc,
614 ref_count: 3, },
616 CircularReferenceNode {
617 ptr: 0x3000,
618 data_ptr: 0x1000,
619 var_name: Some("node_c".to_string()),
620 type_name: Some("Rc<Node>".to_string()),
621 pointer_type: SmartPointerType::Rc,
622 ref_count: 2,
623 },
624 ];
625 let positions = suggest_weak_positions(&multiple_nodes);
626 assert_eq!(positions, vec![1]); }
628
629 #[test]
630 fn test_analyze_cycle() {
631 let mut graph = ReferenceGraph {
633 adjacency: HashMap::new(),
634 reverse_refs: HashMap::new(),
635 smart_pointers: HashMap::new(),
636 allocations: HashMap::new(),
637 };
638
639 let smart_info_a = SmartPointerInfo {
641 data_ptr: 0x2000,
642 pointer_type: SmartPointerType::Rc,
643 is_weak_reference: false,
644 clones: vec![],
645 cloned_from: None,
646 ref_count_history: vec![RefCountSnapshot {
647 strong_count: 1,
648 weak_count: 0,
649 timestamp: 0,
650 }],
651 weak_count: None,
652 is_data_owner: true,
653 is_implicitly_deallocated: false,
654 };
655
656 let smart_info_b = SmartPointerInfo {
657 data_ptr: 0x1000,
658 pointer_type: SmartPointerType::Rc,
659 is_weak_reference: false,
660 clones: vec![],
661 cloned_from: None,
662 ref_count_history: vec![RefCountSnapshot {
663 strong_count: 1,
664 weak_count: 0,
665 timestamp: 0,
666 }],
667 weak_count: None,
668 is_data_owner: true,
669 is_implicitly_deallocated: false,
670 };
671
672 let allocation_a = AllocationInfo {
673 ptr: 0x1000,
674 size: 1024,
675 var_name: Some("node_a".to_string()),
676 type_name: Some("Rc<Node>".to_string()),
677 smart_pointer_info: Some(smart_info_a.clone()),
678 scope_name: None,
679 timestamp_alloc: 0,
680 timestamp_dealloc: None,
681 thread_id: std::thread::current().id(),
682 thread_id_u64: current_thread_id_u64(),
683 borrow_count: 0,
684 stack_trace: None,
685 is_leaked: false,
686 lifetime_ms: None,
687 module_path: None,
688 borrow_info: None,
689 clone_info: None,
690 ownership_history_available: false,
691 memory_layout: None,
692 generic_info: None,
693 dynamic_type_info: None,
694 runtime_state: None,
695 stack_allocation: None,
696 temporary_object: None,
697 fragmentation_analysis: None,
698 generic_instantiation: None,
699 type_relationships: None,
700 type_usage: None,
701 function_call_tracking: None,
702 lifecycle_tracking: None,
703 access_tracking: None,
704 drop_chain_analysis: None,
705 stack_ptr: None,
706 task_id: None,
707 generation_id: 0,
708 };
709
710 let allocation_b = AllocationInfo {
711 ptr: 0x2000,
712 size: 2048,
713 var_name: Some("node_b".to_string()),
714 type_name: Some("Rc<Node>".to_string()),
715 smart_pointer_info: Some(smart_info_b.clone()),
716 scope_name: None,
717 timestamp_alloc: 0,
718 timestamp_dealloc: None,
719 thread_id: std::thread::current().id(),
720 thread_id_u64: current_thread_id_u64(),
721 borrow_count: 0,
722 stack_trace: None,
723 is_leaked: false,
724 lifetime_ms: None,
725 module_path: None,
726 borrow_info: None,
727 clone_info: None,
728 ownership_history_available: false,
729 memory_layout: None,
730 generic_info: None,
731 dynamic_type_info: None,
732 runtime_state: None,
733 stack_allocation: None,
734 temporary_object: None,
735 fragmentation_analysis: None,
736 generic_instantiation: None,
737 type_relationships: None,
738 type_usage: None,
739 function_call_tracking: None,
740 lifecycle_tracking: None,
741 access_tracking: None,
742 drop_chain_analysis: None,
743 stack_ptr: None,
744 task_id: None,
745 generation_id: 0,
746 };
747
748 graph.smart_pointers.insert(0x1000, smart_info_a);
749 graph.smart_pointers.insert(0x2000, smart_info_b);
750 graph.allocations.insert(0x1000, allocation_a);
751 graph.allocations.insert(0x2000, allocation_b);
752
753 let cycle_path = vec![0x1000, 0x2000];
755 let circular_ref = analyze_cycle(&cycle_path, &graph);
756
757 assert_eq!(circular_ref.cycle_path.len(), 2);
758 assert_eq!(circular_ref.estimated_leaked_memory, 3072); assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
760 assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); assert!(!circular_ref.suggested_weak_positions.is_empty());
762 }
763
764 #[test]
765 fn test_reference_graph_creation() {
766 let empty_allocations = vec![];
768 let graph = ReferenceGraph::new(&empty_allocations);
769
770 assert!(graph.adjacency.is_empty());
771 assert!(graph.reverse_refs.is_empty());
772 assert!(graph.smart_pointers.is_empty());
773 assert!(graph.allocations.is_empty());
774
775 let allocations_without_smart = vec![AllocationInfo {
777 ptr: 0x1000,
778 size: 1024,
779 scope_name: None,
780 timestamp_alloc: 0,
781 timestamp_dealloc: None,
782 thread_id: std::thread::current().id(),
783 thread_id_u64: {
784 use std::hash::{Hash, Hasher};
785 let mut hasher = std::collections::hash_map::DefaultHasher::new();
786 std::thread::current().id().hash(&mut hasher);
787 hasher.finish()
788 },
789 borrow_count: 0,
790 stack_trace: None,
791 is_leaked: false,
792 lifetime_ms: None,
793 var_name: None,
794 type_name: None,
795 borrow_info: None,
796 clone_info: None,
797 ownership_history_available: false,
798 smart_pointer_info: None,
799 memory_layout: None,
800 generic_info: None,
801 dynamic_type_info: None,
802 runtime_state: None,
803 stack_allocation: None,
804 temporary_object: None,
805 fragmentation_analysis: None,
806 generic_instantiation: None,
807 type_relationships: None,
808 type_usage: None,
809 function_call_tracking: None,
810 lifecycle_tracking: None,
811 access_tracking: None,
812 drop_chain_analysis: None,
813 module_path: None,
814 stack_ptr: None,
815 task_id: None,
816 generation_id: 0,
817 }];
818 let graph = ReferenceGraph::new(&allocations_without_smart);
819
820 assert!(graph.adjacency.is_empty());
821 assert!(graph.reverse_refs.is_empty());
822 assert!(graph.smart_pointers.is_empty());
823 assert!(graph.allocations.is_empty());
824
825 let smart_info = SmartPointerInfo {
827 data_ptr: 0x2000,
828 pointer_type: SmartPointerType::Rc,
829 is_weak_reference: false,
830 clones: vec![],
831 cloned_from: None,
832 ref_count_history: vec![RefCountSnapshot {
833 strong_count: 1,
834 weak_count: 0,
835 timestamp: 0,
836 }],
837 weak_count: None,
838 is_data_owner: true,
839 is_implicitly_deallocated: false,
840 };
841
842 let allocations_with_smart = vec![AllocationInfo {
843 ptr: 0x1000,
844 size: 1024,
845 var_name: None,
846 type_name: None,
847 scope_name: None,
848 timestamp_alloc: 0,
849 timestamp_dealloc: None,
850 thread_id: thread::current().id(),
851 thread_id_u64: {
852 use std::hash::{Hash, Hasher};
853 let mut hasher = std::collections::hash_map::DefaultHasher::new();
854 thread::current().id().hash(&mut hasher);
855 hasher.finish()
856 },
857 borrow_count: 0,
858 stack_trace: None,
859 is_leaked: false,
860 lifetime_ms: None,
861 borrow_info: None,
862 clone_info: None,
863 ownership_history_available: false,
864 smart_pointer_info: Some(smart_info.clone()),
865 memory_layout: None,
866 generic_info: None,
867 dynamic_type_info: None,
868 runtime_state: None,
869 stack_allocation: None,
870 temporary_object: None,
871 fragmentation_analysis: None,
872 generic_instantiation: None,
873 type_relationships: None,
874 type_usage: None,
875 function_call_tracking: None,
876 lifecycle_tracking: None,
877 access_tracking: None,
878 drop_chain_analysis: None,
879 module_path: None,
880 stack_ptr: None,
881 task_id: None,
882 generation_id: 0,
883 }];
884
885 let graph = ReferenceGraph::new(&allocations_with_smart);
886
887 assert!(!graph.adjacency.is_empty());
888 assert!(!graph.reverse_refs.is_empty());
889 assert!(!graph.smart_pointers.is_empty());
890 assert!(!graph.allocations.is_empty());
891
892 assert!(graph.adjacency.contains_key(&0x1000));
893 assert!(graph.reverse_refs.contains_key(&0x2000));
894 assert!(graph.smart_pointers.contains_key(&0x1000));
895 assert!(graph.allocations.contains_key(&0x1000));
896 }
897
898 #[test]
899 fn test_reference_graph_with_weak_references() {
900 let weak_smart_info = SmartPointerInfo {
902 data_ptr: 0x2000,
903 pointer_type: SmartPointerType::Rc,
904 is_weak_reference: true, clones: vec![],
906 cloned_from: None,
907 ref_count_history: vec![RefCountSnapshot {
908 strong_count: 1,
909 weak_count: 1,
910 timestamp: 0,
911 }],
912 weak_count: Some(1),
913 is_data_owner: false,
914 is_implicitly_deallocated: false,
915 };
916
917 let allocations_with_weak = vec![AllocationInfo {
918 ptr: 0x1000,
919 size: 1024,
920 var_name: None,
921 type_name: None,
922 scope_name: None,
923 timestamp_alloc: 0,
924 timestamp_dealloc: None,
925 thread_id: thread::current().id(),
926 thread_id_u64: {
927 use std::hash::{Hash, Hasher};
928 let mut hasher = std::collections::hash_map::DefaultHasher::new();
929 thread::current().id().hash(&mut hasher);
930 hasher.finish()
931 },
932 borrow_count: 0,
933 stack_trace: None,
934 is_leaked: false,
935 lifetime_ms: None,
936 borrow_info: None,
937 clone_info: None,
938 ownership_history_available: false,
939 smart_pointer_info: Some(weak_smart_info),
940 memory_layout: None,
941 generic_info: None,
942 dynamic_type_info: None,
943 runtime_state: None,
944 stack_allocation: None,
945 temporary_object: None,
946 fragmentation_analysis: None,
947 generic_instantiation: None,
948 type_relationships: None,
949 type_usage: None,
950 function_call_tracking: None,
951 lifecycle_tracking: None,
952 access_tracking: None,
953 drop_chain_analysis: None,
954 module_path: None,
955 stack_ptr: None,
956 task_id: None,
957 generation_id: 0,
958 }];
959
960 let graph = ReferenceGraph::new(&allocations_with_weak);
961
962 assert!(graph.adjacency.is_empty());
964 assert!(graph.reverse_refs.is_empty());
965 assert!(graph.smart_pointers.is_empty());
966 assert!(graph.allocations.is_empty());
967 }
968
969 #[test]
970 fn test_detect_circular_references_empty() {
971 let empty_allocations = vec![];
972 let analysis = detect_circular_references(&empty_allocations);
973
974 assert_eq!(analysis.circular_references.len(), 0);
975 assert_eq!(analysis.total_smart_pointers, 0);
976 assert_eq!(analysis.pointers_in_cycles, 0);
977 assert_eq!(analysis.total_leaked_memory, 0);
978
979 assert_eq!(analysis.statistics.average_cycle_length, 0.0);
981 assert_eq!(analysis.statistics.largest_cycle_size, 0);
982 }
983
984 #[test]
985 fn test_circular_reference_analysis_structure() {
986 let analysis = CircularReferenceAnalysis {
987 circular_references: vec![],
988 total_smart_pointers: 10,
989 pointers_in_cycles: 5,
990 total_leaked_memory: 10240,
991 statistics: CircularReferenceStatistics {
992 by_severity: HashMap::new(),
993 by_type: HashMap::new(),
994 by_pointer_type: HashMap::new(),
995 average_cycle_length: 0.0,
996 largest_cycle_size: 0,
997 },
998 };
999
1000 assert_eq!(analysis.total_smart_pointers, 10);
1001 assert_eq!(analysis.pointers_in_cycles, 5);
1002 assert_eq!(analysis.total_leaked_memory, 10240);
1003 }
1004
1005 #[test]
1006 fn test_circular_reference_severity_determination() {
1007 let memory_size = 1024; let low_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!(low_severity, CircularReferenceSeverity::Low);
1022
1023 let memory_size = 5000; let medium_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!(medium_severity, CircularReferenceSeverity::Medium);
1038
1039 let memory_size = 70000; let high_severity = if memory_size > 1024 * 1024 {
1042 CircularReferenceSeverity::Critical
1044 } else if memory_size > 64 * 1024 {
1045 CircularReferenceSeverity::High
1047 } else if memory_size > 4 * 1024 {
1048 CircularReferenceSeverity::Medium
1050 } else {
1051 CircularReferenceSeverity::Low
1052 };
1053 assert_eq!(high_severity, CircularReferenceSeverity::High);
1054
1055 let memory_size = 2000000; let critical_severity = if memory_size > 1024 * 1024 {
1058 CircularReferenceSeverity::Critical
1060 } else if memory_size > 64 * 1024 {
1061 CircularReferenceSeverity::High
1063 } else if memory_size > 4 * 1024 {
1064 CircularReferenceSeverity::Medium
1066 } else {
1067 CircularReferenceSeverity::Low
1068 };
1069 assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
1070 }
1071
1072 #[test]
1073 fn test_circular_reference_type_determination() {
1074 let cycle_length = 1;
1076 let self_ref_type = if cycle_length == 1 {
1077 CircularReferenceType::SelfReference
1078 } else if cycle_length == 2 {
1079 CircularReferenceType::Simple
1080 } else {
1081 CircularReferenceType::Complex
1082 };
1083 assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
1084
1085 let cycle_length = 2;
1087 let simple_type = if cycle_length == 1 {
1088 CircularReferenceType::SelfReference
1089 } else if cycle_length == 2 {
1090 CircularReferenceType::Simple
1091 } else {
1092 CircularReferenceType::Complex
1093 };
1094 assert_eq!(simple_type, CircularReferenceType::Simple);
1095
1096 let cycle_length = 5;
1098 let complex_type = if cycle_length == 1 {
1099 CircularReferenceType::SelfReference
1100 } else if cycle_length == 2 {
1101 CircularReferenceType::Simple
1102 } else {
1103 CircularReferenceType::Complex
1104 };
1105 assert_eq!(complex_type, CircularReferenceType::Complex);
1106 }
1107}