use crate::capture::types::{AllocationInfo, SmartPointerInfo, SmartPointerType};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CircularReference {
pub cycle_path: Vec<CircularReferenceNode>,
pub suggested_weak_positions: Vec<usize>,
pub estimated_leaked_memory: usize,
pub severity: CircularReferenceSeverity,
pub cycle_type: CircularReferenceType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CircularReferenceNode {
pub ptr: usize,
pub data_ptr: usize,
pub var_name: Option<String>,
pub type_name: Option<String>,
pub pointer_type: SmartPointerType,
pub ref_count: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CircularReferenceSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CircularReferenceType {
Simple,
Complex,
SelfReference,
Nested,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircularReferenceAnalysis {
pub circular_references: Vec<CircularReference>,
pub total_smart_pointers: usize,
pub pointers_in_cycles: usize,
pub total_leaked_memory: usize,
pub statistics: CircularReferenceStatistics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CircularReferenceStatistics {
pub by_severity: HashMap<String, usize>,
pub by_type: HashMap<String, usize>,
pub by_pointer_type: HashMap<String, usize>,
pub average_cycle_length: f64,
pub largest_cycle_size: usize,
}
#[derive(Debug)]
struct ReferenceGraph {
adjacency: HashMap<usize, Vec<usize>>,
reverse_refs: HashMap<usize, Vec<usize>>,
smart_pointers: HashMap<usize, SmartPointerInfo>,
allocations: HashMap<usize, AllocationInfo>,
}
impl ReferenceGraph {
fn new(allocations: &[AllocationInfo]) -> Self {
let mut graph = ReferenceGraph {
adjacency: HashMap::new(),
reverse_refs: HashMap::new(),
smart_pointers: HashMap::new(),
allocations: HashMap::new(),
};
for allocation in allocations {
if let Some(ref smart_info) = allocation.smart_pointer_info {
if smart_info.is_weak_reference {
continue;
}
graph
.smart_pointers
.insert(allocation.ptr, smart_info.clone());
graph.allocations.insert(allocation.ptr, allocation.clone());
graph.adjacency.entry(allocation.ptr).or_default();
graph
.reverse_refs
.entry(smart_info.data_ptr)
.or_default()
.push(allocation.ptr);
for &clone_ptr in &smart_info.clones {
graph
.adjacency
.entry(allocation.ptr)
.or_default()
.push(clone_ptr);
}
if let Some(source_ptr) = smart_info.cloned_from {
graph
.adjacency
.entry(source_ptr)
.or_default()
.push(allocation.ptr);
}
}
}
graph
}
fn detect_cycles(&self) -> Vec<Vec<usize>> {
let mut cycles = Vec::new();
let mut visited = HashSet::new();
let mut rec_stack = HashSet::new();
let mut path = Vec::new();
let mut path_index = HashMap::new();
for &ptr in self.smart_pointers.keys() {
if !visited.contains(&ptr) {
self.dfs_detect_cycles(
ptr,
&mut visited,
&mut rec_stack,
&mut path,
&mut path_index,
&mut cycles,
);
}
}
cycles
}
fn dfs_detect_cycles(
&self,
ptr: usize,
visited: &mut HashSet<usize>,
rec_stack: &mut HashSet<usize>,
path: &mut Vec<usize>,
path_index: &mut HashMap<usize, usize>,
cycles: &mut Vec<Vec<usize>>,
) {
visited.insert(ptr);
rec_stack.insert(ptr);
path_index.insert(ptr, path.len());
path.push(ptr);
if let Some(neighbors) = self.adjacency.get(&ptr) {
for &neighbor in neighbors {
if neighbor == ptr {
cycles.push(vec![ptr]);
tracing::trace!("Self-loop detected at ptr=0x{:x}", ptr);
continue;
}
if !visited.contains(&neighbor) {
self.dfs_detect_cycles(neighbor, visited, rec_stack, path, path_index, cycles);
} else if rec_stack.contains(&neighbor) {
if let Some(&cycle_start) = path_index.get(&neighbor) {
let cycle = path[cycle_start..].to_vec();
cycles.push(cycle);
}
}
}
}
path.pop();
path_index.remove(&ptr);
rec_stack.remove(&ptr);
}
}
pub fn detect_circular_references(allocations: &[AllocationInfo]) -> CircularReferenceAnalysis {
let graph = ReferenceGraph::new(allocations);
let raw_cycles = graph.detect_cycles();
let mut circular_references = Vec::new();
let mut total_leaked_memory = 0;
let mut pointers_in_cycles = HashSet::new();
for cycle_path in raw_cycles {
if cycle_path.len() < 2 {
continue; }
let circular_ref = analyze_cycle(&cycle_path, &graph);
total_leaked_memory += circular_ref.estimated_leaked_memory;
for node in &circular_ref.cycle_path {
pointers_in_cycles.insert(node.ptr);
}
circular_references.push(circular_ref);
}
let statistics = generate_statistics(&circular_references);
CircularReferenceAnalysis {
circular_references,
total_smart_pointers: graph.smart_pointers.len(),
pointers_in_cycles: pointers_in_cycles.len(),
total_leaked_memory,
statistics,
}
}
fn analyze_cycle(cycle_path: &[usize], graph: &ReferenceGraph) -> CircularReference {
let mut nodes = Vec::new();
let mut total_memory = 0;
for &ptr in cycle_path {
if let (Some(smart_info), Some(allocation)) =
(graph.smart_pointers.get(&ptr), graph.allocations.get(&ptr))
{
let node = CircularReferenceNode {
ptr,
data_ptr: smart_info.data_ptr,
var_name: allocation.var_name.clone(),
type_name: allocation.type_name.clone(),
pointer_type: smart_info.pointer_type.clone(),
ref_count: smart_info
.latest_ref_counts()
.map(|snapshot| snapshot.strong_count)
.unwrap_or(1),
};
total_memory += allocation.size;
nodes.push(node);
}
}
let cycle_type = if cycle_path.len() == 1 {
CircularReferenceType::SelfReference
} else if cycle_path.len() == 2 {
CircularReferenceType::Simple
} else {
CircularReferenceType::Complex
};
let severity = if total_memory > 1024 * 1024 {
CircularReferenceSeverity::Critical
} else if total_memory > 64 * 1024 {
CircularReferenceSeverity::High
} else if total_memory > 4 * 1024 {
CircularReferenceSeverity::Medium
} else {
CircularReferenceSeverity::Low
};
let suggested_weak_positions = suggest_weak_positions(&nodes);
CircularReference {
cycle_path: nodes,
suggested_weak_positions,
estimated_leaked_memory: total_memory,
severity,
cycle_type,
}
}
fn suggest_weak_positions(nodes: &[CircularReferenceNode]) -> Vec<usize> {
if let Some((index, _)) = nodes
.iter()
.enumerate()
.max_by_key(|(_, node)| node.ref_count)
{
vec![index]
} else {
vec![0] }
}
fn generate_statistics(circular_references: &[CircularReference]) -> CircularReferenceStatistics {
let mut by_severity = HashMap::new();
let mut by_type = HashMap::new();
let mut by_pointer_type = HashMap::new();
let mut total_cycle_length = 0;
let mut largest_cycle_size = 0;
for circular_ref in circular_references {
let severity_key = format!("{:?}", circular_ref.severity);
*by_severity.entry(severity_key).or_insert(0) += 1;
let type_key = format!("{:?}", circular_ref.cycle_type);
*by_type.entry(type_key).or_insert(0) += 1;
for node in &circular_ref.cycle_path {
let pointer_type_key = format!("{:?}", node.pointer_type);
*by_pointer_type.entry(pointer_type_key).or_insert(0) += 1;
}
let cycle_length = circular_ref.cycle_path.len();
total_cycle_length += cycle_length;
largest_cycle_size = largest_cycle_size.max(cycle_length);
}
let average_cycle_length = if circular_references.is_empty() {
0.0
} else {
total_cycle_length as f64 / circular_references.len() as f64
};
CircularReferenceStatistics {
by_severity,
by_type,
by_pointer_type,
average_cycle_length,
largest_cycle_size,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capture::types::{RefCountSnapshot, SmartPointerInfo, SmartPointerType};
use crate::utils::current_thread_id_u64;
use std::thread;
#[test]
fn test_circular_reference_node_creation() {
let node = CircularReferenceNode {
ptr: 0x1000,
data_ptr: 0x2000,
var_name: Some("test_node".to_string()),
type_name: Some("Rc<RefCell<Node>>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 1,
};
assert_eq!(node.ptr, 0x1000);
assert_eq!(node.data_ptr, 0x2000);
assert_eq!(node.var_name, Some("test_node".to_string()));
assert_eq!(node.type_name, Some("Rc<RefCell<Node>>".to_string()));
assert_eq!(node.pointer_type, SmartPointerType::Rc);
assert_eq!(node.ref_count, 1);
}
#[test]
fn test_circular_reference_creation() {
let cycle_path = vec![
CircularReferenceNode {
ptr: 0x1000,
data_ptr: 0x2000,
var_name: Some("node_a".to_string()),
type_name: Some("Rc<RefCell<Node>>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 1,
},
CircularReferenceNode {
ptr: 0x2000,
data_ptr: 0x1000,
var_name: Some("node_b".to_string()),
type_name: Some("Rc<RefCell<Node>>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 1,
},
];
let circular_ref = CircularReference {
cycle_path: cycle_path.clone(),
suggested_weak_positions: vec![1],
estimated_leaked_memory: 1024,
severity: CircularReferenceSeverity::Medium,
cycle_type: CircularReferenceType::Simple,
};
assert_eq!(circular_ref.cycle_path.len(), 2);
assert_eq!(circular_ref.suggested_weak_positions, vec![1]);
assert_eq!(circular_ref.estimated_leaked_memory, 1024);
assert_eq!(circular_ref.severity, CircularReferenceSeverity::Medium);
assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
}
#[test]
fn test_circular_reference_severity_variants() {
let severities = [
CircularReferenceSeverity::Low,
CircularReferenceSeverity::Medium,
CircularReferenceSeverity::High,
CircularReferenceSeverity::Critical,
];
assert_eq!(severities[0], CircularReferenceSeverity::Low);
assert_eq!(severities[1], CircularReferenceSeverity::Medium);
assert_eq!(severities[2], CircularReferenceSeverity::High);
assert_eq!(severities[3], CircularReferenceSeverity::Critical);
}
#[test]
fn test_circular_reference_type_variants() {
let types = [
CircularReferenceType::Simple,
CircularReferenceType::Complex,
CircularReferenceType::SelfReference,
CircularReferenceType::Nested,
];
assert_eq!(types[0], CircularReferenceType::Simple);
assert_eq!(types[1], CircularReferenceType::Complex);
assert_eq!(types[2], CircularReferenceType::SelfReference);
assert_eq!(types[3], CircularReferenceType::Nested);
}
#[test]
fn test_circular_reference_statistics_generation() {
let empty_cycles = vec![];
let stats = generate_statistics(&empty_cycles);
assert_eq!(stats.average_cycle_length, 0.0);
assert_eq!(stats.largest_cycle_size, 0);
assert!(stats.by_severity.is_empty());
assert!(stats.by_type.is_empty());
assert!(stats.by_pointer_type.is_empty());
let cycles = vec![
CircularReference {
cycle_path: vec![CircularReferenceNode {
ptr: 0x1000,
data_ptr: 0x2000,
var_name: Some("node_a".to_string()),
type_name: Some("Rc<Node>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 2,
}],
suggested_weak_positions: vec![0],
estimated_leaked_memory: 1024,
severity: CircularReferenceSeverity::Low,
cycle_type: CircularReferenceType::SelfReference,
},
CircularReference {
cycle_path: vec![
CircularReferenceNode {
ptr: 0x2000,
data_ptr: 0x3000,
var_name: Some("node_b".to_string()),
type_name: Some("Arc<Node>".to_string()),
pointer_type: SmartPointerType::Arc,
ref_count: 3,
},
CircularReferenceNode {
ptr: 0x3000,
data_ptr: 0x2000,
var_name: Some("node_c".to_string()),
type_name: Some("Arc<Node>".to_string()),
pointer_type: SmartPointerType::Arc,
ref_count: 1,
},
],
suggested_weak_positions: vec![0],
estimated_leaked_memory: 2048,
severity: CircularReferenceSeverity::Medium,
cycle_type: CircularReferenceType::Simple,
},
];
let stats = generate_statistics(&cycles);
assert_eq!(stats.average_cycle_length, 1.5); assert_eq!(stats.largest_cycle_size, 2);
assert!(!stats.by_severity.is_empty());
assert!(!stats.by_type.is_empty());
assert!(!stats.by_pointer_type.is_empty());
assert_eq!(*stats.by_severity.get("Low").unwrap_or(&0), 1);
assert_eq!(*stats.by_severity.get("Medium").unwrap_or(&0), 1);
assert_eq!(*stats.by_type.get("SelfReference").unwrap_or(&0), 1);
assert_eq!(*stats.by_type.get("Simple").unwrap_or(&0), 1);
assert_eq!(*stats.by_pointer_type.get("Rc").unwrap_or(&0), 1);
assert_eq!(*stats.by_pointer_type.get("Arc").unwrap_or(&0), 2);
}
#[test]
fn test_suggest_weak_positions() {
let empty_nodes = vec![];
let positions = suggest_weak_positions(&empty_nodes);
assert_eq!(positions, vec![0]);
let single_node = vec![CircularReferenceNode {
ptr: 0x1000,
data_ptr: 0x2000,
var_name: Some("node".to_string()),
type_name: Some("Rc<Node>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 1,
}];
let positions = suggest_weak_positions(&single_node);
assert_eq!(positions, vec![0]);
let multiple_nodes = vec![
CircularReferenceNode {
ptr: 0x1000,
data_ptr: 0x2000,
var_name: Some("node_a".to_string()),
type_name: Some("Rc<Node>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 1,
},
CircularReferenceNode {
ptr: 0x2000,
data_ptr: 0x3000,
var_name: Some("node_b".to_string()),
type_name: Some("Rc<Node>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 3, },
CircularReferenceNode {
ptr: 0x3000,
data_ptr: 0x1000,
var_name: Some("node_c".to_string()),
type_name: Some("Rc<Node>".to_string()),
pointer_type: SmartPointerType::Rc,
ref_count: 2,
},
];
let positions = suggest_weak_positions(&multiple_nodes);
assert_eq!(positions, vec![1]); }
#[test]
fn test_analyze_cycle() {
let mut graph = ReferenceGraph {
adjacency: HashMap::new(),
reverse_refs: HashMap::new(),
smart_pointers: HashMap::new(),
allocations: HashMap::new(),
};
let smart_info_a = SmartPointerInfo {
data_ptr: 0x2000,
pointer_type: SmartPointerType::Rc,
is_weak_reference: false,
clones: vec![],
cloned_from: None,
ref_count_history: vec![RefCountSnapshot {
strong_count: 1,
weak_count: 0,
timestamp: 0,
}],
weak_count: None,
is_data_owner: true,
is_implicitly_deallocated: false,
};
let smart_info_b = SmartPointerInfo {
data_ptr: 0x1000,
pointer_type: SmartPointerType::Rc,
is_weak_reference: false,
clones: vec![],
cloned_from: None,
ref_count_history: vec![RefCountSnapshot {
strong_count: 1,
weak_count: 0,
timestamp: 0,
}],
weak_count: None,
is_data_owner: true,
is_implicitly_deallocated: false,
};
let allocation_a = AllocationInfo {
ptr: 0x1000,
size: 1024,
var_name: Some("node_a".to_string()),
type_name: Some("Rc<Node>".to_string()),
smart_pointer_info: Some(smart_info_a.clone()),
scope_name: None,
timestamp_alloc: 0,
timestamp_dealloc: None,
thread_id: std::thread::current().id(),
thread_id_u64: current_thread_id_u64(),
borrow_count: 0,
stack_trace: None,
is_leaked: false,
lifetime_ms: None,
module_path: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
stack_ptr: None,
task_id: None,
};
let allocation_b = AllocationInfo {
ptr: 0x2000,
size: 2048,
var_name: Some("node_b".to_string()),
type_name: Some("Rc<Node>".to_string()),
smart_pointer_info: Some(smart_info_b.clone()),
scope_name: None,
timestamp_alloc: 0,
timestamp_dealloc: None,
thread_id: std::thread::current().id(),
thread_id_u64: current_thread_id_u64(),
borrow_count: 0,
stack_trace: None,
is_leaked: false,
lifetime_ms: None,
module_path: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
stack_ptr: None,
task_id: None,
};
graph.smart_pointers.insert(0x1000, smart_info_a);
graph.smart_pointers.insert(0x2000, smart_info_b);
graph.allocations.insert(0x1000, allocation_a);
graph.allocations.insert(0x2000, allocation_b);
let cycle_path = vec![0x1000, 0x2000];
let circular_ref = analyze_cycle(&cycle_path, &graph);
assert_eq!(circular_ref.cycle_path.len(), 2);
assert_eq!(circular_ref.estimated_leaked_memory, 3072); assert_eq!(circular_ref.cycle_type, CircularReferenceType::Simple);
assert_eq!(circular_ref.severity, CircularReferenceSeverity::Low); assert!(!circular_ref.suggested_weak_positions.is_empty());
}
#[test]
fn test_reference_graph_creation() {
let empty_allocations = vec![];
let graph = ReferenceGraph::new(&empty_allocations);
assert!(graph.adjacency.is_empty());
assert!(graph.reverse_refs.is_empty());
assert!(graph.smart_pointers.is_empty());
assert!(graph.allocations.is_empty());
let allocations_without_smart = vec![AllocationInfo {
ptr: 0x1000,
size: 1024,
scope_name: None,
timestamp_alloc: 0,
timestamp_dealloc: None,
thread_id: std::thread::current().id(),
thread_id_u64: {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::thread::current().id().hash(&mut hasher);
hasher.finish()
},
borrow_count: 0,
stack_trace: None,
is_leaked: false,
lifetime_ms: None,
var_name: None,
type_name: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
smart_pointer_info: None,
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
module_path: None,
stack_ptr: None,
task_id: None,
}];
let graph = ReferenceGraph::new(&allocations_without_smart);
assert!(graph.adjacency.is_empty());
assert!(graph.reverse_refs.is_empty());
assert!(graph.smart_pointers.is_empty());
assert!(graph.allocations.is_empty());
let smart_info = SmartPointerInfo {
data_ptr: 0x2000,
pointer_type: SmartPointerType::Rc,
is_weak_reference: false,
clones: vec![],
cloned_from: None,
ref_count_history: vec![RefCountSnapshot {
strong_count: 1,
weak_count: 0,
timestamp: 0,
}],
weak_count: None,
is_data_owner: true,
is_implicitly_deallocated: false,
};
let allocations_with_smart = vec![AllocationInfo {
ptr: 0x1000,
size: 1024,
var_name: None,
type_name: None,
scope_name: None,
timestamp_alloc: 0,
timestamp_dealloc: None,
thread_id: thread::current().id(),
thread_id_u64: {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
thread::current().id().hash(&mut hasher);
hasher.finish()
},
borrow_count: 0,
stack_trace: None,
is_leaked: false,
lifetime_ms: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
smart_pointer_info: Some(smart_info.clone()),
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
module_path: None,
stack_ptr: None,
task_id: None,
}];
let graph = ReferenceGraph::new(&allocations_with_smart);
assert!(!graph.adjacency.is_empty());
assert!(!graph.reverse_refs.is_empty());
assert!(!graph.smart_pointers.is_empty());
assert!(!graph.allocations.is_empty());
assert!(graph.adjacency.contains_key(&0x1000));
assert!(graph.reverse_refs.contains_key(&0x2000));
assert!(graph.smart_pointers.contains_key(&0x1000));
assert!(graph.allocations.contains_key(&0x1000));
}
#[test]
fn test_reference_graph_with_weak_references() {
let weak_smart_info = SmartPointerInfo {
data_ptr: 0x2000,
pointer_type: SmartPointerType::Rc,
is_weak_reference: true, clones: vec![],
cloned_from: None,
ref_count_history: vec![RefCountSnapshot {
strong_count: 1,
weak_count: 1,
timestamp: 0,
}],
weak_count: Some(1),
is_data_owner: false,
is_implicitly_deallocated: false,
};
let allocations_with_weak = vec![AllocationInfo {
ptr: 0x1000,
size: 1024,
var_name: None,
type_name: None,
scope_name: None,
timestamp_alloc: 0,
timestamp_dealloc: None,
thread_id: thread::current().id(),
thread_id_u64: {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
thread::current().id().hash(&mut hasher);
hasher.finish()
},
borrow_count: 0,
stack_trace: None,
is_leaked: false,
lifetime_ms: None,
borrow_info: None,
clone_info: None,
ownership_history_available: false,
smart_pointer_info: Some(weak_smart_info),
memory_layout: None,
generic_info: None,
dynamic_type_info: None,
runtime_state: None,
stack_allocation: None,
temporary_object: None,
fragmentation_analysis: None,
generic_instantiation: None,
type_relationships: None,
type_usage: None,
function_call_tracking: None,
lifecycle_tracking: None,
access_tracking: None,
drop_chain_analysis: None,
module_path: None,
stack_ptr: None,
task_id: None,
}];
let graph = ReferenceGraph::new(&allocations_with_weak);
assert!(graph.adjacency.is_empty());
assert!(graph.reverse_refs.is_empty());
assert!(graph.smart_pointers.is_empty());
assert!(graph.allocations.is_empty());
}
#[test]
fn test_detect_circular_references_empty() {
let empty_allocations = vec![];
let analysis = detect_circular_references(&empty_allocations);
assert_eq!(analysis.circular_references.len(), 0);
assert_eq!(analysis.total_smart_pointers, 0);
assert_eq!(analysis.pointers_in_cycles, 0);
assert_eq!(analysis.total_leaked_memory, 0);
assert_eq!(analysis.statistics.average_cycle_length, 0.0);
assert_eq!(analysis.statistics.largest_cycle_size, 0);
}
#[test]
fn test_circular_reference_analysis_structure() {
let analysis = CircularReferenceAnalysis {
circular_references: vec![],
total_smart_pointers: 10,
pointers_in_cycles: 5,
total_leaked_memory: 10240,
statistics: CircularReferenceStatistics {
by_severity: HashMap::new(),
by_type: HashMap::new(),
by_pointer_type: HashMap::new(),
average_cycle_length: 0.0,
largest_cycle_size: 0,
},
};
assert_eq!(analysis.total_smart_pointers, 10);
assert_eq!(analysis.pointers_in_cycles, 5);
assert_eq!(analysis.total_leaked_memory, 10240);
}
#[test]
fn test_circular_reference_severity_determination() {
let memory_size = 1024; let low_severity = if memory_size > 1024 * 1024 {
CircularReferenceSeverity::Critical
} else if memory_size > 64 * 1024 {
CircularReferenceSeverity::High
} else if memory_size > 4 * 1024 {
CircularReferenceSeverity::Medium
} else {
CircularReferenceSeverity::Low
};
assert_eq!(low_severity, CircularReferenceSeverity::Low);
let memory_size = 5000; let medium_severity = if memory_size > 1024 * 1024 {
CircularReferenceSeverity::Critical
} else if memory_size > 64 * 1024 {
CircularReferenceSeverity::High
} else if memory_size > 4 * 1024 {
CircularReferenceSeverity::Medium
} else {
CircularReferenceSeverity::Low
};
assert_eq!(medium_severity, CircularReferenceSeverity::Medium);
let memory_size = 70000; let high_severity = if memory_size > 1024 * 1024 {
CircularReferenceSeverity::Critical
} else if memory_size > 64 * 1024 {
CircularReferenceSeverity::High
} else if memory_size > 4 * 1024 {
CircularReferenceSeverity::Medium
} else {
CircularReferenceSeverity::Low
};
assert_eq!(high_severity, CircularReferenceSeverity::High);
let memory_size = 2000000; let critical_severity = if memory_size > 1024 * 1024 {
CircularReferenceSeverity::Critical
} else if memory_size > 64 * 1024 {
CircularReferenceSeverity::High
} else if memory_size > 4 * 1024 {
CircularReferenceSeverity::Medium
} else {
CircularReferenceSeverity::Low
};
assert_eq!(critical_severity, CircularReferenceSeverity::Critical);
}
#[test]
fn test_circular_reference_type_determination() {
let cycle_length = 1;
let self_ref_type = if cycle_length == 1 {
CircularReferenceType::SelfReference
} else if cycle_length == 2 {
CircularReferenceType::Simple
} else {
CircularReferenceType::Complex
};
assert_eq!(self_ref_type, CircularReferenceType::SelfReference);
let cycle_length = 2;
let simple_type = if cycle_length == 1 {
CircularReferenceType::SelfReference
} else if cycle_length == 2 {
CircularReferenceType::Simple
} else {
CircularReferenceType::Complex
};
assert_eq!(simple_type, CircularReferenceType::Simple);
let cycle_length = 5;
let complex_type = if cycle_length == 1 {
CircularReferenceType::SelfReference
} else if cycle_length == 2 {
CircularReferenceType::Simple
} else {
CircularReferenceType::Complex
};
assert_eq!(complex_type, CircularReferenceType::Complex);
}
}