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        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    /// Depth-first search to detect cycles
212    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                // Check for self-loop (node points to itself)
229                if neighbor == ptr {
230                    // Self-loop detected - record as a single-node cycle
231                    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                    // Found a cycle - extract the cycle path
240                    // O(1) lookup using HashMap instead of O(n) position search
241                    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
255/// Detect circular references in smart pointer allocations
256pub 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    // Process each detected cycle
265    for cycle_path in raw_cycles {
266        if cycle_path.len() < 2 {
267            continue; // Skip trivial cycles
268        }
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    // Generate statistics
281    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
292/// Analyze a single cycle to create a CircularReference
293fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
294    let mut nodes = Vec::new();
295    let mut total_memory = 0;
296
297    // Build cycle nodes
298    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    // Determine cycle type
320    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    // Determine severity based on memory impact and cycle complexity
329    let severity = if total_memory > 1024 * 1024 {
330        // > 1MB
331        CircularReferenceSeverity::Critical
332    } else if total_memory > 64 * 1024 {
333        // > 64KB
334        CircularReferenceSeverity::High
335    } else if total_memory > 4 * 1024 {
336        // > 4KB
337        CircularReferenceSeverity::Medium
338    } else {
339        CircularReferenceSeverity::Low
340    };
341
342    // Suggest weak reference positions (break the cycle at the longest-lived reference)
343    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
354/// Suggest positions where weak references should be used to break cycles
355fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
356    // Simple heuristic: suggest breaking at the node with the highest reference count
357    // (likely to be the most shared and least critical to make weak)
358    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] // Fallback to first position
366    }
367}
368
369/// Generate statistics for the analysis
370fn 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        // Count by severity
379        let severity_key = format!("{:?}", circular_ref.severity);
380        *by_severity.entry(severity_key).or_insert(0) += 1;
381
382        // Count by type
383        let type_key = format!("{:?}", circular_ref.cycle_type);
384        *by_type.entry(type_key).or_insert(0) += 1;
385
386        // Count by pointer type
387        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        // Track cycle sizes
393        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        // Just ensure all variants can be created and compared
486        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        // Just ensure all variants can be created and compared
502        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        // Test with empty cycles
511        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        // Test with some cycles
521        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); // (1 + 2) / 2
565        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        // Check specific counts
571        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        // Test with empty nodes
582        let empty_nodes = vec![];
583        let positions = suggest_weak_positions(&empty_nodes);
584        assert_eq!(positions, vec![0]); // Should fallback to position 0
585
586        // Test with single node
587        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        // Test with multiple nodes, different ref counts
599        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, // Highest ref count
615            },
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]); // Should suggest position with highest ref count
627    }
628
629    #[test]
630    fn test_analyze_cycle() {
631        // Create a mock graph for testing
632        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        // Add mock data to the graph
640        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            borrow_info: None,
688            clone_info: None,
689            ownership_history_available: false,
690            memory_layout: None,
691            generic_info: None,
692            dynamic_type_info: None,
693            runtime_state: None,
694            stack_allocation: None,
695            temporary_object: None,
696            fragmentation_analysis: None,
697            generic_instantiation: None,
698            type_relationships: None,
699            type_usage: None,
700            function_call_tracking: None,
701            lifecycle_tracking: None,
702            access_tracking: None,
703            drop_chain_analysis: None,
704        };
705
706        let allocation_b = AllocationInfo {
707            ptr: 0x2000,
708            size: 2048,
709            var_name: Some("node_b".to_string()),
710            type_name: Some("Rc<Node>".to_string()),
711            smart_pointer_info: Some(smart_info_b.clone()),
712            scope_name: None,
713            timestamp_alloc: 0,
714            timestamp_dealloc: None,
715            thread_id: std::thread::current().id(),
716            thread_id_u64: current_thread_id_u64(),
717            borrow_count: 0,
718            stack_trace: None,
719            is_leaked: false,
720            lifetime_ms: None,
721            borrow_info: None,
722            clone_info: None,
723            ownership_history_available: false,
724            memory_layout: None,
725            generic_info: None,
726            dynamic_type_info: None,
727            runtime_state: None,
728            stack_allocation: None,
729            temporary_object: None,
730            fragmentation_analysis: None,
731            generic_instantiation: None,
732            type_relationships: None,
733            type_usage: None,
734            function_call_tracking: None,
735            lifecycle_tracking: None,
736            access_tracking: None,
737            drop_chain_analysis: None,
738        };
739
740        graph.smart_pointers.insert(0x1000, smart_info_a);
741        graph.smart_pointers.insert(0x2000, smart_info_b);
742        graph.allocations.insert(0x1000, allocation_a);
743        graph.allocations.insert(0x2000, allocation_b);
744
745        // Test analyzing a simple cycle
746        let cycle_path = vec![0x1000, 0x2000];
747        let circular_ref = analyze_cycle(&cycle_path, &graph);
748
749        assert_eq!(circular_ref.cycle_path.len(), 2);
750        assert_eq!(circular_ref.estimated_leaked_memory, 3072); // 1024 + 2048
751        assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
752        assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); // 3072 bytes < 4KB threshold
753        assert!(!circular_ref.suggested_weak_positions.is_empty());
754    }
755
756    #[test]
757    fn test_reference_graph_creation() {
758        // Test with empty allocations
759        let empty_allocations = vec![];
760        let graph = ReferenceGraph::new(&empty_allocations);
761
762        assert!(graph.adjacency.is_empty());
763        assert!(graph.reverse_refs.is_empty());
764        assert!(graph.smart_pointers.is_empty());
765        assert!(graph.allocations.is_empty());
766
767        // Test with allocations without smart pointer info
768        let allocations_without_smart = vec![AllocationInfo {
769            ptr: 0x1000,
770            size: 1024,
771            scope_name: None,
772            timestamp_alloc: 0,
773            timestamp_dealloc: None,
774            thread_id: std::thread::current().id(),
775            thread_id_u64: {
776                use std::hash::{Hash, Hasher};
777                let mut hasher = std::collections::hash_map::DefaultHasher::new();
778                std::thread::current().id().hash(&mut hasher);
779                hasher.finish()
780            },
781            borrow_count: 0,
782            stack_trace: None,
783            is_leaked: false,
784            lifetime_ms: None,
785            var_name: None,
786            type_name: None,
787            borrow_info: None,
788            clone_info: None,
789            ownership_history_available: false,
790            smart_pointer_info: None,
791            memory_layout: None,
792            generic_info: None,
793            dynamic_type_info: None,
794            runtime_state: None,
795            stack_allocation: None,
796            temporary_object: None,
797            fragmentation_analysis: None,
798            generic_instantiation: None,
799            type_relationships: None,
800            type_usage: None,
801            function_call_tracking: None,
802            lifecycle_tracking: None,
803            access_tracking: None,
804            drop_chain_analysis: None,
805        }];
806        let graph = ReferenceGraph::new(&allocations_without_smart);
807
808        assert!(graph.adjacency.is_empty());
809        assert!(graph.reverse_refs.is_empty());
810        assert!(graph.smart_pointers.is_empty());
811        assert!(graph.allocations.is_empty());
812
813        // Test with smart pointer allocations
814        let smart_info = SmartPointerInfo {
815            data_ptr: 0x2000,
816            pointer_type: SmartPointerType::Rc,
817            is_weak_reference: false,
818            clones: vec![],
819            cloned_from: None,
820            ref_count_history: vec![RefCountSnapshot {
821                strong_count: 1,
822                weak_count: 0,
823                timestamp: 0,
824            }],
825            weak_count: None,
826            is_data_owner: true,
827            is_implicitly_deallocated: false,
828        };
829
830        let allocations_with_smart = vec![AllocationInfo {
831            ptr: 0x1000,
832            size: 1024,
833            var_name: None,
834            type_name: None,
835            scope_name: None,
836            timestamp_alloc: 0,
837            timestamp_dealloc: None,
838            thread_id: thread::current().id(),
839            thread_id_u64: {
840                use std::hash::{Hash, Hasher};
841                let mut hasher = std::collections::hash_map::DefaultHasher::new();
842                thread::current().id().hash(&mut hasher);
843                hasher.finish()
844            },
845            borrow_count: 0,
846            stack_trace: None,
847            is_leaked: false,
848            lifetime_ms: None,
849            borrow_info: None,
850            clone_info: None,
851            ownership_history_available: false,
852            smart_pointer_info: Some(smart_info.clone()),
853            memory_layout: None,
854            generic_info: None,
855            dynamic_type_info: None,
856            runtime_state: None,
857            stack_allocation: None,
858            temporary_object: None,
859            fragmentation_analysis: None,
860            generic_instantiation: None,
861            type_relationships: None,
862            type_usage: None,
863            function_call_tracking: None,
864            lifecycle_tracking: None,
865            access_tracking: None,
866            drop_chain_analysis: None,
867        }];
868
869        let graph = ReferenceGraph::new(&allocations_with_smart);
870
871        assert!(!graph.adjacency.is_empty());
872        assert!(!graph.reverse_refs.is_empty());
873        assert!(!graph.smart_pointers.is_empty());
874        assert!(!graph.allocations.is_empty());
875
876        assert!(graph.adjacency.contains_key(&0x1000));
877        assert!(graph.reverse_refs.contains_key(&0x2000));
878        assert!(graph.smart_pointers.contains_key(&0x1000));
879        assert!(graph.allocations.contains_key(&0x1000));
880    }
881
882    #[test]
883    fn test_reference_graph_with_weak_references() {
884        // Test that weak references are skipped
885        let weak_smart_info = SmartPointerInfo {
886            data_ptr: 0x2000,
887            pointer_type: SmartPointerType::Rc,
888            is_weak_reference: true, // This is a weak reference
889            clones: vec![],
890            cloned_from: None,
891            ref_count_history: vec![RefCountSnapshot {
892                strong_count: 1,
893                weak_count: 1,
894                timestamp: 0,
895            }],
896            weak_count: Some(1),
897            is_data_owner: false,
898            is_implicitly_deallocated: false,
899        };
900
901        let allocations_with_weak = vec![AllocationInfo {
902            ptr: 0x1000,
903            size: 1024,
904            var_name: None,
905            type_name: None,
906            scope_name: None,
907            timestamp_alloc: 0,
908            timestamp_dealloc: None,
909            thread_id: thread::current().id(),
910            thread_id_u64: {
911                use std::hash::{Hash, Hasher};
912                let mut hasher = std::collections::hash_map::DefaultHasher::new();
913                thread::current().id().hash(&mut hasher);
914                hasher.finish()
915            },
916            borrow_count: 0,
917            stack_trace: None,
918            is_leaked: false,
919            lifetime_ms: None,
920            borrow_info: None,
921            clone_info: None,
922            ownership_history_available: false,
923            smart_pointer_info: Some(weak_smart_info),
924            memory_layout: None,
925            generic_info: None,
926            dynamic_type_info: None,
927            runtime_state: None,
928            stack_allocation: None,
929            temporary_object: None,
930            fragmentation_analysis: None,
931            generic_instantiation: None,
932            type_relationships: None,
933            type_usage: None,
934            function_call_tracking: None,
935            lifecycle_tracking: None,
936            access_tracking: None,
937            drop_chain_analysis: None,
938        }];
939
940        let graph = ReferenceGraph::new(&allocations_with_weak);
941
942        // Weak references should be skipped, so graph should be empty
943        assert!(graph.adjacency.is_empty());
944        assert!(graph.reverse_refs.is_empty());
945        assert!(graph.smart_pointers.is_empty());
946        assert!(graph.allocations.is_empty());
947    }
948
949    #[test]
950    fn test_detect_circular_references_empty() {
951        let empty_allocations = vec![];
952        let analysis = detect_circular_references(&empty_allocations);
953
954        assert_eq!(analysis.circular_references.len(), 0);
955        assert_eq!(analysis.total_smart_pointers, 0);
956        assert_eq!(analysis.pointers_in_cycles, 0);
957        assert_eq!(analysis.total_leaked_memory, 0);
958
959        // Check statistics
960        assert_eq!(analysis.statistics.average_cycle_length, 0.0);
961        assert_eq!(analysis.statistics.largest_cycle_size, 0);
962    }
963
964    #[test]
965    fn test_circular_reference_analysis_structure() {
966        let analysis = CircularReferenceAnalysis {
967            circular_references: vec![],
968            total_smart_pointers: 10,
969            pointers_in_cycles: 5,
970            total_leaked_memory: 10240,
971            statistics: CircularReferenceStatistics {
972                by_severity: HashMap::new(),
973                by_type: HashMap::new(),
974                by_pointer_type: HashMap::new(),
975                average_cycle_length: 0.0,
976                largest_cycle_size: 0,
977            },
978        };
979
980        assert_eq!(analysis.total_smart_pointers, 10);
981        assert_eq!(analysis.pointers_in_cycles, 5);
982        assert_eq!(analysis.total_leaked_memory, 10240);
983    }
984
985    #[test]
986    fn test_circular_reference_severity_determination() {
987        // Test low severity - memory size below all thresholds
988        let memory_size = 1024; // 1KB
989        let low_severity = if memory_size > 1024 * 1024 {
990            // 1MB
991            CircularReferenceSeverity::Critical
992        } else if memory_size > 64 * 1024 {
993            // 64KB
994            CircularReferenceSeverity::High
995        } else if memory_size > 4 * 1024 {
996            // 4KB
997            CircularReferenceSeverity::Medium
998        } else {
999            CircularReferenceSeverity::Low
1000        };
1001        assert_eq!(low_severity, CircularReferenceSeverity::Low);
1002
1003        // Test medium severity - memory size between 4KB and 64KB
1004        let memory_size = 5000; // ~5KB
1005        let medium_severity = if memory_size > 1024 * 1024 {
1006            // 1MB
1007            CircularReferenceSeverity::Critical
1008        } else if memory_size > 64 * 1024 {
1009            // 64KB
1010            CircularReferenceSeverity::High
1011        } else if memory_size > 4 * 1024 {
1012            // 4KB
1013            CircularReferenceSeverity::Medium
1014        } else {
1015            CircularReferenceSeverity::Low
1016        };
1017        assert_eq!(medium_severity, CircularReferenceSeverity::Medium);
1018
1019        // Test high severity - memory size between 64KB and 1MB
1020        let memory_size = 70000; // ~68KB
1021        let high_severity = if memory_size > 1024 * 1024 {
1022            // 1MB
1023            CircularReferenceSeverity::Critical
1024        } else if memory_size > 64 * 1024 {
1025            // 64KB
1026            CircularReferenceSeverity::High
1027        } else if memory_size > 4 * 1024 {
1028            // 4KB
1029            CircularReferenceSeverity::Medium
1030        } else {
1031            CircularReferenceSeverity::Low
1032        };
1033        assert_eq!(high_severity, CircularReferenceSeverity::High);
1034
1035        // Test critical severity - memory size above 1MB
1036        let memory_size = 2000000; // ~2MB
1037        let critical_severity = if memory_size > 1024 * 1024 {
1038            // 1MB
1039            CircularReferenceSeverity::Critical
1040        } else if memory_size > 64 * 1024 {
1041            // 64KB
1042            CircularReferenceSeverity::High
1043        } else if memory_size > 4 * 1024 {
1044            // 4KB
1045            CircularReferenceSeverity::Medium
1046        } else {
1047            CircularReferenceSeverity::Low
1048        };
1049        assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
1050    }
1051
1052    #[test]
1053    fn test_circular_reference_type_determination() {
1054        // Test self reference - cycle length 1
1055        let cycle_length = 1;
1056        let self_ref_type = if cycle_length == 1 {
1057            CircularReferenceType::SelfReference
1058        } else if cycle_length == 2 {
1059            CircularReferenceType::Simple
1060        } else {
1061            CircularReferenceType::Complex
1062        };
1063        assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
1064
1065        // Test simple cycle - cycle length 2
1066        let cycle_length = 2;
1067        let simple_type = if cycle_length == 1 {
1068            CircularReferenceType::SelfReference
1069        } else if cycle_length == 2 {
1070            CircularReferenceType::Simple
1071        } else {
1072            CircularReferenceType::Complex
1073        };
1074        assert_eq!(simple_type, CircularReferenceType::Simple);
1075
1076        // Test complex cycle - cycle length > 2
1077        let cycle_length = 5;
1078        let complex_type = if cycle_length == 1 {
1079            CircularReferenceType::SelfReference
1080        } else if cycle_length == 2 {
1081            CircularReferenceType::Simple
1082        } else {
1083            CircularReferenceType::Complex
1084        };
1085        assert_eq!(complex_type, CircularReferenceType::Complex);
1086    }
1087}