use crate::capture::types::AllocationInfo;
use crate::core::safe_operations::SafeLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
static GLOBAL_LIFECYCLE_ANALYZER: OnceLock<Arc<LifecycleAnalyzer>> = OnceLock::new();
pub fn get_global_lifecycle_analyzer() -> Arc<LifecycleAnalyzer> {
GLOBAL_LIFECYCLE_ANALYZER
.get_or_init(|| Arc::new(LifecycleAnalyzer::new()))
.clone()
}
pub struct LifecycleAnalyzer {
drop_events: Mutex<Arc<Vec<DropEvent>>>,
raii_patterns: Mutex<Arc<Vec<RAIIPattern>>>,
borrow_tracker: Mutex<BorrowTracker>,
closure_captures: Mutex<Arc<Vec<ClosureCapture>>>,
}
impl LifecycleAnalyzer {
pub fn new() -> Self {
Self {
drop_events: Mutex::new(Arc::new(Vec::new())),
raii_patterns: Mutex::new(Arc::new(Vec::new())),
borrow_tracker: Mutex::new(BorrowTracker::new()),
closure_captures: Mutex::new(Arc::new(Vec::new())),
}
}
pub fn record_drop_event(&self, ptr: usize, type_name: &str, custom_drop: bool) {
let event = DropEvent {
ptr,
type_name: type_name.to_string(),
timestamp: current_timestamp(),
custom_drop,
thread_id: format!("{:?}", std::thread::current().id()),
call_stack: capture_call_stack(),
};
if let Ok(mut events) = self.drop_events.safe_lock() {
let mut new_events = (**events).clone();
new_events.push(event);
*events = Arc::new(new_events);
}
}
pub fn detect_raii_patterns(&self, allocations: &[AllocationInfo]) -> Vec<RAIIPattern> {
let mut patterns = Vec::new();
for allocation in allocations {
if let Some(type_name) = &allocation.type_name {
if let Some(pattern) = self.analyze_raii_pattern(allocation, type_name) {
patterns.push(pattern);
}
}
}
if let Ok(mut stored_patterns) = self.raii_patterns.safe_lock() {
let mut new_patterns = (**stored_patterns).clone();
new_patterns.extend(patterns.clone());
*stored_patterns = Arc::new(new_patterns);
}
patterns
}
fn analyze_raii_pattern(
&self,
allocation: &AllocationInfo,
type_name: &str,
) -> Option<RAIIPattern> {
let resource_type = self.identify_resource_type(type_name)?;
let acquisition_method = self.identify_acquisition_method(type_name);
let release_method = self.identify_release_method(type_name);
Some(RAIIPattern {
ptr: allocation.ptr,
type_name: type_name.to_string(),
resource_type,
acquisition_method,
release_method,
acquisition_timestamp: allocation.timestamp_alloc,
release_timestamp: allocation.timestamp_dealloc,
scope_info: self.analyze_scope_info(allocation),
is_exception_safe: self.is_exception_safe(type_name),
})
}
fn identify_resource_type(&self, type_name: &str) -> Option<ResourceType> {
if type_name.contains("File")
|| type_name.contains("BufReader")
|| type_name.contains("BufWriter")
{
Some(ResourceType::FileHandle)
} else if type_name.contains("TcpStream")
|| type_name.contains("UdpSocket")
|| type_name.contains("Listener")
{
Some(ResourceType::NetworkSocket)
} else if type_name.contains("Mutex")
|| type_name.contains("RwLock")
|| type_name.contains("Semaphore")
{
Some(ResourceType::SynchronizationPrimitive)
} else if type_name.contains("Thread") || type_name.contains("JoinHandle") {
Some(ResourceType::ThreadHandle)
} else if type_name.contains("Box")
|| type_name.contains("Vec")
|| type_name.contains("String")
{
Some(ResourceType::Memory)
} else if type_name.contains("Guard") || type_name.contains("Lock") {
Some(ResourceType::LockGuard)
} else {
None
}
}
fn identify_acquisition_method(&self, type_name: &str) -> AcquisitionMethod {
if type_name.contains("new") || type_name.contains("Box") {
AcquisitionMethod::Constructor
} else if type_name.contains("open") || type_name.contains("connect") {
AcquisitionMethod::SystemCall
} else if type_name.contains("lock") || type_name.contains("Guard") {
AcquisitionMethod::Lock
} else if type_name.contains("with_capacity") || type_name.contains("reserve") {
AcquisitionMethod::Allocation
} else {
AcquisitionMethod::Unknown
}
}
fn identify_release_method(&self, type_name: &str) -> ReleaseMethod {
if type_name.contains("Guard")
|| type_name.contains("Lock")
|| type_name.contains("File")
|| type_name.contains("Stream")
{
ReleaseMethod::AutomaticDrop
} else if type_name.contains("Box") || type_name.contains("Vec") {
ReleaseMethod::Deallocation
} else {
ReleaseMethod::CustomDrop
}
}
fn analyze_scope_info(&self, allocation: &AllocationInfo) -> ScopeInfo {
ScopeInfo {
scope_name: allocation
.scope_name
.clone()
.unwrap_or_else(|| "unknown".to_string()),
scope_type: self.infer_scope_type(&allocation.scope_name),
nesting_level: self.calculate_nesting_level(&allocation.scope_name),
}
}
fn infer_scope_type(&self, scope_name: &Option<String>) -> ScopeType {
match scope_name {
Some(name) if name.contains("fn ") => ScopeType::Function,
Some(name) if name.contains("impl ") => ScopeType::Method,
Some(name) if name.contains("for ") || name.contains("while ") => ScopeType::Loop,
Some(name) if name.contains("if ") || name.contains("match ") => ScopeType::Conditional,
Some(name) if name.contains("{") => ScopeType::Block,
_ => ScopeType::Unknown,
}
}
fn calculate_nesting_level(&self, scope_name: &Option<String>) -> usize {
scope_name
.as_ref()
.map(|name| name.matches('{').count())
.unwrap_or(0)
}
fn is_exception_safe(&self, type_name: &str) -> bool {
!type_name.contains("unsafe") && !type_name.contains("ffi")
}
pub fn track_borrow(&self, ptr: usize, borrow_type: BorrowType, location: &str) {
if let Ok(mut tracker) = self.borrow_tracker.safe_lock() {
tracker.track_borrow(ptr, borrow_type, location);
}
}
pub fn track_borrow_release(&self, ptr: usize, borrow_id: u64) {
if let Ok(mut tracker) = self.borrow_tracker.safe_lock() {
tracker.release_borrow(ptr, borrow_id);
}
}
pub fn analyze_closure_capture(
&self,
closure_ptr: usize,
captured_vars: Vec<CapturedVariable>,
) {
let capture = ClosureCapture {
closure_ptr,
captured_vars,
capture_timestamp: current_timestamp(),
thread_id: format!("{:?}", std::thread::current().id()),
};
if let Ok(mut captures) = self.closure_captures.safe_lock() {
let mut new_captures = (**captures).clone();
new_captures.push(capture);
*captures = Arc::new(new_captures);
}
}
pub fn get_lifecycle_report(&self) -> LifecycleAnalysisReport {
let drop_events = self
.drop_events
.safe_lock()
.map(|events| Arc::clone(&events))
.unwrap_or_else(|_| Arc::new(Vec::new()));
let raii_patterns = self
.raii_patterns
.safe_lock()
.map(|patterns| Arc::clone(&patterns))
.unwrap_or_else(|_| Arc::new(Vec::new()));
let borrow_analysis = self
.borrow_tracker
.safe_lock()
.map(|tracker| tracker.get_analysis())
.unwrap_or_else(|_| BorrowAnalysis {
conflicts: Vec::new(),
active_borrows: 0,
borrow_patterns: Vec::new(),
long_lived_borrows: Vec::new(),
total_borrows: 0,
});
let closure_captures = self
.closure_captures
.safe_lock()
.map(|captures| Arc::clone(&captures))
.unwrap_or_else(|_| Arc::new(Vec::new()));
LifecycleAnalysisReport {
drop_events: (*drop_events).clone(),
raii_patterns: (*raii_patterns).clone(),
borrow_analysis,
closure_captures: (*closure_captures).clone(),
analysis_timestamp: current_timestamp(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropEvent {
pub ptr: usize,
pub type_name: String,
pub timestamp: u64,
pub custom_drop: bool,
pub thread_id: String,
pub call_stack: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RAIIPattern {
pub ptr: usize,
pub type_name: String,
pub resource_type: ResourceType,
pub acquisition_method: AcquisitionMethod,
pub release_method: ReleaseMethod,
pub acquisition_timestamp: u64,
pub release_timestamp: Option<u64>,
pub scope_info: ScopeInfo,
pub is_exception_safe: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ResourceType {
Memory,
FileHandle,
NetworkSocket,
SynchronizationPrimitive,
ThreadHandle,
LockGuard,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AcquisitionMethod {
Constructor,
SystemCall,
Lock,
Allocation,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ReleaseMethod {
AutomaticDrop,
CustomDrop,
Deallocation,
SystemCall,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeInfo {
pub scope_name: String,
pub scope_type: ScopeType,
pub nesting_level: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ScopeType {
Function,
Method,
Block,
Loop,
Conditional,
Unknown,
}
#[derive(Debug)]
pub struct BorrowTracker {
active_borrows: HashMap<usize, Vec<BorrowInfo>>,
borrow_history: Vec<BorrowEvent>,
next_borrow_id: u64,
}
impl Default for BorrowTracker {
fn default() -> Self {
Self::new()
}
}
impl BorrowTracker {
pub fn new() -> Self {
Self {
active_borrows: HashMap::new(),
borrow_history: Vec::new(),
next_borrow_id: 1,
}
}
pub fn track_borrow(&mut self, ptr: usize, borrow_type: BorrowType, location: &str) -> u64 {
let borrow_id = self.next_borrow_id;
self.next_borrow_id += 1;
let borrow_info = BorrowInfo {
borrow_id,
borrow_type,
start_timestamp: current_timestamp(),
location: location.to_string(),
thread_id: format!("{:?}", std::thread::current().id()),
};
self.active_borrows
.entry(ptr)
.or_default()
.push(borrow_info.clone());
self.borrow_history.push(BorrowEvent {
ptr,
borrow_info,
event_type: BorrowEventType::Acquired,
timestamp: current_timestamp(),
});
borrow_id
}
pub fn release_borrow(&mut self, ptr: usize, borrow_id: u64) {
if let Some(borrows) = self.active_borrows.get_mut(&ptr) {
if let Some(pos) = borrows.iter().position(|b| b.borrow_id == borrow_id) {
let borrow_info = borrows.remove(pos);
self.borrow_history.push(BorrowEvent {
ptr,
borrow_info,
event_type: BorrowEventType::Released,
timestamp: current_timestamp(),
});
if borrows.is_empty() {
self.active_borrows.remove(&ptr);
}
}
}
}
pub fn get_analysis(&self) -> BorrowAnalysis {
let mut conflicts = Vec::new();
let mut long_lived_borrows = Vec::new();
for events in self.borrow_history.windows(2) {
if let [event1, event2] = events {
if event1.ptr == event2.ptr
&& self.has_borrow_conflict(&event1.borrow_info, &event2.borrow_info)
{
conflicts.push(BorrowConflict {
ptr: event1.ptr,
first_borrow: event1.borrow_info.clone(),
second_borrow: event2.borrow_info.clone(),
conflict_type: self
.classify_conflict(&event1.borrow_info, &event2.borrow_info),
});
}
}
}
let current_time = current_timestamp();
for (ptr, borrows) in &self.active_borrows {
for borrow in borrows {
if current_time - borrow.start_timestamp > 1_000_000_000 {
long_lived_borrows.push(LongLivedBorrow {
ptr: *ptr,
borrow_info: borrow.clone(),
duration_ns: current_time - borrow.start_timestamp,
});
}
}
}
BorrowAnalysis {
total_borrows: self.borrow_history.len(),
active_borrows: self.active_borrows.len(),
conflicts,
long_lived_borrows,
borrow_patterns: self.analyze_borrow_patterns(),
}
}
fn has_borrow_conflict(&self, borrow1: &BorrowInfo, borrow2: &BorrowInfo) -> bool {
matches!(
(&borrow1.borrow_type, &borrow2.borrow_type),
(BorrowType::Mutable, _) | (_, BorrowType::Mutable)
)
}
fn classify_conflict(&self, borrow1: &BorrowInfo, borrow2: &BorrowInfo) -> ConflictType {
match (&borrow1.borrow_type, &borrow2.borrow_type) {
(BorrowType::Mutable, BorrowType::Mutable) => ConflictType::MutableMutable,
(BorrowType::Mutable, BorrowType::Immutable)
| (BorrowType::Immutable, BorrowType::Mutable) => ConflictType::MutableImmutable,
_ => ConflictType::None,
}
}
fn analyze_borrow_patterns(&self) -> Vec<BorrowPattern> {
let mut patterns = Vec::new();
let short_borrows = self
.borrow_history
.iter()
.filter(|event| matches!(event.event_type, BorrowEventType::Released))
.filter(|event| {
self.borrow_history.iter().any(|acquire_event| {
acquire_event.borrow_info.borrow_id == event.borrow_info.borrow_id
&& matches!(acquire_event.event_type, BorrowEventType::Acquired)
&& event.timestamp >= acquire_event.timestamp
&& event.timestamp - acquire_event.timestamp < 1_000_000
})
})
.count();
if short_borrows > 10 {
patterns.push(BorrowPattern {
pattern_type: BorrowPatternType::FrequentShortBorrows,
description: format!("Detected {short_borrows} short-lived borrows (< 1ms)",),
impact: if short_borrows > 100 {
PatternImpact::High
} else {
PatternImpact::Medium
},
suggestion: "Consider batching operations to reduce borrow overhead".to_string(),
});
}
patterns
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BorrowType {
Immutable,
Mutable,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BorrowInfo {
pub borrow_id: u64,
pub borrow_type: BorrowType,
pub start_timestamp: u64,
pub location: String,
pub thread_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BorrowEvent {
pub ptr: usize,
pub borrow_info: BorrowInfo,
pub event_type: BorrowEventType,
pub timestamp: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BorrowEventType {
Acquired,
Released,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BorrowAnalysis {
pub total_borrows: usize,
pub active_borrows: usize,
pub conflicts: Vec<BorrowConflict>,
pub long_lived_borrows: Vec<LongLivedBorrow>,
pub borrow_patterns: Vec<BorrowPattern>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BorrowConflict {
pub ptr: usize,
pub first_borrow: BorrowInfo,
pub second_borrow: BorrowInfo,
pub conflict_type: ConflictType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ConflictType {
MutableMutable,
MutableImmutable,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LongLivedBorrow {
pub ptr: usize,
pub borrow_info: BorrowInfo,
pub duration_ns: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BorrowPattern {
pub pattern_type: BorrowPatternType,
pub description: String,
pub impact: PatternImpact,
pub suggestion: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BorrowPatternType {
FrequentShortBorrows,
LongLivedBorrows,
ConflictProne,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PatternImpact {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClosureCapture {
pub closure_ptr: usize,
pub captured_vars: Vec<CapturedVariable>,
pub capture_timestamp: u64,
pub thread_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapturedVariable {
pub var_name: String,
pub var_ptr: usize,
pub capture_mode: CaptureMode,
pub var_type: String,
pub size: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CaptureMode {
ByValue,
ByReference,
ByMutableReference,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LifecycleAnalysisReport {
pub drop_events: Vec<DropEvent>,
pub raii_patterns: Vec<RAIIPattern>,
pub borrow_analysis: BorrowAnalysis,
pub closure_captures: Vec<ClosureCapture>,
pub analysis_timestamp: u64,
}
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
fn capture_call_stack() -> Vec<String> {
#[cfg(feature = "backtrace")]
{
let bt = backtrace::Backtrace::new();
bt.frames()
.iter()
.skip(2) .filter_map(|frame| {
frame
.symbols()
.first()
.and_then(|sym| sym.name())
.map(|name| name.to_string())
})
.collect()
}
#[cfg(not(feature = "backtrace"))]
{
Vec::new()
}
}
impl Default for LifecycleAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lifecycle_analyzer_creation() {
let analyzer = LifecycleAnalyzer::new();
assert!(analyzer.drop_events.lock().is_ok());
assert!(analyzer.raii_patterns.lock().is_ok());
assert!(analyzer.borrow_tracker.lock().is_ok());
assert!(analyzer.closure_captures.lock().is_ok());
}
#[test]
fn test_lifecycle_analyzer_singleton_behavior() {
let analyzer1 = LifecycleAnalyzer::new();
let analyzer2 = LifecycleAnalyzer::new();
assert!(analyzer1.drop_events.lock().is_ok());
assert!(analyzer2.drop_events.lock().is_ok());
}
#[test]
fn test_record_drop_event() {
let analyzer = LifecycleAnalyzer::new();
analyzer.record_drop_event(0x1000, "Vec<i32>", true);
let events = analyzer
.drop_events
.lock()
.expect("Drop events lock should not be poisoned");
assert_eq!(events.len(), 1);
assert_eq!(events[0].ptr, 0x1000);
assert_eq!(events[0].type_name, "Vec<i32>");
assert!(events[0].custom_drop);
assert!(events[0].timestamp > 0);
assert!(!events[0].thread_id.is_empty());
#[cfg(all(target_os = "linux", feature = "backtrace"))]
assert!(!events[0].call_stack.is_empty());
}
#[test]
fn test_identify_resource_type() {
let analyzer = LifecycleAnalyzer::new();
assert_eq!(
analyzer.identify_resource_type("std::fs::File"),
Some(ResourceType::FileHandle)
);
assert_eq!(
analyzer.identify_resource_type("BufReader<File>"),
Some(ResourceType::FileHandle)
);
assert_eq!(
analyzer.identify_resource_type("TcpStream"),
Some(ResourceType::NetworkSocket)
);
assert_eq!(
analyzer.identify_resource_type("UdpSocket"),
Some(ResourceType::NetworkSocket)
);
assert_eq!(
analyzer.identify_resource_type("Mutex<T>"),
Some(ResourceType::SynchronizationPrimitive)
);
assert_eq!(
analyzer.identify_resource_type("RwLock<T>"),
Some(ResourceType::SynchronizationPrimitive)
);
assert_eq!(
analyzer.identify_resource_type("JoinHandle<()>"),
Some(ResourceType::ThreadHandle)
);
assert_eq!(
analyzer.identify_resource_type("Box<dyn Trait>"),
Some(ResourceType::Memory)
);
assert_eq!(
analyzer.identify_resource_type("Vec<i32>"),
Some(ResourceType::Memory)
);
assert_eq!(
analyzer.identify_resource_type("String"),
Some(ResourceType::Memory)
);
assert_eq!(
analyzer.identify_resource_type("MutexGuard<T>"),
Some(ResourceType::SynchronizationPrimitive)
);
assert_eq!(analyzer.identify_resource_type("SomeCustomType"), None);
}
#[test]
fn test_identify_acquisition_method() {
let analyzer = LifecycleAnalyzer::new();
assert_eq!(
analyzer.identify_acquisition_method("Box::new"),
AcquisitionMethod::Constructor
);
assert_eq!(
analyzer.identify_acquisition_method("File::open"),
AcquisitionMethod::SystemCall
);
assert_eq!(
analyzer.identify_acquisition_method("mutex.lock()"),
AcquisitionMethod::Lock
);
assert_eq!(
analyzer.identify_acquisition_method("Vec::with_capacity"),
AcquisitionMethod::Allocation
);
assert_eq!(
analyzer.identify_acquisition_method("unknown_method"),
AcquisitionMethod::Unknown
);
}
#[test]
fn test_identify_release_method() {
let analyzer = LifecycleAnalyzer::new();
assert_eq!(
analyzer.identify_release_method("MutexGuard"),
ReleaseMethod::AutomaticDrop
);
assert_eq!(
analyzer.identify_release_method("File"),
ReleaseMethod::AutomaticDrop
);
assert_eq!(
analyzer.identify_release_method("Box<T>"),
ReleaseMethod::Deallocation
);
assert_eq!(
analyzer.identify_release_method("Vec<T>"),
ReleaseMethod::Deallocation
);
assert_eq!(
analyzer.identify_release_method("CustomType"),
ReleaseMethod::CustomDrop
);
}
#[test]
fn test_infer_scope_type() {
let analyzer = LifecycleAnalyzer::new();
assert_eq!(
analyzer.infer_scope_type(&Some("fn main()".to_string())),
ScopeType::Function
);
assert_eq!(
analyzer.infer_scope_type(&Some("impl MyStruct".to_string())),
ScopeType::Method
);
assert_eq!(
analyzer.infer_scope_type(&Some("for i in 0..10".to_string())),
ScopeType::Loop
);
assert_eq!(
analyzer.infer_scope_type(&Some("while condition".to_string())),
ScopeType::Loop
);
assert_eq!(
analyzer.infer_scope_type(&Some("if condition".to_string())),
ScopeType::Conditional
);
assert_eq!(
analyzer.infer_scope_type(&Some("match value".to_string())),
ScopeType::Conditional
);
assert_eq!(
analyzer.infer_scope_type(&Some("{ block }".to_string())),
ScopeType::Block
);
assert_eq!(analyzer.infer_scope_type(&None), ScopeType::Unknown);
}
#[test]
fn test_calculate_nesting_level() {
let analyzer = LifecycleAnalyzer::new();
assert_eq!(
analyzer.calculate_nesting_level(&Some("fn main() { { } }".to_string())),
2
);
assert_eq!(
analyzer.calculate_nesting_level(&Some("simple".to_string())),
0
);
assert_eq!(analyzer.calculate_nesting_level(&None), 0);
}
#[test]
fn test_is_exception_safe() {
let analyzer = LifecycleAnalyzer::new();
assert!(analyzer.is_exception_safe("Vec<i32>"));
assert!(analyzer.is_exception_safe("String"));
assert!(!analyzer.is_exception_safe("unsafe fn"));
assert!(!analyzer.is_exception_safe("ffi::CString"));
}
#[test]
fn test_detect_raii_patterns() {
let analyzer = LifecycleAnalyzer::new();
let mut allocation = AllocationInfo::new(0x1000, 1024);
allocation.type_name = Some("std::fs::File".to_string());
allocation.scope_name = Some("fn main()".to_string());
let allocations = vec![allocation];
let patterns = analyzer.detect_raii_patterns(&allocations);
assert_eq!(patterns.len(), 1);
let pattern = &patterns[0];
assert_eq!(pattern.ptr, 0x1000);
assert_eq!(pattern.type_name, "std::fs::File");
assert_eq!(pattern.resource_type, ResourceType::FileHandle);
assert_eq!(pattern.acquisition_method, AcquisitionMethod::Unknown);
assert_eq!(pattern.release_method, ReleaseMethod::AutomaticDrop);
assert!(pattern.is_exception_safe);
assert_eq!(pattern.scope_info.scope_name, "fn main()");
assert_eq!(pattern.scope_info.scope_type, ScopeType::Function);
}
#[test]
fn test_borrow_tracker_creation() {
let tracker = BorrowTracker::new();
assert!(tracker.active_borrows.is_empty());
assert!(tracker.borrow_history.is_empty());
assert_eq!(tracker.next_borrow_id, 1);
}
#[test]
fn test_track_borrow() {
let mut tracker = BorrowTracker::new();
let borrow_id = tracker.track_borrow(0x1000, BorrowType::Immutable, "test.rs:10");
assert_eq!(borrow_id, 1);
assert_eq!(tracker.next_borrow_id, 2);
assert!(tracker.active_borrows.contains_key(&0x1000));
assert_eq!(tracker.active_borrows[&0x1000].len(), 1);
assert_eq!(tracker.borrow_history.len(), 1);
let borrow_info = &tracker.active_borrows[&0x1000][0];
assert_eq!(borrow_info.borrow_id, 1);
assert_eq!(borrow_info.borrow_type, BorrowType::Immutable);
assert_eq!(borrow_info.location, "test.rs:10");
assert!(borrow_info.start_timestamp > 0);
}
#[test]
fn test_release_borrow() {
let mut tracker = BorrowTracker::new();
let borrow_id = tracker.track_borrow(0x1000, BorrowType::Mutable, "test.rs:15");
tracker.release_borrow(0x1000, borrow_id);
assert!(!tracker.active_borrows.contains_key(&0x1000));
assert_eq!(tracker.borrow_history.len(), 2);
let release_event = &tracker.borrow_history[1];
assert_eq!(release_event.event_type, BorrowEventType::Released);
assert_eq!(release_event.ptr, 0x1000);
}
#[test]
fn test_borrow_conflict_detection() {
let mut tracker = BorrowTracker::new();
tracker.track_borrow(0x1000, BorrowType::Mutable, "test.rs:20");
tracker.track_borrow(0x1000, BorrowType::Immutable, "test.rs:21");
let analysis = tracker.get_analysis();
assert_eq!(analysis.total_borrows, 2);
assert_eq!(analysis.active_borrows, 1); assert_eq!(analysis.conflicts.len(), 1);
let conflict = &analysis.conflicts[0];
assert_eq!(conflict.ptr, 0x1000);
assert_eq!(conflict.conflict_type, ConflictType::MutableImmutable);
}
#[test]
fn test_has_borrow_conflict() {
let tracker = BorrowTracker::new();
let mutable_borrow = BorrowInfo {
borrow_id: 1,
borrow_type: BorrowType::Mutable,
start_timestamp: 1000,
location: "test.rs:1".to_string(),
thread_id: "thread-1".to_string(),
};
let immutable_borrow = BorrowInfo {
borrow_id: 2,
borrow_type: BorrowType::Immutable,
start_timestamp: 2000,
location: "test.rs:2".to_string(),
thread_id: "thread-1".to_string(),
};
let another_immutable = BorrowInfo {
borrow_id: 3,
borrow_type: BorrowType::Immutable,
start_timestamp: 3000,
location: "test.rs:3".to_string(),
thread_id: "thread-1".to_string(),
};
assert!(tracker.has_borrow_conflict(&mutable_borrow, &immutable_borrow));
assert!(tracker.has_borrow_conflict(&immutable_borrow, &mutable_borrow));
assert!(!tracker.has_borrow_conflict(&immutable_borrow, &another_immutable));
}
#[test]
fn test_classify_conflict() {
let tracker = BorrowTracker::new();
let mutable1 = BorrowInfo {
borrow_id: 1,
borrow_type: BorrowType::Mutable,
start_timestamp: 1000,
location: "test.rs:1".to_string(),
thread_id: "thread-1".to_string(),
};
let mutable2 = BorrowInfo {
borrow_id: 2,
borrow_type: BorrowType::Mutable,
start_timestamp: 2000,
location: "test.rs:2".to_string(),
thread_id: "thread-1".to_string(),
};
let immutable = BorrowInfo {
borrow_id: 3,
borrow_type: BorrowType::Immutable,
start_timestamp: 3000,
location: "test.rs:3".to_string(),
thread_id: "thread-1".to_string(),
};
assert_eq!(
tracker.classify_conflict(&mutable1, &mutable2),
ConflictType::MutableMutable
);
assert_eq!(
tracker.classify_conflict(&mutable1, &immutable),
ConflictType::MutableImmutable
);
assert_eq!(
tracker.classify_conflict(&immutable, &mutable1),
ConflictType::MutableImmutable
);
}
#[test]
fn test_analyze_closure_capture() {
let analyzer = LifecycleAnalyzer::new();
let captured_vars = vec![
CapturedVariable {
var_name: "x".to_string(),
var_ptr: 0x1000,
capture_mode: CaptureMode::ByValue,
var_type: "i32".to_string(),
size: 4,
},
CapturedVariable {
var_name: "y".to_string(),
var_ptr: 0x2000,
capture_mode: CaptureMode::ByReference,
var_type: "&str".to_string(),
size: 8,
},
];
analyzer.analyze_closure_capture(0x5000, captured_vars.clone());
let captures = analyzer
.closure_captures
.lock()
.expect("Closure captures lock should not be poisoned");
assert_eq!(captures.len(), 1);
let capture = &captures[0];
assert_eq!(capture.closure_ptr, 0x5000);
assert_eq!(capture.captured_vars.len(), 2);
assert_eq!(capture.captured_vars[0].var_name, "x");
assert_eq!(capture.captured_vars[0].capture_mode, CaptureMode::ByValue);
assert_eq!(capture.captured_vars[1].var_name, "y");
assert_eq!(
capture.captured_vars[1].capture_mode,
CaptureMode::ByReference
);
assert!(capture.capture_timestamp > 0);
assert!(!capture.thread_id.is_empty());
}
#[test]
fn test_track_borrow_operations() {
let analyzer = LifecycleAnalyzer::new();
analyzer.track_borrow(0x1000, BorrowType::Immutable, "test.rs:30");
analyzer.track_borrow_release(0x1000, 1);
let tracker = analyzer
.borrow_tracker
.lock()
.expect("Borrow tracker lock should not be poisoned");
assert_eq!(tracker.borrow_history.len(), 2);
assert!(!tracker.active_borrows.contains_key(&0x1000));
}
#[test]
fn test_get_lifecycle_report() {
let analyzer = LifecycleAnalyzer::new();
analyzer.record_drop_event(0x1000, "Vec<i32>", true);
let mut allocation = AllocationInfo::new(0x2000, 512);
allocation.type_name = Some("std::fs::File".to_string());
analyzer.detect_raii_patterns(&[allocation]);
analyzer.track_borrow(0x3000, BorrowType::Mutable, "test.rs:40");
let captured_vars = vec![CapturedVariable {
var_name: "data".to_string(),
var_ptr: 0x4000,
capture_mode: CaptureMode::ByValue,
var_type: "String".to_string(),
size: 24,
}];
analyzer.analyze_closure_capture(0x5000, captured_vars);
let report = analyzer.get_lifecycle_report();
assert_eq!(report.drop_events.len(), 1);
assert_eq!(report.raii_patterns.len(), 1);
assert_eq!(report.borrow_analysis.total_borrows, 1);
assert_eq!(report.closure_captures.len(), 1);
assert!(report.analysis_timestamp > 0);
}
#[test]
fn test_borrow_pattern_analysis() {
let mut tracker = BorrowTracker::new();
for i in 0..15 {
let borrow_id = tracker.track_borrow(0x1000 + i, BorrowType::Immutable, "test.rs");
tracker.borrow_history.push(BorrowEvent {
ptr: 0x1000 + i,
borrow_info: BorrowInfo {
borrow_id,
borrow_type: BorrowType::Immutable,
start_timestamp: 1000, location: "test.rs".to_string(),
thread_id: "thread-1".to_string(),
},
event_type: BorrowEventType::Released,
timestamp: 1000 + 500_000, });
}
let analysis = tracker.get_analysis();
if !analysis.borrow_patterns.is_empty() {
let pattern = &analysis.borrow_patterns[0];
assert_eq!(
pattern.pattern_type,
BorrowPatternType::FrequentShortBorrows
);
assert!(pattern.description.contains("short-lived borrows"));
assert!(!pattern.suggestion.is_empty());
}
assert_eq!(analysis.total_borrows, 30); }
#[test]
fn test_enum_variants() {
let resource_types = vec![
ResourceType::Memory,
ResourceType::FileHandle,
ResourceType::NetworkSocket,
ResourceType::SynchronizationPrimitive,
ResourceType::ThreadHandle,
ResourceType::LockGuard,
ResourceType::Other("custom".to_string()),
];
for resource_type in resource_types {
assert!(!format!("{resource_type:?}").is_empty());
}
let acquisition_methods = vec![
AcquisitionMethod::Constructor,
AcquisitionMethod::SystemCall,
AcquisitionMethod::Lock,
AcquisitionMethod::Allocation,
AcquisitionMethod::Unknown,
];
for method in acquisition_methods {
assert!(!format!("{method:?}").is_empty());
}
let release_methods = vec![
ReleaseMethod::AutomaticDrop,
ReleaseMethod::CustomDrop,
ReleaseMethod::Deallocation,
ReleaseMethod::SystemCall,
];
for method in release_methods {
assert!(!format!("{method:?}").is_empty());
}
let scope_types = vec![
ScopeType::Function,
ScopeType::Method,
ScopeType::Block,
ScopeType::Loop,
ScopeType::Conditional,
ScopeType::Unknown,
];
for scope_type in scope_types {
assert!(!format!("{scope_type:?}").is_empty());
}
let borrow_types = vec![BorrowType::Immutable, BorrowType::Mutable];
for borrow_type in borrow_types {
assert!(!format!("{borrow_type:?}").is_empty());
}
let capture_modes = vec![
CaptureMode::ByValue,
CaptureMode::ByReference,
CaptureMode::ByMutableReference,
];
for mode in capture_modes {
assert!(!format!("{mode:?}").is_empty());
}
}
#[test]
fn test_current_timestamp() {
let timestamp1 = current_timestamp();
std::thread::sleep(std::time::Duration::from_millis(1));
let timestamp2 = current_timestamp();
assert!(timestamp2 > timestamp1);
assert!(timestamp1 > 0);
}
#[test]
#[cfg(target_os = "linux")]
fn test_capture_call_stack() {
let call_stack = capture_call_stack();
#[cfg(feature = "backtrace")]
{
assert!(!call_stack.is_empty());
assert!(
call_stack[0].contains("placeholder") || !call_stack[0].is_empty(),
"call_stack[0] should be non-empty, got: {}",
call_stack[0]
);
}
#[cfg(not(feature = "backtrace"))]
{
assert!(call_stack.is_empty());
}
}
#[test]
fn test_default_implementations() {
let _analyzer = LifecycleAnalyzer::default();
let _tracker = BorrowTracker::default();
let _analysis = BorrowAnalysis::default();
let _report = LifecycleAnalysisReport::default();
}
}