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 };
708
709 let allocation_b = AllocationInfo {
710 ptr: 0x2000,
711 size: 2048,
712 var_name: Some("node_b".to_string()),
713 type_name: Some("Rc<Node>".to_string()),
714 smart_pointer_info: Some(smart_info_b.clone()),
715 scope_name: None,
716 timestamp_alloc: 0,
717 timestamp_dealloc: None,
718 thread_id: std::thread::current().id(),
719 thread_id_u64: current_thread_id_u64(),
720 borrow_count: 0,
721 stack_trace: None,
722 is_leaked: false,
723 lifetime_ms: None,
724 module_path: None,
725 borrow_info: None,
726 clone_info: None,
727 ownership_history_available: false,
728 memory_layout: None,
729 generic_info: None,
730 dynamic_type_info: None,
731 runtime_state: None,
732 stack_allocation: None,
733 temporary_object: None,
734 fragmentation_analysis: None,
735 generic_instantiation: None,
736 type_relationships: None,
737 type_usage: None,
738 function_call_tracking: None,
739 lifecycle_tracking: None,
740 access_tracking: None,
741 drop_chain_analysis: None,
742 stack_ptr: None,
743 task_id: None,
744 };
745
746 graph.smart_pointers.insert(0x1000, smart_info_a);
747 graph.smart_pointers.insert(0x2000, smart_info_b);
748 graph.allocations.insert(0x1000, allocation_a);
749 graph.allocations.insert(0x2000, allocation_b);
750
751 let cycle_path = vec![0x1000, 0x2000];
753 let circular_ref = analyze_cycle(&cycle_path, &graph);
754
755 assert_eq!(circular_ref.cycle_path.len(), 2);
756 assert_eq!(circular_ref.estimated_leaked_memory, 3072); assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
758 assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); assert!(!circular_ref.suggested_weak_positions.is_empty());
760 }
761
762 #[test]
763 fn test_reference_graph_creation() {
764 let empty_allocations = vec![];
766 let graph = ReferenceGraph::new(&empty_allocations);
767
768 assert!(graph.adjacency.is_empty());
769 assert!(graph.reverse_refs.is_empty());
770 assert!(graph.smart_pointers.is_empty());
771 assert!(graph.allocations.is_empty());
772
773 let allocations_without_smart = vec![AllocationInfo {
775 ptr: 0x1000,
776 size: 1024,
777 scope_name: None,
778 timestamp_alloc: 0,
779 timestamp_dealloc: None,
780 thread_id: std::thread::current().id(),
781 thread_id_u64: {
782 use std::hash::{Hash, Hasher};
783 let mut hasher = std::collections::hash_map::DefaultHasher::new();
784 std::thread::current().id().hash(&mut hasher);
785 hasher.finish()
786 },
787 borrow_count: 0,
788 stack_trace: None,
789 is_leaked: false,
790 lifetime_ms: None,
791 var_name: None,
792 type_name: None,
793 borrow_info: None,
794 clone_info: None,
795 ownership_history_available: false,
796 smart_pointer_info: None,
797 memory_layout: None,
798 generic_info: None,
799 dynamic_type_info: None,
800 runtime_state: None,
801 stack_allocation: None,
802 temporary_object: None,
803 fragmentation_analysis: None,
804 generic_instantiation: None,
805 type_relationships: None,
806 type_usage: None,
807 function_call_tracking: None,
808 lifecycle_tracking: None,
809 access_tracking: None,
810 drop_chain_analysis: None,
811 module_path: None,
812 stack_ptr: None,
813 task_id: None,
814 }];
815 let graph = ReferenceGraph::new(&allocations_without_smart);
816
817 assert!(graph.adjacency.is_empty());
818 assert!(graph.reverse_refs.is_empty());
819 assert!(graph.smart_pointers.is_empty());
820 assert!(graph.allocations.is_empty());
821
822 let smart_info = SmartPointerInfo {
824 data_ptr: 0x2000,
825 pointer_type: SmartPointerType::Rc,
826 is_weak_reference: false,
827 clones: vec![],
828 cloned_from: None,
829 ref_count_history: vec![RefCountSnapshot {
830 strong_count: 1,
831 weak_count: 0,
832 timestamp: 0,
833 }],
834 weak_count: None,
835 is_data_owner: true,
836 is_implicitly_deallocated: false,
837 };
838
839 let allocations_with_smart = vec![AllocationInfo {
840 ptr: 0x1000,
841 size: 1024,
842 var_name: None,
843 type_name: None,
844 scope_name: None,
845 timestamp_alloc: 0,
846 timestamp_dealloc: None,
847 thread_id: thread::current().id(),
848 thread_id_u64: {
849 use std::hash::{Hash, Hasher};
850 let mut hasher = std::collections::hash_map::DefaultHasher::new();
851 thread::current().id().hash(&mut hasher);
852 hasher.finish()
853 },
854 borrow_count: 0,
855 stack_trace: None,
856 is_leaked: false,
857 lifetime_ms: None,
858 borrow_info: None,
859 clone_info: None,
860 ownership_history_available: false,
861 smart_pointer_info: Some(smart_info.clone()),
862 memory_layout: None,
863 generic_info: None,
864 dynamic_type_info: None,
865 runtime_state: None,
866 stack_allocation: None,
867 temporary_object: None,
868 fragmentation_analysis: None,
869 generic_instantiation: None,
870 type_relationships: None,
871 type_usage: None,
872 function_call_tracking: None,
873 lifecycle_tracking: None,
874 access_tracking: None,
875 drop_chain_analysis: None,
876 module_path: None,
877 stack_ptr: None,
878 task_id: None,
879 }];
880
881 let graph = ReferenceGraph::new(&allocations_with_smart);
882
883 assert!(!graph.adjacency.is_empty());
884 assert!(!graph.reverse_refs.is_empty());
885 assert!(!graph.smart_pointers.is_empty());
886 assert!(!graph.allocations.is_empty());
887
888 assert!(graph.adjacency.contains_key(&0x1000));
889 assert!(graph.reverse_refs.contains_key(&0x2000));
890 assert!(graph.smart_pointers.contains_key(&0x1000));
891 assert!(graph.allocations.contains_key(&0x1000));
892 }
893
894 #[test]
895 fn test_reference_graph_with_weak_references() {
896 let weak_smart_info = SmartPointerInfo {
898 data_ptr: 0x2000,
899 pointer_type: SmartPointerType::Rc,
900 is_weak_reference: true, clones: vec![],
902 cloned_from: None,
903 ref_count_history: vec![RefCountSnapshot {
904 strong_count: 1,
905 weak_count: 1,
906 timestamp: 0,
907 }],
908 weak_count: Some(1),
909 is_data_owner: false,
910 is_implicitly_deallocated: false,
911 };
912
913 let allocations_with_weak = vec![AllocationInfo {
914 ptr: 0x1000,
915 size: 1024,
916 var_name: None,
917 type_name: None,
918 scope_name: None,
919 timestamp_alloc: 0,
920 timestamp_dealloc: None,
921 thread_id: thread::current().id(),
922 thread_id_u64: {
923 use std::hash::{Hash, Hasher};
924 let mut hasher = std::collections::hash_map::DefaultHasher::new();
925 thread::current().id().hash(&mut hasher);
926 hasher.finish()
927 },
928 borrow_count: 0,
929 stack_trace: None,
930 is_leaked: false,
931 lifetime_ms: None,
932 borrow_info: None,
933 clone_info: None,
934 ownership_history_available: false,
935 smart_pointer_info: Some(weak_smart_info),
936 memory_layout: None,
937 generic_info: None,
938 dynamic_type_info: None,
939 runtime_state: None,
940 stack_allocation: None,
941 temporary_object: None,
942 fragmentation_analysis: None,
943 generic_instantiation: None,
944 type_relationships: None,
945 type_usage: None,
946 function_call_tracking: None,
947 lifecycle_tracking: None,
948 access_tracking: None,
949 drop_chain_analysis: None,
950 module_path: None,
951 stack_ptr: None,
952 task_id: None,
953 }];
954
955 let graph = ReferenceGraph::new(&allocations_with_weak);
956
957 assert!(graph.adjacency.is_empty());
959 assert!(graph.reverse_refs.is_empty());
960 assert!(graph.smart_pointers.is_empty());
961 assert!(graph.allocations.is_empty());
962 }
963
964 #[test]
965 fn test_detect_circular_references_empty() {
966 let empty_allocations = vec![];
967 let analysis = detect_circular_references(&empty_allocations);
968
969 assert_eq!(analysis.circular_references.len(), 0);
970 assert_eq!(analysis.total_smart_pointers, 0);
971 assert_eq!(analysis.pointers_in_cycles, 0);
972 assert_eq!(analysis.total_leaked_memory, 0);
973
974 assert_eq!(analysis.statistics.average_cycle_length, 0.0);
976 assert_eq!(analysis.statistics.largest_cycle_size, 0);
977 }
978
979 #[test]
980 fn test_circular_reference_analysis_structure() {
981 let analysis = CircularReferenceAnalysis {
982 circular_references: vec![],
983 total_smart_pointers: 10,
984 pointers_in_cycles: 5,
985 total_leaked_memory: 10240,
986 statistics: CircularReferenceStatistics {
987 by_severity: HashMap::new(),
988 by_type: HashMap::new(),
989 by_pointer_type: HashMap::new(),
990 average_cycle_length: 0.0,
991 largest_cycle_size: 0,
992 },
993 };
994
995 assert_eq!(analysis.total_smart_pointers, 10);
996 assert_eq!(analysis.pointers_in_cycles, 5);
997 assert_eq!(analysis.total_leaked_memory, 10240);
998 }
999
1000 #[test]
1001 fn test_circular_reference_severity_determination() {
1002 let memory_size = 1024; let low_severity = if memory_size > 1024 * 1024 {
1005 CircularReferenceSeverity::Critical
1007 } else if memory_size > 64 * 1024 {
1008 CircularReferenceSeverity::High
1010 } else if memory_size > 4 * 1024 {
1011 CircularReferenceSeverity::Medium
1013 } else {
1014 CircularReferenceSeverity::Low
1015 };
1016 assert_eq!(low_severity, CircularReferenceSeverity::Low);
1017
1018 let memory_size = 5000; let medium_severity = if memory_size > 1024 * 1024 {
1021 CircularReferenceSeverity::Critical
1023 } else if memory_size > 64 * 1024 {
1024 CircularReferenceSeverity::High
1026 } else if memory_size > 4 * 1024 {
1027 CircularReferenceSeverity::Medium
1029 } else {
1030 CircularReferenceSeverity::Low
1031 };
1032 assert_eq!(medium_severity, CircularReferenceSeverity::Medium);
1033
1034 let memory_size = 70000; let high_severity = if memory_size > 1024 * 1024 {
1037 CircularReferenceSeverity::Critical
1039 } else if memory_size > 64 * 1024 {
1040 CircularReferenceSeverity::High
1042 } else if memory_size > 4 * 1024 {
1043 CircularReferenceSeverity::Medium
1045 } else {
1046 CircularReferenceSeverity::Low
1047 };
1048 assert_eq!(high_severity, CircularReferenceSeverity::High);
1049
1050 let memory_size = 2000000; let critical_severity = if memory_size > 1024 * 1024 {
1053 CircularReferenceSeverity::Critical
1055 } else if memory_size > 64 * 1024 {
1056 CircularReferenceSeverity::High
1058 } else if memory_size > 4 * 1024 {
1059 CircularReferenceSeverity::Medium
1061 } else {
1062 CircularReferenceSeverity::Low
1063 };
1064 assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
1065 }
1066
1067 #[test]
1068 fn test_circular_reference_type_determination() {
1069 let cycle_length = 1;
1071 let self_ref_type = if cycle_length == 1 {
1072 CircularReferenceType::SelfReference
1073 } else if cycle_length == 2 {
1074 CircularReferenceType::Simple
1075 } else {
1076 CircularReferenceType::Complex
1077 };
1078 assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
1079
1080 let cycle_length = 2;
1082 let simple_type = if cycle_length == 1 {
1083 CircularReferenceType::SelfReference
1084 } else if cycle_length == 2 {
1085 CircularReferenceType::Simple
1086 } else {
1087 CircularReferenceType::Complex
1088 };
1089 assert_eq!(simple_type, CircularReferenceType::Simple);
1090
1091 let cycle_length = 5;
1093 let complex_type = if cycle_length == 1 {
1094 CircularReferenceType::SelfReference
1095 } else if cycle_length == 2 {
1096 CircularReferenceType::Simple
1097 } else {
1098 CircularReferenceType::Complex
1099 };
1100 assert_eq!(complex_type, CircularReferenceType::Complex);
1101 }
1102}