use crate::analysis::closure::types::*;
use crate::capture::types::AllocationInfo;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
static GLOBAL_CLOSURE_ANALYZER: OnceLock<Arc<ClosureAnalyzer>> = OnceLock::new();
pub fn get_global_closure_analyzer() -> Arc<ClosureAnalyzer> {
GLOBAL_CLOSURE_ANALYZER
.get_or_init(|| Arc::new(ClosureAnalyzer::new()))
.clone()
}
pub struct ClosureAnalyzer {
closures: Mutex<HashMap<usize, ClosureInfo>>,
capture_events: Mutex<Vec<CaptureEvent>>,
lifetime_graph: Mutex<LifetimeGraph>,
}
impl ClosureAnalyzer {
pub fn new() -> Self {
Self {
closures: Mutex::new(HashMap::new()),
capture_events: Mutex::new(Vec::new()),
lifetime_graph: Mutex::new(LifetimeGraph::new()),
}
}
pub fn register_closure(&self, closure_ptr: usize, captures: Vec<CaptureInfo>) {
let closure_info = ClosureInfo {
ptr: closure_ptr,
captures: captures.clone(),
creation_timestamp: current_timestamp(),
thread_id: format!("{:?}", std::thread::current().id()),
call_site: capture_call_site(),
memory_footprint: self.calculate_closure_footprint(&captures),
optimization_potential: self.analyze_optimization_potential(&captures),
};
for capture in &captures {
let event = CaptureEvent {
closure_ptr,
captured_var: capture.clone(),
event_type: CaptureEventType::Captured,
timestamp: current_timestamp(),
};
if let Ok(mut events) = self.capture_events.lock() {
events.push(event);
}
}
if let Ok(mut graph) = self.lifetime_graph.lock() {
graph.add_closure_relationships(closure_ptr, &captures);
}
if let Ok(mut closures) = self.closures.lock() {
closures.insert(closure_ptr, closure_info);
}
}
pub fn track_closure_drop(&self, closure_ptr: usize) {
if let Ok(mut closures) = self.closures.lock() {
if let Some(closure_info) = closures.get_mut(&closure_ptr) {
for capture in &closure_info.captures {
let event = CaptureEvent {
closure_ptr,
captured_var: capture.clone(),
event_type: CaptureEventType::Released,
timestamp: current_timestamp(),
};
if let Ok(mut events) = self.capture_events.lock() {
events.push(event);
}
}
}
closures.remove(&closure_ptr);
}
if let Ok(mut graph) = self.lifetime_graph.lock() {
graph.remove_closure(closure_ptr);
}
}
pub fn analyze_closure_patterns(
&self,
allocations: &[AllocationInfo],
) -> ClosureAnalysisReport {
let mut detected_closures = Vec::new();
let mut capture_statistics = CaptureStatistics::default();
for allocation in allocations {
if let Some(type_name) = &allocation.type_name {
if self.is_closure_type(type_name) {
if let Some(analysis) = self.analyze_closure_allocation(allocation) {
detected_closures.push(analysis);
}
}
}
}
if let Ok(closures) = self.closures.lock() {
capture_statistics = self.calculate_capture_statistics(&closures);
}
let optimization_suggestions = self.generate_optimization_suggestions(&detected_closures);
let lifetime_analysis = self.analyze_capture_lifetimes();
ClosureAnalysisReport {
detected_closures,
capture_statistics,
optimization_suggestions,
lifetime_analysis,
analysis_timestamp: current_timestamp(),
}
}
fn is_closure_type(&self, type_name: &str) -> bool {
type_name.contains("closure")
|| type_name.contains("{{closure}}")
|| type_name.starts_with("fn(")
|| type_name.contains("dyn Fn")
|| type_name.contains("impl Fn")
}
fn analyze_closure_allocation(&self, allocation: &AllocationInfo) -> Option<DetectedClosure> {
let type_name = allocation.type_name.as_ref()?;
Some(DetectedClosure {
ptr: allocation.ptr,
type_name: type_name.clone(),
size: allocation.size,
estimated_captures: self.estimate_captures_from_size(allocation.size),
closure_type: self.classify_closure_type(type_name),
creation_context: CreationContext {
scope_name: allocation.scope_name.clone(),
thread_id: format!("{:?}", allocation.thread_id),
timestamp: allocation.timestamp_alloc,
},
memory_impact: self.assess_memory_impact(allocation.size),
})
}
fn estimate_captures_from_size(&self, size: usize) -> usize {
if size <= 8 {
0
} else if size <= 32 {
2
} else if size <= 128 {
8
} else {
size / 16
}
}
fn classify_closure_type(&self, type_name: &str) -> ClosureType {
if type_name.contains("FnOnce") {
ClosureType::FnOnce
} else if type_name.contains("FnMut") {
ClosureType::FnMut
} else if type_name.contains("Fn") {
ClosureType::Fn
} else {
ClosureType::Unknown
}
}
fn assess_memory_impact(&self, size: usize) -> MemoryImpact {
match size {
0..=16 => MemoryImpact::Minimal,
17..=64 => MemoryImpact::Low,
65..=256 => MemoryImpact::Medium,
257..=1024 => MemoryImpact::High,
_ => MemoryImpact::VeryHigh,
}
}
fn calculate_closure_footprint(&self, captures: &[CaptureInfo]) -> ClosureFootprint {
let total_size = captures.iter().map(|c| c.size).sum();
let by_value_count = captures
.iter()
.filter(|c| c.mode == CaptureMode::ByValue)
.count();
let by_ref_count = captures
.iter()
.filter(|c| c.mode == CaptureMode::ByReference)
.count();
let by_mut_ref_count = captures
.iter()
.filter(|c| c.mode == CaptureMode::ByMutableReference)
.count();
ClosureFootprint {
total_size,
capture_count: captures.len(),
by_value_count,
by_ref_count,
by_mut_ref_count,
estimated_heap_usage: self.estimate_heap_usage(captures),
}
}
fn estimate_heap_usage(&self, captures: &[CaptureInfo]) -> usize {
captures
.iter()
.filter(|c| c.mode == CaptureMode::ByValue)
.filter(|c| self.is_heap_allocated_type(&c.var_type))
.map(|c| c.size)
.sum()
}
fn is_heap_allocated_type(&self, type_name: &str) -> bool {
type_name.contains("Vec")
|| type_name.contains("String")
|| type_name.contains("HashMap")
|| type_name.contains("Box")
|| type_name.contains("Arc")
|| type_name.contains("Rc")
}
fn analyze_optimization_potential(&self, captures: &[CaptureInfo]) -> OptimizationPotential {
let mut suggestions = Vec::new();
let mut potential_savings = 0;
for capture in captures {
if capture.mode == CaptureMode::ByValue && capture.size > 64 {
suggestions.push(format!(
"Consider capturing '{}' by reference instead of by value to save {} bytes",
capture.var_name, capture.size
));
potential_savings += capture.size;
}
}
let mut_captures = captures
.iter()
.filter(|c| c.mode == CaptureMode::ByMutableReference)
.count();
if mut_captures > captures.len() / 2 {
suggestions.push("Consider if all mutable captures are necessary".to_string());
}
let heap_captures = captures
.iter()
.filter(|c| c.mode == CaptureMode::ByValue && self.is_heap_allocated_type(&c.var_type))
.count();
if heap_captures > 0 {
suggestions
.push("Consider using move semantics for heap-allocated captures".to_string());
}
OptimizationPotential {
level: if potential_savings > 256 {
OptimizationLevel::High
} else if potential_savings > 64 {
OptimizationLevel::Medium
} else if !suggestions.is_empty() {
OptimizationLevel::Low
} else {
OptimizationLevel::None
},
potential_savings,
suggestions,
}
}
fn calculate_capture_statistics(
&self,
closures: &HashMap<usize, ClosureInfo>,
) -> CaptureStatistics {
let total_closures = closures.len();
let total_captures = closures.values().map(|c| c.captures.len()).sum();
let mut by_mode = HashMap::new();
let mut by_type = HashMap::new();
let mut total_memory = 0;
for closure in closures.values() {
total_memory += closure.memory_footprint.total_size;
for capture in &closure.captures {
*by_mode.entry(capture.mode.clone()).or_insert(0) += 1;
*by_type.entry(capture.var_type.clone()).or_insert(0) += 1;
}
}
let avg_captures_per_closure = if total_closures > 0 {
total_captures as f64 / total_closures as f64
} else {
0.0
};
CaptureStatistics {
total_closures,
total_captures,
avg_captures_per_closure,
total_memory_usage: total_memory,
captures_by_mode: by_mode,
captures_by_type: by_type,
}
}
fn generate_optimization_suggestions(
&self,
closures: &[DetectedClosure],
) -> Vec<OptimizationSuggestion> {
let mut suggestions = Vec::new();
let high_memory_closures = closures
.iter()
.filter(|c| matches!(c.memory_impact, MemoryImpact::High | MemoryImpact::VeryHigh))
.count();
if high_memory_closures > 0 {
suggestions.push(OptimizationSuggestion {
category: OptimizationCategory::Memory,
priority: SuggestionPriority::High,
description: format!(
"Found {high_memory_closures} closures with high memory usage",
),
recommendation: "Consider reducing capture size or using references".to_string(),
estimated_impact: "20-50% memory reduction".to_string(),
});
}
let fnonce_count = closures
.iter()
.filter(|c| c.closure_type == ClosureType::FnOnce)
.count();
if fnonce_count > closures.len() / 2 {
suggestions.push(OptimizationSuggestion {
category: OptimizationCategory::Performance,
priority: SuggestionPriority::Medium,
description: "Many FnOnce closures detected".to_string(),
recommendation: "Consider if Fn or FnMut traits would be more appropriate"
.to_string(),
estimated_impact: "Improved reusability".to_string(),
});
}
suggestions
}
fn analyze_capture_lifetimes(&self) -> LifetimeAnalysis {
if let Ok(graph) = self.lifetime_graph.lock() {
graph.analyze_lifetimes()
} else {
LifetimeAnalysis::default()
}
}
}
impl Default for ClosureAnalyzer {
fn default() -> Self {
Self::new()
}
}
fn current_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
fn capture_call_site() -> String {
format!("{}:{}", file!(), line!())
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_closure_analyzer_creation() {
let analyzer = ClosureAnalyzer::new();
let closures = analyzer.closures.lock().unwrap();
let events = analyzer.capture_events.lock().unwrap();
assert!(closures.is_empty(), "New analyzer should have no closures");
assert!(
events.is_empty(),
"New analyzer should have no capture events"
);
}
#[test]
fn test_closure_analyzer_default() {
let analyzer = ClosureAnalyzer::default();
let closures = analyzer.closures.lock().unwrap();
assert!(
closures.is_empty(),
"Default analyzer should have no closures"
);
}
#[test]
fn test_register_closure() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![CaptureInfo {
var_name: "x".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
}];
analyzer.register_closure(0x5000, captures);
let closures = analyzer.closures.lock().unwrap();
assert!(
closures.contains_key(&0x5000),
"Should contain registered closure"
);
let events = analyzer.capture_events.lock().unwrap();
assert_eq!(events.len(), 1, "Should have one capture event");
assert_eq!(
events[0].event_type,
CaptureEventType::Captured,
"Event should be Captured"
);
}
#[test]
fn test_register_closure_multiple_captures() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "a".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
},
CaptureInfo {
var_name: "b".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByReference,
var_type: "String".to_string(),
size: 24,
lifetime_bound: None,
},
CaptureInfo {
var_name: "c".to_string(),
var_ptr: 0x3000,
mode: CaptureMode::ByMutableReference,
var_type: "Vec<u8>".to_string(),
size: 24,
lifetime_bound: None,
},
];
analyzer.register_closure(0x6000, captures);
let events = analyzer.capture_events.lock().unwrap();
assert_eq!(events.len(), 3, "Should have three capture events");
}
#[test]
fn test_track_closure_drop() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![CaptureInfo {
var_name: "x".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
}];
analyzer.register_closure(0x5000, captures);
analyzer.track_closure_drop(0x5000);
let closures = analyzer.closures.lock().unwrap();
assert!(
!closures.contains_key(&0x5000),
"Should not contain dropped closure"
);
let events = analyzer.capture_events.lock().unwrap();
assert_eq!(events.len(), 2, "Should have captured and released events");
assert_eq!(
events[1].event_type,
CaptureEventType::Released,
"Second event should be Released"
);
}
#[test]
fn test_track_closure_drop_nonexistent() {
let analyzer = ClosureAnalyzer::new();
analyzer.track_closure_drop(0xdead);
let closures = analyzer.closures.lock().unwrap();
assert!(closures.is_empty(), "Should still be empty");
}
#[test]
fn test_is_closure_type() {
let analyzer = ClosureAnalyzer::new();
assert!(
analyzer.is_closure_type("closure"),
"Should detect 'closure'"
);
assert!(
analyzer.is_closure_type("{{closure}}"),
"Should detect '{{closure}}'"
);
assert!(
analyzer.is_closure_type("fn() -> i32"),
"Should detect 'fn('"
);
assert!(
analyzer.is_closure_type("dyn Fn()"),
"Should detect 'dyn Fn'"
);
assert!(
analyzer.is_closure_type("impl FnMut()"),
"Should detect 'impl Fn'"
);
assert!(
!analyzer.is_closure_type("String"),
"Should not detect 'String'"
);
assert!(
!analyzer.is_closure_type("Vec<u8>"),
"Should not detect 'Vec<u8>'"
);
}
#[test]
fn test_estimate_captures_from_size() {
let analyzer = ClosureAnalyzer::new();
assert_eq!(
analyzer.estimate_captures_from_size(0),
0,
"Size 0 should estimate 0 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(8),
0,
"Size <= 8 should estimate 0 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(16),
2,
"Size 9-32 should estimate 2 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(32),
2,
"Size 32 should estimate 2 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(64),
8,
"Size 33-128 should estimate 8 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(128),
8,
"Size 128 should estimate 8 captures"
);
assert_eq!(
analyzer.estimate_captures_from_size(256),
16,
"Size > 128 should estimate size/16 captures"
);
}
#[test]
fn test_classify_closure_type() {
let analyzer = ClosureAnalyzer::new();
assert_eq!(
analyzer.classify_closure_type("FnOnce"),
ClosureType::FnOnce,
"Should classify FnOnce"
);
assert_eq!(
analyzer.classify_closure_type("impl FnOnce"),
ClosureType::FnOnce,
"Should classify impl FnOnce"
);
assert_eq!(
analyzer.classify_closure_type("FnMut"),
ClosureType::FnMut,
"Should classify FnMut"
);
assert_eq!(
analyzer.classify_closure_type("dyn FnMut"),
ClosureType::FnMut,
"Should classify dyn FnMut"
);
assert_eq!(
analyzer.classify_closure_type("Fn"),
ClosureType::Fn,
"Should classify Fn"
);
assert_eq!(
analyzer.classify_closure_type("dyn Fn"),
ClosureType::Fn,
"Should classify dyn Fn"
);
assert_eq!(
analyzer.classify_closure_type("SomeType"),
ClosureType::Unknown,
"Should classify unknown"
);
}
#[test]
fn test_assess_memory_impact() {
let analyzer = ClosureAnalyzer::new();
assert_eq!(
analyzer.assess_memory_impact(0),
MemoryImpact::Minimal,
"Size 0 should be Minimal"
);
assert_eq!(
analyzer.assess_memory_impact(16),
MemoryImpact::Minimal,
"Size 16 should be Minimal"
);
assert_eq!(
analyzer.assess_memory_impact(32),
MemoryImpact::Low,
"Size 32 should be Low"
);
assert_eq!(
analyzer.assess_memory_impact(64),
MemoryImpact::Low,
"Size 64 should be Low"
);
assert_eq!(
analyzer.assess_memory_impact(128),
MemoryImpact::Medium,
"Size 128 should be Medium"
);
assert_eq!(
analyzer.assess_memory_impact(256),
MemoryImpact::Medium,
"Size 256 should be Medium"
);
assert_eq!(
analyzer.assess_memory_impact(512),
MemoryImpact::High,
"Size 512 should be High"
);
assert_eq!(
analyzer.assess_memory_impact(1024),
MemoryImpact::High,
"Size 1024 should be High"
);
assert_eq!(
analyzer.assess_memory_impact(2048),
MemoryImpact::VeryHigh,
"Size 2048 should be VeryHigh"
);
}
#[test]
fn test_calculate_closure_footprint() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "a".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
},
CaptureInfo {
var_name: "b".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByReference,
var_type: "String".to_string(),
size: 8,
lifetime_bound: None,
},
CaptureInfo {
var_name: "c".to_string(),
var_ptr: 0x3000,
mode: CaptureMode::ByMutableReference,
var_type: "Vec<u8>".to_string(),
size: 8,
lifetime_bound: None,
},
];
let footprint = analyzer.calculate_closure_footprint(&captures);
assert_eq!(footprint.total_size, 20, "Total size should be 20");
assert_eq!(footprint.capture_count, 3, "Capture count should be 3");
assert_eq!(footprint.by_value_count, 1, "By value count should be 1");
assert_eq!(footprint.by_ref_count, 1, "By ref count should be 1");
assert_eq!(
footprint.by_mut_ref_count, 1,
"By mut ref count should be 1"
);
}
#[test]
fn test_is_heap_allocated_type() {
let analyzer = ClosureAnalyzer::new();
assert!(
analyzer.is_heap_allocated_type("Vec<u8>"),
"Vec should be heap type"
);
assert!(
analyzer.is_heap_allocated_type("String"),
"String should be heap type"
);
assert!(
analyzer.is_heap_allocated_type("HashMap<K, V>"),
"HashMap should be heap type"
);
assert!(
analyzer.is_heap_allocated_type("Box<T>"),
"Box should be heap type"
);
assert!(
analyzer.is_heap_allocated_type("Arc<T>"),
"Arc should be heap type"
);
assert!(
analyzer.is_heap_allocated_type("Rc<T>"),
"Rc should be heap type"
);
assert!(
!analyzer.is_heap_allocated_type("i32"),
"i32 should not be heap type"
);
assert!(
!analyzer.is_heap_allocated_type("&str"),
"&str should not be heap type"
);
}
#[test]
fn test_estimate_heap_usage() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "vec".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "Vec<u8>".to_string(),
size: 24,
lifetime_bound: None,
},
CaptureInfo {
var_name: "s".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByValue,
var_type: "String".to_string(),
size: 24,
lifetime_bound: None,
},
CaptureInfo {
var_name: "num".to_string(),
var_ptr: 0x3000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
},
CaptureInfo {
var_name: "ref_vec".to_string(),
var_ptr: 0x4000,
mode: CaptureMode::ByReference,
var_type: "Vec<u8>".to_string(),
size: 8,
lifetime_bound: None,
},
];
let heap_usage = analyzer.estimate_heap_usage(&captures);
assert_eq!(heap_usage, 48, "Should sum Vec and String sizes (24+24)");
}
#[test]
fn test_analyze_optimization_potential_large_value() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![CaptureInfo {
var_name: "large_data".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "[u8; 128]".to_string(),
size: 128,
lifetime_bound: None,
}];
let potential = analyzer.analyze_optimization_potential(&captures);
assert_eq!(
potential.level,
OptimizationLevel::Medium,
"Large capture should have Medium optimization level"
);
assert_eq!(
potential.potential_savings, 128,
"Potential savings should be 128"
);
assert!(
potential
.suggestions
.iter()
.any(|s| s.contains("reference")),
"Should suggest reference capture"
);
}
#[test]
fn test_analyze_optimization_potential_high_savings() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "data1".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "[u8; 128]".to_string(),
size: 128,
lifetime_bound: None,
},
CaptureInfo {
var_name: "data2".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByValue,
var_type: "[u8; 128]".to_string(),
size: 128,
lifetime_bound: None,
},
CaptureInfo {
var_name: "data3".to_string(),
var_ptr: 0x3000,
mode: CaptureMode::ByValue,
var_type: "[u8; 128]".to_string(),
size: 128,
lifetime_bound: None,
},
];
let potential = analyzer.analyze_optimization_potential(&captures);
assert_eq!(
potential.level,
OptimizationLevel::High,
"Savings > 256 should have High optimization level"
);
assert_eq!(
potential.potential_savings, 384,
"Potential savings should be 384 (128 * 3)"
);
}
#[test]
fn test_analyze_optimization_potential_many_mutable() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "a".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByMutableReference,
var_type: "i32".to_string(),
size: 8,
lifetime_bound: None,
},
CaptureInfo {
var_name: "b".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByMutableReference,
var_type: "i32".to_string(),
size: 8,
lifetime_bound: None,
},
CaptureInfo {
var_name: "c".to_string(),
var_ptr: 0x3000,
mode: CaptureMode::ByReference,
var_type: "i32".to_string(),
size: 8,
lifetime_bound: None,
},
];
let potential = analyzer.analyze_optimization_potential(&captures);
assert!(
potential.suggestions.iter().any(|s| s.contains("mutable")),
"Should suggest reviewing mutable captures"
);
}
#[test]
fn test_analyze_optimization_potential_heap_captures() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![CaptureInfo {
var_name: "vec".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "Vec<u8>".to_string(),
size: 24,
lifetime_bound: None,
}];
let potential = analyzer.analyze_optimization_potential(&captures);
assert!(
potential
.suggestions
.iter()
.any(|s| s.contains("move") || s.contains("heap")),
"Should suggest move semantics for heap captures"
);
}
#[test]
fn test_analyze_optimization_potential_none() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![CaptureInfo {
var_name: "small".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByReference,
var_type: "i32".to_string(),
size: 8,
lifetime_bound: None,
}];
let potential = analyzer.analyze_optimization_potential(&captures);
assert_eq!(
potential.level,
OptimizationLevel::None,
"Small reference capture should have None optimization level"
);
assert_eq!(
potential.potential_savings, 0,
"Potential savings should be 0"
);
}
#[test]
fn test_calculate_capture_statistics() {
let analyzer = ClosureAnalyzer::new();
let mut closures = HashMap::new();
closures.insert(
0x1000,
ClosureInfo {
ptr: 0x1000,
captures: vec![
CaptureInfo {
var_name: "a".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
},
CaptureInfo {
var_name: "b".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByReference,
var_type: "String".to_string(),
size: 8,
lifetime_bound: None,
},
],
creation_timestamp: 1000,
thread_id: "main".to_string(),
call_site: "test.rs:1".to_string(),
memory_footprint: ClosureFootprint {
total_size: 12,
capture_count: 2,
by_value_count: 1,
by_ref_count: 1,
by_mut_ref_count: 0,
estimated_heap_usage: 0,
},
optimization_potential: OptimizationPotential {
level: OptimizationLevel::None,
potential_savings: 0,
suggestions: vec![],
},
},
);
let stats = analyzer.calculate_capture_statistics(&closures);
assert_eq!(stats.total_closures, 1, "Should have 1 closure");
assert_eq!(stats.total_captures, 2, "Should have 2 captures");
assert_eq!(stats.avg_captures_per_closure, 2.0, "Average should be 2.0");
assert_eq!(stats.total_memory_usage, 12, "Total memory should be 12");
}
#[test]
fn test_calculate_capture_statistics_empty() {
let analyzer = ClosureAnalyzer::new();
let closures = HashMap::new();
let stats = analyzer.calculate_capture_statistics(&closures);
assert_eq!(stats.total_closures, 0, "Should have 0 closures");
assert_eq!(stats.total_captures, 0, "Should have 0 captures");
assert_eq!(stats.avg_captures_per_closure, 0.0, "Average should be 0.0");
}
#[test]
fn test_generate_optimization_suggestions_high_memory() {
let analyzer = ClosureAnalyzer::new();
let closures = vec![DetectedClosure {
ptr: 0x1000,
type_name: "closure".to_string(),
size: 1024,
estimated_captures: 10,
closure_type: ClosureType::Fn,
creation_context: CreationContext {
scope_name: None,
thread_id: "main".to_string(),
timestamp: 1000,
},
memory_impact: MemoryImpact::High,
}];
let suggestions = analyzer.generate_optimization_suggestions(&closures);
assert!(
suggestions
.iter()
.any(|s| s.category == OptimizationCategory::Memory),
"Should have memory suggestion"
);
assert!(
suggestions
.iter()
.any(|s| s.priority == SuggestionPriority::High),
"Should have high priority suggestion"
);
}
#[test]
fn test_generate_optimization_suggestions_many_fnonce() {
let analyzer = ClosureAnalyzer::new();
let closures = vec![
DetectedClosure {
ptr: 0x1000,
type_name: "FnOnce".to_string(),
size: 64,
estimated_captures: 2,
closure_type: ClosureType::FnOnce,
creation_context: CreationContext {
scope_name: None,
thread_id: "main".to_string(),
timestamp: 1000,
},
memory_impact: MemoryImpact::Low,
},
DetectedClosure {
ptr: 0x2000,
type_name: "FnOnce".to_string(),
size: 64,
estimated_captures: 2,
closure_type: ClosureType::FnOnce,
creation_context: CreationContext {
scope_name: None,
thread_id: "main".to_string(),
timestamp: 1000,
},
memory_impact: MemoryImpact::Low,
},
DetectedClosure {
ptr: 0x3000,
type_name: "Fn".to_string(),
size: 64,
estimated_captures: 2,
closure_type: ClosureType::Fn,
creation_context: CreationContext {
scope_name: None,
thread_id: "main".to_string(),
timestamp: 1000,
},
memory_impact: MemoryImpact::Low,
},
];
let suggestions = analyzer.generate_optimization_suggestions(&closures);
assert!(
suggestions.iter().any(|s| s.description.contains("FnOnce")),
"Should mention FnOnce closures"
);
}
#[test]
fn test_analyze_capture_lifetimes() {
let analyzer = ClosureAnalyzer::new();
let captures = vec![
CaptureInfo {
var_name: "a".to_string(),
var_ptr: 0x1000,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
},
CaptureInfo {
var_name: "b".to_string(),
var_ptr: 0x2000,
mode: CaptureMode::ByReference,
var_type: "String".to_string(),
size: 8,
lifetime_bound: None,
},
];
analyzer.register_closure(0x5000, captures);
let analysis = analyzer.analyze_capture_lifetimes();
assert_eq!(
analysis.total_relationships, 1,
"Should have 1 relationship"
);
}
#[test]
fn test_global_closure_analyzer_singleton() {
let analyzer1 = get_global_closure_analyzer();
let analyzer2 = get_global_closure_analyzer();
assert!(
Arc::ptr_eq(&analyzer1, &analyzer2),
"Should return same instance"
);
}
#[test]
fn test_current_timestamp() {
let ts = current_timestamp();
assert!(ts > 0, "Timestamp should be positive");
}
#[test]
fn test_capture_call_site() {
let site = capture_call_site();
assert!(!site.is_empty(), "Call site should not be empty");
assert!(
site.contains(".rs"),
"Call site should contain .rs extension"
);
}
#[test]
fn test_analyze_closure_patterns_with_allocations() {
let analyzer = ClosureAnalyzer::new();
let allocations = vec![AllocationInfo {
ptr: 0x1000,
size: 64,
var_name: Some("closure_var".to_string()),
type_name: Some("closure".to_string()),
scope_name: Some("test_scope".to_string()),
timestamp_alloc: 1000,
timestamp_dealloc: None,
thread_id: thread::current().id(),
thread_id_u64: 1,
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,
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,
stack_ptr: None,
task_id: None,
}];
let report = analyzer.analyze_closure_patterns(&allocations);
assert_eq!(report.detected_closures.len(), 1, "Should detect 1 closure");
assert!(report.analysis_timestamp > 0, "Should have timestamp");
}
#[test]
fn test_analyze_closure_patterns_non_closure() {
let analyzer = ClosureAnalyzer::new();
let allocations = vec![AllocationInfo {
ptr: 0x1000,
size: 64,
var_name: Some("string_var".to_string()),
type_name: Some("String".to_string()),
scope_name: Some("test_scope".to_string()),
timestamp_alloc: 1000,
timestamp_dealloc: None,
thread_id: thread::current().id(),
thread_id_u64: 1,
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,
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,
stack_ptr: None,
task_id: None,
}];
let report = analyzer.analyze_closure_patterns(&allocations);
assert_eq!(
report.detected_closures.len(),
0,
"Should not detect non-closure"
);
}
#[test]
fn test_analyze_closure_allocation() {
let analyzer = ClosureAnalyzer::new();
let allocation = AllocationInfo {
ptr: 0x1000,
size: 128,
var_name: Some("my_closure".to_string()),
type_name: Some("dyn FnMut()".to_string()),
scope_name: Some("test_scope".to_string()),
timestamp_alloc: 1000,
timestamp_dealloc: None,
thread_id: thread::current().id(),
thread_id_u64: 1,
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,
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,
stack_ptr: None,
task_id: None,
};
let detected = analyzer.analyze_closure_allocation(&allocation);
assert!(detected.is_some(), "Should detect closure");
let closure = detected.unwrap();
assert_eq!(closure.ptr, 0x1000, "Pointer should match");
assert_eq!(
closure.closure_type,
ClosureType::FnMut,
"Should classify as FnMut"
);
assert_eq!(
closure.memory_impact,
MemoryImpact::Medium,
"Size 128 should be Medium impact"
);
}
#[test]
fn test_concurrent_access() {
let analyzer = Arc::new(ClosureAnalyzer::new());
let mut handles = vec![];
for i in 0..5 {
let analyzer_clone = analyzer.clone();
let handle = thread::spawn(move || {
let captures = vec![CaptureInfo {
var_name: format!("var{}", i),
var_ptr: 0x1000 + i,
mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
lifetime_bound: None,
}];
analyzer_clone.register_closure(0x5000 + i, captures);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let closures = analyzer.closures.lock().unwrap();
assert_eq!(closures.len(), 5, "Should have 5 closures from 5 threads");
}
}