memscope_rs/analysis/
circular_reference.rs

1//! Circular reference detection for smart pointers
2//!
3//! This module provides functionality to detect circular references in Rc/Arc
4//! smart pointers that can lead to memory leaks.
5
6use crate::core::types::{AllocationInfo, SmartPointerInfo, SmartPointerType};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10/// Represents a detected circular reference
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct CircularReference {
13    /// The cycle path showing the circular reference chain
14    pub cycle_path: Vec<CircularReferenceNode>,
15
16    /// Suggested positions where Weak references should be used to break the cycle
17    pub suggested_weak_positions: Vec<usize>,
18
19    /// Estimated memory that would be leaked due to this cycle
20    pub estimated_leaked_memory: usize,
21
22    /// Severity level of this circular reference
23    pub severity: CircularReferenceSeverity,
24
25    /// Type of circular reference detected
26    pub cycle_type: CircularReferenceType,
27}
28
29/// Node in a circular reference path
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct CircularReferenceNode {
32    /// Pointer address of this node
33    pub ptr: usize,
34
35    /// Data pointer this node points to
36    pub data_ptr: usize,
37
38    /// Variable name if available
39    pub var_name: Option<String>,
40
41    /// Type name of the smart pointer
42    pub type_name: Option<String>,
43
44    /// Smart pointer type
45    pub pointer_type: SmartPointerType,
46
47    /// Current reference count
48    pub ref_count: usize,
49}
50
51/// Severity levels for circular references
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub enum CircularReferenceSeverity {
54    /// Low severity - small memory impact
55    Low,
56    /// Medium severity - moderate memory impact
57    Medium,
58    /// High severity - significant memory impact
59    High,
60    /// Critical severity - large memory impact or complex cycle
61    Critical,
62}
63
64/// Types of circular references
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub enum CircularReferenceType {
67    /// Simple two-node cycle (A -> B -> A)
68    Simple,
69    /// Complex multi-node cycle
70    Complex,
71    /// Self-referencing cycle (A -> A)
72    SelfReference,
73    /// Nested cycles (cycles within cycles)
74    Nested,
75}
76
77/// Analysis result for circular reference detection
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct CircularReferenceAnalysis {
80    /// All detected circular references
81    pub circular_references: Vec<CircularReference>,
82
83    /// Total number of smart pointers analyzed
84    pub total_smart_pointers: usize,
85
86    /// Number of smart pointers involved in cycles
87    pub pointers_in_cycles: usize,
88
89    /// Total estimated leaked memory
90    pub total_leaked_memory: usize,
91
92    /// Analysis statistics
93    pub statistics: CircularReferenceStatistics,
94}
95
96/// Statistics for circular reference analysis
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CircularReferenceStatistics {
99    /// Count by severity
100    pub by_severity: HashMap<String, usize>,
101
102    /// Count by type
103    pub by_type: HashMap<String, usize>,
104
105    /// Count by smart pointer type
106    pub by_pointer_type: HashMap<String, usize>,
107
108    /// Average cycle length
109    pub average_cycle_length: f64,
110
111    /// Largest cycle size
112    pub largest_cycle_size: usize,
113}
114
115/// Graph representation for circular reference detection
116#[derive(Debug)]
117struct ReferenceGraph {
118    /// Adjacency list: ptr -> list of data_ptrs it references
119    adjacency: HashMap<usize, Vec<usize>>,
120
121    /// Reverse mapping: data_ptr -> list of ptrs that reference it
122    reverse_refs: HashMap<usize, Vec<usize>>,
123
124    /// Smart pointer information by ptr
125    smart_pointers: HashMap<usize, SmartPointerInfo>,
126
127    /// Allocation information by ptr
128    allocations: HashMap<usize, AllocationInfo>,
129}
130
131impl ReferenceGraph {
132    /// Create a new reference graph from allocations
133    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        // Build the graph from smart pointer allocations
142        for allocation in allocations {
143            if let Some(ref smart_info) = allocation.smart_pointer_info {
144                // Skip weak references for cycle detection (they don't create strong cycles)
145                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                // Add edge from this pointer to its data
155                graph.adjacency.entry(allocation.ptr).or_default();
156
157                // Add reverse reference from data to this pointer
158                graph
159                    .reverse_refs
160                    .entry(smart_info.data_ptr)
161                    .or_default()
162                    .push(allocation.ptr);
163
164                // Add edges to cloned pointers (they share the same data)
165                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                // Add edge from cloned_from if it exists
174                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    /// Detect all circular references in the graph
188    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    /// Depth-first search to detect cycles
204    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 !visited.contains(&neighbor) {
219                    self.dfs_detect_cycles(neighbor, visited, rec_stack, path, cycles);
220                } else if rec_stack.contains(&neighbor) {
221                    // Found a cycle - extract the cycle path
222                    if let Some(cycle_start) = path.iter().position(|&x| x == neighbor) {
223                        let cycle = path[cycle_start..].to_vec();
224                        cycles.push(cycle);
225                    }
226                }
227            }
228        }
229
230        path.pop();
231        rec_stack.remove(&ptr);
232    }
233}
234
235/// Detect circular references in smart pointer allocations
236pub fn detect_circular_references(allocations: &[AllocationInfo]) -> CircularReferenceAnalysis {
237    let graph = ReferenceGraph::new(allocations);
238    let raw_cycles = graph.detect_cycles();
239
240    let mut circular_references = Vec::new();
241    let mut total_leaked_memory = 0;
242    let mut pointers_in_cycles = HashSet::new();
243
244    // Process each detected cycle
245    for cycle_path in raw_cycles {
246        if cycle_path.len() < 2 {
247            continue; // Skip trivial cycles
248        }
249
250        let circular_ref = analyze_cycle(&cycle_path, &graph);
251        total_leaked_memory += circular_ref.estimated_leaked_memory;
252
253        for node in &circular_ref.cycle_path {
254            pointers_in_cycles.insert(node.ptr);
255        }
256
257        circular_references.push(circular_ref);
258    }
259
260    // Generate statistics
261    let statistics = generate_statistics(&circular_references);
262
263    CircularReferenceAnalysis {
264        circular_references,
265        total_smart_pointers: graph.smart_pointers.len(),
266        pointers_in_cycles: pointers_in_cycles.len(),
267        total_leaked_memory,
268        statistics,
269    }
270}
271
272/// Analyze a single cycle to create a CircularReference
273fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
274    let mut nodes = Vec::new();
275    let mut total_memory = 0;
276
277    // Build cycle nodes
278    for &ptr in cycle_path {
279        if let (Some(smart_info), Some(allocation)) =
280            (graph.smart_pointers.get(&ptr), graph.allocations.get(&ptr))
281        {
282            let node = CircularReferenceNode {
283                ptr,
284                data_ptr: smart_info.data_ptr,
285                var_name: allocation.var_name.clone(),
286                type_name: allocation.type_name.clone(),
287                pointer_type: smart_info.pointer_type.clone(),
288                ref_count: smart_info
289                    .latest_ref_counts()
290                    .map(|snapshot| snapshot.strong_count)
291                    .unwrap_or(1),
292            };
293
294            total_memory += allocation.size;
295            nodes.push(node);
296        }
297    }
298
299    // Determine cycle type
300    let cycle_type = if cycle_path.len() == 1 {
301        CircularReferenceType::SelfReference
302    } else if cycle_path.len() == 2 {
303        CircularReferenceType::Simple
304    } else {
305        CircularReferenceType::Complex
306    };
307
308    // Determine severity based on memory impact and cycle complexity
309    let severity = if total_memory > 1024 * 1024 {
310        // > 1MB
311        CircularReferenceSeverity::Critical
312    } else if total_memory > 64 * 1024 {
313        // > 64KB
314        CircularReferenceSeverity::High
315    } else if total_memory > 4 * 1024 {
316        // > 4KB
317        CircularReferenceSeverity::Medium
318    } else {
319        CircularReferenceSeverity::Low
320    };
321
322    // Suggest weak reference positions (break the cycle at the longest-lived reference)
323    let suggested_weak_positions = suggest_weak_positions(&nodes);
324
325    CircularReference {
326        cycle_path: nodes,
327        suggested_weak_positions,
328        estimated_leaked_memory: total_memory,
329        severity,
330        cycle_type,
331    }
332}
333
334/// Suggest positions where weak references should be used to break cycles
335fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
336    // Simple heuristic: suggest breaking at the node with the highest reference count
337    // (likely to be the most shared and least critical to make weak)
338    if let Some((index, _)) = nodes
339        .iter()
340        .enumerate()
341        .max_by_key(|(_, node)| node.ref_count)
342    {
343        vec![index]
344    } else {
345        vec![0] // Fallback to first position
346    }
347}
348
349/// Generate statistics for the analysis
350fn generate_statistics(circular_references: &[CircularReference]) -> CircularReferenceStatistics {
351    let mut by_severity = HashMap::new();
352    let mut by_type = HashMap::new();
353    let mut by_pointer_type = HashMap::new();
354    let mut total_cycle_length = 0;
355    let mut largest_cycle_size = 0;
356
357    for circular_ref in circular_references {
358        // Count by severity
359        let severity_key = format!("{:?}", circular_ref.severity);
360        *by_severity.entry(severity_key).or_insert(0) += 1;
361
362        // Count by type
363        let type_key = format!("{:?}", circular_ref.cycle_type);
364        *by_type.entry(type_key).or_insert(0) += 1;
365
366        // Count by pointer type
367        for node in &circular_ref.cycle_path {
368            let pointer_type_key = format!("{:?}", node.pointer_type);
369            *by_pointer_type.entry(pointer_type_key).or_insert(0) += 1;
370        }
371
372        // Track cycle sizes
373        let cycle_length = circular_ref.cycle_path.len();
374        total_cycle_length += cycle_length;
375        largest_cycle_size = largest_cycle_size.max(cycle_length);
376    }
377
378    let average_cycle_length = if circular_references.is_empty() {
379        0.0
380    } else {
381        total_cycle_length as f64 / circular_references.len() as f64
382    };
383
384    CircularReferenceStatistics {
385        by_severity,
386        by_type,
387        by_pointer_type,
388        average_cycle_length,
389        largest_cycle_size,
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::core::types::{RefCountSnapshot, SmartPointerInfo, SmartPointerType};
397
398    #[test]
399    fn test_circular_reference_node_creation() {
400        let node = CircularReferenceNode {
401            ptr: 0x1000,
402            data_ptr: 0x2000,
403            var_name: Some("test_node".to_string()),
404            type_name: Some("Rc<RefCell<Node>>".to_string()),
405            pointer_type: SmartPointerType::Rc,
406            ref_count: 1,
407        };
408
409        assert_eq!(node.ptr, 0x1000);
410        assert_eq!(node.data_ptr, 0x2000);
411        assert_eq!(node.var_name, Some("test_node".to_string()));
412        assert_eq!(node.type_name, Some("Rc<RefCell<Node>>".to_string()));
413        assert_eq!(node.pointer_type, SmartPointerType::Rc);
414        assert_eq!(node.ref_count, 1);
415    }
416
417    #[test]
418    fn test_circular_reference_creation() {
419        let cycle_path = vec![
420            CircularReferenceNode {
421                ptr: 0x1000,
422                data_ptr: 0x2000,
423                var_name: Some("node_a".to_string()),
424                type_name: Some("Rc<RefCell<Node>>".to_string()),
425                pointer_type: SmartPointerType::Rc,
426                ref_count: 1,
427            },
428            CircularReferenceNode {
429                ptr: 0x2000,
430                data_ptr: 0x1000,
431                var_name: Some("node_b".to_string()),
432                type_name: Some("Rc<RefCell<Node>>".to_string()),
433                pointer_type: SmartPointerType::Rc,
434                ref_count: 1,
435            },
436        ];
437
438        let circular_ref = CircularReference {
439            cycle_path: cycle_path.clone(),
440            suggested_weak_positions: vec![1],
441            estimated_leaked_memory: 1024,
442            severity: CircularReferenceSeverity::Medium,
443            cycle_type: CircularReferenceType::Simple,
444        };
445
446        assert_eq!(circular_ref.cycle_path.len(), 2);
447        assert_eq!(circular_ref.suggested_weak_positions, vec![1]);
448        assert_eq!(circular_ref.estimated_leaked_memory, 1024);
449        assert_eq!(circular_ref.severity, CircularReferenceSeverity::Medium);
450        assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
451    }
452
453    #[test]
454    fn test_circular_reference_severity_variants() {
455        let severities = [
456            CircularReferenceSeverity::Low,
457            CircularReferenceSeverity::Medium,
458            CircularReferenceSeverity::High,
459            CircularReferenceSeverity::Critical,
460        ];
461
462        // Just ensure all variants can be created and compared
463        assert_eq!(severities[0], CircularReferenceSeverity::Low);
464        assert_eq!(severities[1], CircularReferenceSeverity::Medium);
465        assert_eq!(severities[2], CircularReferenceSeverity::High);
466        assert_eq!(severities[3], CircularReferenceSeverity::Critical);
467    }
468
469    #[test]
470    fn test_circular_reference_type_variants() {
471        let types = [
472            CircularReferenceType::Simple,
473            CircularReferenceType::Complex,
474            CircularReferenceType::SelfReference,
475            CircularReferenceType::Nested,
476        ];
477
478        // Just ensure all variants can be created and compared
479        assert_eq!(types[0], CircularReferenceType::Simple);
480        assert_eq!(types[1], CircularReferenceType::Complex);
481        assert_eq!(types[2], CircularReferenceType::SelfReference);
482        assert_eq!(types[3], CircularReferenceType::Nested);
483    }
484
485    #[test]
486    fn test_circular_reference_statistics_generation() {
487        // Test with empty cycles
488        let empty_cycles = vec![];
489        let stats = generate_statistics(&empty_cycles);
490
491        assert_eq!(stats.average_cycle_length, 0.0);
492        assert_eq!(stats.largest_cycle_size, 0);
493        assert!(stats.by_severity.is_empty());
494        assert!(stats.by_type.is_empty());
495        assert!(stats.by_pointer_type.is_empty());
496
497        // Test with some cycles
498        let cycles = vec![
499            CircularReference {
500                cycle_path: vec![CircularReferenceNode {
501                    ptr: 0x1000,
502                    data_ptr: 0x2000,
503                    var_name: Some("node_a".to_string()),
504                    type_name: Some("Rc<Node>".to_string()),
505                    pointer_type: SmartPointerType::Rc,
506                    ref_count: 2,
507                }],
508                suggested_weak_positions: vec![0],
509                estimated_leaked_memory: 1024,
510                severity: CircularReferenceSeverity::Low,
511                cycle_type: CircularReferenceType::SelfReference,
512            },
513            CircularReference {
514                cycle_path: vec![
515                    CircularReferenceNode {
516                        ptr: 0x2000,
517                        data_ptr: 0x3000,
518                        var_name: Some("node_b".to_string()),
519                        type_name: Some("Arc<Node>".to_string()),
520                        pointer_type: SmartPointerType::Arc,
521                        ref_count: 3,
522                    },
523                    CircularReferenceNode {
524                        ptr: 0x3000,
525                        data_ptr: 0x2000,
526                        var_name: Some("node_c".to_string()),
527                        type_name: Some("Arc<Node>".to_string()),
528                        pointer_type: SmartPointerType::Arc,
529                        ref_count: 1,
530                    },
531                ],
532                suggested_weak_positions: vec![0],
533                estimated_leaked_memory: 2048,
534                severity: CircularReferenceSeverity::Medium,
535                cycle_type: CircularReferenceType::Simple,
536            },
537        ];
538
539        let stats = generate_statistics(&cycles);
540
541        assert_eq!(stats.average_cycle_length, 1.5); // (1 + 2) / 2
542        assert_eq!(stats.largest_cycle_size, 2);
543        assert!(!stats.by_severity.is_empty());
544        assert!(!stats.by_type.is_empty());
545        assert!(!stats.by_pointer_type.is_empty());
546
547        // Check specific counts
548        assert_eq!(*stats.by_severity.get("Low").unwrap_or(&0), 1);
549        assert_eq!(*stats.by_severity.get("Medium").unwrap_or(&0), 1);
550        assert_eq!(*stats.by_type.get("SelfReference").unwrap_or(&0), 1);
551        assert_eq!(*stats.by_type.get("Simple").unwrap_or(&0), 1);
552        assert_eq!(*stats.by_pointer_type.get("Rc").unwrap_or(&0), 1);
553        assert_eq!(*stats.by_pointer_type.get("Arc").unwrap_or(&0), 2);
554    }
555
556    #[test]
557    fn test_suggest_weak_positions() {
558        // Test with empty nodes
559        let empty_nodes = vec![];
560        let positions = suggest_weak_positions(&empty_nodes);
561        assert_eq!(positions, vec![0]); // Should fallback to position 0
562
563        // Test with single node
564        let single_node = vec![CircularReferenceNode {
565            ptr: 0x1000,
566            data_ptr: 0x2000,
567            var_name: Some("node".to_string()),
568            type_name: Some("Rc<Node>".to_string()),
569            pointer_type: SmartPointerType::Rc,
570            ref_count: 1,
571        }];
572        let positions = suggest_weak_positions(&single_node);
573        assert_eq!(positions, vec![0]);
574
575        // Test with multiple nodes, different ref counts
576        let multiple_nodes = vec![
577            CircularReferenceNode {
578                ptr: 0x1000,
579                data_ptr: 0x2000,
580                var_name: Some("node_a".to_string()),
581                type_name: Some("Rc<Node>".to_string()),
582                pointer_type: SmartPointerType::Rc,
583                ref_count: 1,
584            },
585            CircularReferenceNode {
586                ptr: 0x2000,
587                data_ptr: 0x3000,
588                var_name: Some("node_b".to_string()),
589                type_name: Some("Rc<Node>".to_string()),
590                pointer_type: SmartPointerType::Rc,
591                ref_count: 3, // Highest ref count
592            },
593            CircularReferenceNode {
594                ptr: 0x3000,
595                data_ptr: 0x1000,
596                var_name: Some("node_c".to_string()),
597                type_name: Some("Rc<Node>".to_string()),
598                pointer_type: SmartPointerType::Rc,
599                ref_count: 2,
600            },
601        ];
602        let positions = suggest_weak_positions(&multiple_nodes);
603        assert_eq!(positions, vec![1]); // Should suggest position with highest ref count
604    }
605
606    #[test]
607    fn test_analyze_cycle() {
608        // Create a mock graph for testing
609        let mut graph = ReferenceGraph {
610            adjacency: HashMap::new(),
611            reverse_refs: HashMap::new(),
612            smart_pointers: HashMap::new(),
613            allocations: HashMap::new(),
614        };
615
616        // Add mock data to the graph
617        let smart_info_a = SmartPointerInfo {
618            data_ptr: 0x2000,
619            pointer_type: SmartPointerType::Rc,
620            is_weak_reference: false,
621            clones: vec![],
622            cloned_from: None,
623            ref_count_history: vec![RefCountSnapshot {
624                strong_count: 1,
625                weak_count: 0,
626                timestamp: 0,
627            }],
628            weak_count: None,
629            is_data_owner: true,
630            is_implicitly_deallocated: false,
631        };
632
633        let smart_info_b = SmartPointerInfo {
634            data_ptr: 0x1000,
635            pointer_type: SmartPointerType::Rc,
636            is_weak_reference: false,
637            clones: vec![],
638            cloned_from: None,
639            ref_count_history: vec![RefCountSnapshot {
640                strong_count: 1,
641                weak_count: 0,
642                timestamp: 0,
643            }],
644            weak_count: None,
645            is_data_owner: true,
646            is_implicitly_deallocated: false,
647        };
648
649        let allocation_a = AllocationInfo {
650            ptr: 0x1000,
651            size: 1024,
652            var_name: Some("node_a".to_string()),
653            type_name: Some("Rc<Node>".to_string()),
654            smart_pointer_info: Some(smart_info_a.clone()),
655            scope_name: None,
656            timestamp_alloc: 0,
657            timestamp_dealloc: None,
658            thread_id: "main".to_string(),
659            borrow_count: 0,
660            stack_trace: None,
661            is_leaked: false,
662            lifetime_ms: None,
663            borrow_info: None,
664            clone_info: None,
665            ownership_history_available: false,
666            memory_layout: None,
667            generic_info: None,
668            dynamic_type_info: None,
669            runtime_state: None,
670            stack_allocation: None,
671            temporary_object: None,
672            fragmentation_analysis: None,
673            generic_instantiation: None,
674            type_relationships: None,
675            type_usage: None,
676            function_call_tracking: None,
677            lifecycle_tracking: None,
678            access_tracking: None,
679            drop_chain_analysis: None,
680        };
681
682        let allocation_b = AllocationInfo {
683            ptr: 0x2000,
684            size: 2048,
685            var_name: Some("node_b".to_string()),
686            type_name: Some("Rc<Node>".to_string()),
687            smart_pointer_info: Some(smart_info_b.clone()),
688            scope_name: None,
689            timestamp_alloc: 0,
690            timestamp_dealloc: None,
691            thread_id: "main".to_string(),
692            borrow_count: 0,
693            stack_trace: None,
694            is_leaked: false,
695            lifetime_ms: None,
696            borrow_info: None,
697            clone_info: None,
698            ownership_history_available: false,
699            memory_layout: None,
700            generic_info: None,
701            dynamic_type_info: None,
702            runtime_state: None,
703            stack_allocation: None,
704            temporary_object: None,
705            fragmentation_analysis: None,
706            generic_instantiation: None,
707            type_relationships: None,
708            type_usage: None,
709            function_call_tracking: None,
710            lifecycle_tracking: None,
711            access_tracking: None,
712            drop_chain_analysis: None,
713        };
714
715        graph.smart_pointers.insert(0x1000, smart_info_a);
716        graph.smart_pointers.insert(0x2000, smart_info_b);
717        graph.allocations.insert(0x1000, allocation_a);
718        graph.allocations.insert(0x2000, allocation_b);
719
720        // Test analyzing a simple cycle
721        let cycle_path = vec![0x1000, 0x2000];
722        let circular_ref = analyze_cycle(&cycle_path, &graph);
723
724        assert_eq!(circular_ref.cycle_path.len(), 2);
725        assert_eq!(circular_ref.estimated_leaked_memory, 3072); // 1024 + 2048
726        assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
727        assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); // 3072 bytes < 4KB threshold
728        assert!(!circular_ref.suggested_weak_positions.is_empty());
729    }
730
731    #[test]
732    fn test_reference_graph_creation() {
733        // Test with empty allocations
734        let empty_allocations = vec![];
735        let graph = ReferenceGraph::new(&empty_allocations);
736
737        assert!(graph.adjacency.is_empty());
738        assert!(graph.reverse_refs.is_empty());
739        assert!(graph.smart_pointers.is_empty());
740        assert!(graph.allocations.is_empty());
741
742        // Test with allocations without smart pointer info
743        let allocations_without_smart = vec![AllocationInfo {
744            ptr: 0x1000,
745            size: 1024,
746            scope_name: None,
747            timestamp_alloc: 0,
748            timestamp_dealloc: None,
749            thread_id: "main".to_string(),
750            borrow_count: 0,
751            stack_trace: None,
752            is_leaked: false,
753            lifetime_ms: None,
754            var_name: None,
755            type_name: None,
756            borrow_info: None,
757            clone_info: None,
758            ownership_history_available: false,
759            smart_pointer_info: None,
760            memory_layout: None,
761            generic_info: None,
762            dynamic_type_info: None,
763            runtime_state: None,
764            stack_allocation: None,
765            temporary_object: None,
766            fragmentation_analysis: None,
767            generic_instantiation: None,
768            type_relationships: None,
769            type_usage: None,
770            function_call_tracking: None,
771            lifecycle_tracking: None,
772            access_tracking: None,
773            drop_chain_analysis: None,
774        }];
775        let graph = ReferenceGraph::new(&allocations_without_smart);
776
777        assert!(graph.adjacency.is_empty());
778        assert!(graph.reverse_refs.is_empty());
779        assert!(graph.smart_pointers.is_empty());
780        assert!(graph.allocations.is_empty());
781
782        // Test with smart pointer allocations
783        let smart_info = SmartPointerInfo {
784            data_ptr: 0x2000,
785            pointer_type: SmartPointerType::Rc,
786            is_weak_reference: false,
787            clones: vec![],
788            cloned_from: None,
789            ref_count_history: vec![RefCountSnapshot {
790                strong_count: 1,
791                weak_count: 0,
792                timestamp: 0,
793            }],
794            weak_count: None,
795            is_data_owner: true,
796            is_implicitly_deallocated: false,
797        };
798
799        let allocations_with_smart = vec![AllocationInfo {
800            ptr: 0x1000,
801            size: 1024,
802            var_name: None,
803            type_name: None,
804            scope_name: None,
805            timestamp_alloc: 0,
806            timestamp_dealloc: None,
807            thread_id: "main".to_string(),
808            borrow_count: 0,
809            stack_trace: None,
810            is_leaked: false,
811            lifetime_ms: None,
812            borrow_info: None,
813            clone_info: None,
814            ownership_history_available: false,
815            smart_pointer_info: Some(smart_info.clone()),
816            memory_layout: None,
817            generic_info: None,
818            dynamic_type_info: None,
819            runtime_state: None,
820            stack_allocation: None,
821            temporary_object: None,
822            fragmentation_analysis: None,
823            generic_instantiation: None,
824            type_relationships: None,
825            type_usage: None,
826            function_call_tracking: None,
827            lifecycle_tracking: None,
828            access_tracking: None,
829            drop_chain_analysis: None,
830        }];
831
832        let graph = ReferenceGraph::new(&allocations_with_smart);
833
834        assert!(!graph.adjacency.is_empty());
835        assert!(!graph.reverse_refs.is_empty());
836        assert!(!graph.smart_pointers.is_empty());
837        assert!(!graph.allocations.is_empty());
838
839        assert!(graph.adjacency.contains_key(&0x1000));
840        assert!(graph.reverse_refs.contains_key(&0x2000));
841        assert!(graph.smart_pointers.contains_key(&0x1000));
842        assert!(graph.allocations.contains_key(&0x1000));
843    }
844
845    #[test]
846    fn test_reference_graph_with_weak_references() {
847        // Test that weak references are skipped
848        let weak_smart_info = SmartPointerInfo {
849            data_ptr: 0x2000,
850            pointer_type: SmartPointerType::Rc,
851            is_weak_reference: true, // This is a weak reference
852            clones: vec![],
853            cloned_from: None,
854            ref_count_history: vec![RefCountSnapshot {
855                strong_count: 1,
856                weak_count: 1,
857                timestamp: 0,
858            }],
859            weak_count: Some(1),
860            is_data_owner: false,
861            is_implicitly_deallocated: false,
862        };
863
864        let allocations_with_weak = vec![AllocationInfo {
865            ptr: 0x1000,
866            size: 1024,
867            var_name: None,
868            type_name: None,
869            scope_name: None,
870            timestamp_alloc: 0,
871            timestamp_dealloc: None,
872            thread_id: "main".to_string(),
873            borrow_count: 0,
874            stack_trace: None,
875            is_leaked: false,
876            lifetime_ms: None,
877            borrow_info: None,
878            clone_info: None,
879            ownership_history_available: false,
880            smart_pointer_info: Some(weak_smart_info),
881            memory_layout: None,
882            generic_info: None,
883            dynamic_type_info: None,
884            runtime_state: None,
885            stack_allocation: None,
886            temporary_object: None,
887            fragmentation_analysis: None,
888            generic_instantiation: None,
889            type_relationships: None,
890            type_usage: None,
891            function_call_tracking: None,
892            lifecycle_tracking: None,
893            access_tracking: None,
894            drop_chain_analysis: None,
895        }];
896
897        let graph = ReferenceGraph::new(&allocations_with_weak);
898
899        // Weak references should be skipped, so graph should be empty
900        assert!(graph.adjacency.is_empty());
901        assert!(graph.reverse_refs.is_empty());
902        assert!(graph.smart_pointers.is_empty());
903        assert!(graph.allocations.is_empty());
904    }
905
906    #[test]
907    fn test_detect_circular_references_empty() {
908        let empty_allocations = vec![];
909        let analysis = detect_circular_references(&empty_allocations);
910
911        assert_eq!(analysis.circular_references.len(), 0);
912        assert_eq!(analysis.total_smart_pointers, 0);
913        assert_eq!(analysis.pointers_in_cycles, 0);
914        assert_eq!(analysis.total_leaked_memory, 0);
915
916        // Check statistics
917        assert_eq!(analysis.statistics.average_cycle_length, 0.0);
918        assert_eq!(analysis.statistics.largest_cycle_size, 0);
919    }
920
921    #[test]
922    fn test_circular_reference_analysis_structure() {
923        let analysis = CircularReferenceAnalysis {
924            circular_references: vec![],
925            total_smart_pointers: 10,
926            pointers_in_cycles: 5,
927            total_leaked_memory: 10240,
928            statistics: CircularReferenceStatistics {
929                by_severity: HashMap::new(),
930                by_type: HashMap::new(),
931                by_pointer_type: HashMap::new(),
932                average_cycle_length: 0.0,
933                largest_cycle_size: 0,
934            },
935        };
936
937        assert_eq!(analysis.total_smart_pointers, 10);
938        assert_eq!(analysis.pointers_in_cycles, 5);
939        assert_eq!(analysis.total_leaked_memory, 10240);
940    }
941
942    #[test]
943    fn test_circular_reference_severity_determination() {
944        // Test low severity - memory size below all thresholds
945        let memory_size = 1024; // 1KB
946        let low_severity = if memory_size > 1024 * 1024 {
947            // 1MB
948            CircularReferenceSeverity::Critical
949        } else if memory_size > 64 * 1024 {
950            // 64KB
951            CircularReferenceSeverity::High
952        } else if memory_size > 4 * 1024 {
953            // 4KB
954            CircularReferenceSeverity::Medium
955        } else {
956            CircularReferenceSeverity::Low
957        };
958        assert_eq!(low_severity, CircularReferenceSeverity::Low);
959
960        // Test medium severity - memory size between 4KB and 64KB
961        let memory_size = 5000; // ~5KB
962        let medium_severity = if memory_size > 1024 * 1024 {
963            // 1MB
964            CircularReferenceSeverity::Critical
965        } else if memory_size > 64 * 1024 {
966            // 64KB
967            CircularReferenceSeverity::High
968        } else if memory_size > 4 * 1024 {
969            // 4KB
970            CircularReferenceSeverity::Medium
971        } else {
972            CircularReferenceSeverity::Low
973        };
974        assert_eq!(medium_severity, CircularReferenceSeverity::Medium);
975
976        // Test high severity - memory size between 64KB and 1MB
977        let memory_size = 70000; // ~68KB
978        let high_severity = if memory_size > 1024 * 1024 {
979            // 1MB
980            CircularReferenceSeverity::Critical
981        } else if memory_size > 64 * 1024 {
982            // 64KB
983            CircularReferenceSeverity::High
984        } else if memory_size > 4 * 1024 {
985            // 4KB
986            CircularReferenceSeverity::Medium
987        } else {
988            CircularReferenceSeverity::Low
989        };
990        assert_eq!(high_severity, CircularReferenceSeverity::High);
991
992        // Test critical severity - memory size above 1MB
993        let memory_size = 2000000; // ~2MB
994        let critical_severity = if memory_size > 1024 * 1024 {
995            // 1MB
996            CircularReferenceSeverity::Critical
997        } else if memory_size > 64 * 1024 {
998            // 64KB
999            CircularReferenceSeverity::High
1000        } else if memory_size > 4 * 1024 {
1001            // 4KB
1002            CircularReferenceSeverity::Medium
1003        } else {
1004            CircularReferenceSeverity::Low
1005        };
1006        assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
1007    }
1008
1009    #[test]
1010    fn test_circular_reference_type_determination() {
1011        // Test self reference - cycle length 1
1012        let cycle_length = 1;
1013        let self_ref_type = if cycle_length == 1 {
1014            CircularReferenceType::SelfReference
1015        } else if cycle_length == 2 {
1016            CircularReferenceType::Simple
1017        } else {
1018            CircularReferenceType::Complex
1019        };
1020        assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
1021
1022        // Test simple cycle - cycle length 2
1023        let cycle_length = 2;
1024        let simple_type = if cycle_length == 1 {
1025            CircularReferenceType::SelfReference
1026        } else if cycle_length == 2 {
1027            CircularReferenceType::Simple
1028        } else {
1029            CircularReferenceType::Complex
1030        };
1031        assert_eq!(simple_type, CircularReferenceType::Simple);
1032
1033        // Test complex cycle - cycle length > 2
1034        let cycle_length = 5;
1035        let complex_type = if cycle_length == 1 {
1036            CircularReferenceType::SelfReference
1037        } else if cycle_length == 2 {
1038            CircularReferenceType::Simple
1039        } else {
1040            CircularReferenceType::Complex
1041        };
1042        assert_eq!(complex_type, CircularReferenceType::Complex);
1043    }
1044}