Skip to main content

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