1use crate::capture::types::AllocationInfo;
10use crate::core::safe_operations::SafeLock;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::{Arc, Mutex, OnceLock};
14use std::time::{SystemTime, UNIX_EPOCH};
15
16static GLOBAL_LIFECYCLE_ANALYZER: OnceLock<Arc<LifecycleAnalyzer>> = OnceLock::new();
18
19pub fn get_global_lifecycle_analyzer() -> Arc<LifecycleAnalyzer> {
21 GLOBAL_LIFECYCLE_ANALYZER
22 .get_or_init(|| Arc::new(LifecycleAnalyzer::new()))
23 .clone()
24}
25
26pub struct LifecycleAnalyzer {
28 drop_events: Mutex<Arc<Vec<DropEvent>>>,
30 raii_patterns: Mutex<Arc<Vec<RAIIPattern>>>,
32 borrow_tracker: Mutex<BorrowTracker>,
34 closure_captures: Mutex<Arc<Vec<ClosureCapture>>>,
36}
37
38impl LifecycleAnalyzer {
39 pub fn new() -> Self {
41 Self {
42 drop_events: Mutex::new(Arc::new(Vec::new())),
43 raii_patterns: Mutex::new(Arc::new(Vec::new())),
44 borrow_tracker: Mutex::new(BorrowTracker::new()),
45 closure_captures: Mutex::new(Arc::new(Vec::new())),
46 }
47 }
48
49 pub fn record_drop_event(&self, ptr: usize, type_name: &str, custom_drop: bool) {
51 let event = DropEvent {
52 ptr,
53 type_name: type_name.to_string(),
54 timestamp: current_timestamp(),
55 custom_drop,
56 thread_id: format!("{:?}", std::thread::current().id()),
57 call_stack: capture_call_stack(),
58 };
59
60 if let Ok(mut events) = self.drop_events.safe_lock() {
61 let mut new_events = (**events).clone();
62 new_events.push(event);
63 *events = Arc::new(new_events);
64 }
65 }
66
67 pub fn detect_raii_patterns(&self, allocations: &[AllocationInfo]) -> Vec<RAIIPattern> {
69 let mut patterns = Vec::new();
70
71 for allocation in allocations {
72 if let Some(type_name) = &allocation.type_name {
73 if let Some(pattern) = self.analyze_raii_pattern(allocation, type_name) {
74 patterns.push(pattern);
75 }
76 }
77 }
78
79 if let Ok(mut stored_patterns) = self.raii_patterns.safe_lock() {
81 let mut new_patterns = (**stored_patterns).clone();
82 new_patterns.extend(patterns.clone());
83 *stored_patterns = Arc::new(new_patterns);
84 }
85
86 patterns
87 }
88
89 fn analyze_raii_pattern(
91 &self,
92 allocation: &AllocationInfo,
93 type_name: &str,
94 ) -> Option<RAIIPattern> {
95 let resource_type = self.identify_resource_type(type_name)?;
96 let acquisition_method = self.identify_acquisition_method(type_name);
97 let release_method = self.identify_release_method(type_name);
98
99 Some(RAIIPattern {
100 ptr: allocation.ptr,
101 type_name: type_name.to_string(),
102 resource_type,
103 acquisition_method,
104 release_method,
105 acquisition_timestamp: allocation.timestamp_alloc,
106 release_timestamp: allocation.timestamp_dealloc,
107 scope_info: self.analyze_scope_info(allocation),
108 is_exception_safe: self.is_exception_safe(type_name),
109 })
110 }
111
112 fn identify_resource_type(&self, type_name: &str) -> Option<ResourceType> {
114 if type_name.contains("File")
115 || type_name.contains("BufReader")
116 || type_name.contains("BufWriter")
117 {
118 Some(ResourceType::FileHandle)
119 } else if type_name.contains("TcpStream")
120 || type_name.contains("UdpSocket")
121 || type_name.contains("Listener")
122 {
123 Some(ResourceType::NetworkSocket)
124 } else if type_name.contains("Mutex")
125 || type_name.contains("RwLock")
126 || type_name.contains("Semaphore")
127 {
128 Some(ResourceType::SynchronizationPrimitive)
129 } else if type_name.contains("Thread") || type_name.contains("JoinHandle") {
130 Some(ResourceType::ThreadHandle)
131 } else if type_name.contains("Box")
132 || type_name.contains("Vec")
133 || type_name.contains("String")
134 {
135 Some(ResourceType::Memory)
136 } else if type_name.contains("Guard") || type_name.contains("Lock") {
137 Some(ResourceType::LockGuard)
138 } else {
139 None
140 }
141 }
142
143 fn identify_acquisition_method(&self, type_name: &str) -> AcquisitionMethod {
145 if type_name.contains("new") || type_name.contains("Box") {
146 AcquisitionMethod::Constructor
147 } else if type_name.contains("open") || type_name.contains("connect") {
148 AcquisitionMethod::SystemCall
149 } else if type_name.contains("lock") || type_name.contains("Guard") {
150 AcquisitionMethod::Lock
151 } else if type_name.contains("with_capacity") || type_name.contains("reserve") {
152 AcquisitionMethod::Allocation
153 } else {
154 AcquisitionMethod::Unknown
155 }
156 }
157
158 fn identify_release_method(&self, type_name: &str) -> ReleaseMethod {
160 if type_name.contains("Guard")
161 || type_name.contains("Lock")
162 || type_name.contains("File")
163 || type_name.contains("Stream")
164 {
165 ReleaseMethod::AutomaticDrop
166 } else if type_name.contains("Box") || type_name.contains("Vec") {
167 ReleaseMethod::Deallocation
168 } else {
169 ReleaseMethod::CustomDrop
170 }
171 }
172
173 fn analyze_scope_info(&self, allocation: &AllocationInfo) -> ScopeInfo {
175 ScopeInfo {
176 scope_name: allocation
177 .scope_name
178 .clone()
179 .unwrap_or_else(|| "unknown".to_string()),
180 scope_type: self.infer_scope_type(&allocation.scope_name),
181 nesting_level: self.calculate_nesting_level(&allocation.scope_name),
182 }
183 }
184
185 fn infer_scope_type(&self, scope_name: &Option<String>) -> ScopeType {
187 match scope_name {
188 Some(name) if name.contains("fn ") => ScopeType::Function,
189 Some(name) if name.contains("impl ") => ScopeType::Method,
190 Some(name) if name.contains("for ") || name.contains("while ") => ScopeType::Loop,
191 Some(name) if name.contains("if ") || name.contains("match ") => ScopeType::Conditional,
192 Some(name) if name.contains("{") => ScopeType::Block,
193 _ => ScopeType::Unknown,
194 }
195 }
196
197 fn calculate_nesting_level(&self, scope_name: &Option<String>) -> usize {
199 scope_name
200 .as_ref()
201 .map(|name| name.matches('{').count())
202 .unwrap_or(0)
203 }
204
205 fn is_exception_safe(&self, type_name: &str) -> bool {
207 !type_name.contains("unsafe") && !type_name.contains("ffi")
210 }
211
212 pub fn track_borrow(&self, ptr: usize, borrow_type: BorrowType, location: &str) {
214 if let Ok(mut tracker) = self.borrow_tracker.safe_lock() {
215 tracker.track_borrow(ptr, borrow_type, location);
216 }
217 }
218
219 pub fn track_borrow_release(&self, ptr: usize, borrow_id: u64) {
221 if let Ok(mut tracker) = self.borrow_tracker.safe_lock() {
222 tracker.release_borrow(ptr, borrow_id);
223 }
224 }
225
226 pub fn analyze_closure_capture(
228 &self,
229 closure_ptr: usize,
230 captured_vars: Vec<CapturedVariable>,
231 ) {
232 let capture = ClosureCapture {
233 closure_ptr,
234 captured_vars,
235 capture_timestamp: current_timestamp(),
236 thread_id: format!("{:?}", std::thread::current().id()),
237 };
238
239 if let Ok(mut captures) = self.closure_captures.safe_lock() {
240 let mut new_captures = (**captures).clone();
241 new_captures.push(capture);
242 *captures = Arc::new(new_captures);
243 }
244 }
245
246 pub fn get_lifecycle_report(&self) -> LifecycleAnalysisReport {
248 let drop_events = self
249 .drop_events
250 .safe_lock()
251 .map(|events| Arc::clone(&events))
252 .unwrap_or_else(|_| Arc::new(Vec::new()));
253 let raii_patterns = self
254 .raii_patterns
255 .safe_lock()
256 .map(|patterns| Arc::clone(&patterns))
257 .unwrap_or_else(|_| Arc::new(Vec::new()));
258 let borrow_analysis = self
259 .borrow_tracker
260 .safe_lock()
261 .map(|tracker| tracker.get_analysis())
262 .unwrap_or_else(|_| BorrowAnalysis {
263 conflicts: Vec::new(),
264 active_borrows: 0,
265 borrow_patterns: Vec::new(),
266 long_lived_borrows: Vec::new(),
267 total_borrows: 0,
268 });
269 let closure_captures = self
270 .closure_captures
271 .safe_lock()
272 .map(|captures| Arc::clone(&captures))
273 .unwrap_or_else(|_| Arc::new(Vec::new()));
274
275 LifecycleAnalysisReport {
276 drop_events: (*drop_events).clone(),
277 raii_patterns: (*raii_patterns).clone(),
278 borrow_analysis,
279 closure_captures: (*closure_captures).clone(),
280 analysis_timestamp: current_timestamp(),
281 }
282 }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct DropEvent {
288 pub ptr: usize,
290 pub type_name: String,
292 pub timestamp: u64,
294 pub custom_drop: bool,
296 pub thread_id: String,
298 pub call_stack: Vec<String>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct RAIIPattern {
305 pub ptr: usize,
307 pub type_name: String,
309 pub resource_type: ResourceType,
311 pub acquisition_method: AcquisitionMethod,
313 pub release_method: ReleaseMethod,
315 pub acquisition_timestamp: u64,
317 pub release_timestamp: Option<u64>,
319 pub scope_info: ScopeInfo,
321 pub is_exception_safe: bool,
323}
324
325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
327pub enum ResourceType {
328 Memory,
330 FileHandle,
332 NetworkSocket,
334 SynchronizationPrimitive,
336 ThreadHandle,
338 LockGuard,
340 Other(String),
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub enum AcquisitionMethod {
347 Constructor,
349 SystemCall,
351 Lock,
353 Allocation,
355 Unknown,
357}
358
359#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
361pub enum ReleaseMethod {
362 AutomaticDrop,
364 CustomDrop,
366 Deallocation,
368 SystemCall,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct ScopeInfo {
375 pub scope_name: String,
377 pub scope_type: ScopeType,
379 pub nesting_level: usize,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub enum ScopeType {
386 Function,
388 Method,
390 Block,
392 Loop,
394 Conditional,
396 Unknown,
398}
399
400#[derive(Debug)]
402pub struct BorrowTracker {
403 active_borrows: HashMap<usize, Vec<BorrowInfo>>,
405 borrow_history: Vec<BorrowEvent>,
407 next_borrow_id: u64,
409}
410
411impl Default for BorrowTracker {
412 fn default() -> Self {
413 Self::new()
414 }
415}
416
417impl BorrowTracker {
418 pub fn new() -> Self {
420 Self {
421 active_borrows: HashMap::new(),
422 borrow_history: Vec::new(),
423 next_borrow_id: 1,
424 }
425 }
426
427 pub fn track_borrow(&mut self, ptr: usize, borrow_type: BorrowType, location: &str) -> u64 {
429 let borrow_id = self.next_borrow_id;
430 self.next_borrow_id += 1;
431
432 let borrow_info = BorrowInfo {
433 borrow_id,
434 borrow_type,
435 start_timestamp: current_timestamp(),
436 location: location.to_string(),
437 thread_id: format!("{:?}", std::thread::current().id()),
438 };
439
440 self.active_borrows
441 .entry(ptr)
442 .or_default()
443 .push(borrow_info.clone());
444
445 self.borrow_history.push(BorrowEvent {
446 ptr,
447 borrow_info,
448 event_type: BorrowEventType::Acquired,
449 timestamp: current_timestamp(),
450 });
451
452 borrow_id
453 }
454
455 pub fn release_borrow(&mut self, ptr: usize, borrow_id: u64) {
457 if let Some(borrows) = self.active_borrows.get_mut(&ptr) {
458 if let Some(pos) = borrows.iter().position(|b| b.borrow_id == borrow_id) {
459 let borrow_info = borrows.remove(pos);
460
461 self.borrow_history.push(BorrowEvent {
462 ptr,
463 borrow_info,
464 event_type: BorrowEventType::Released,
465 timestamp: current_timestamp(),
466 });
467
468 if borrows.is_empty() {
469 self.active_borrows.remove(&ptr);
470 }
471 }
472 }
473 }
474
475 pub fn get_analysis(&self) -> BorrowAnalysis {
477 let mut conflicts = Vec::new();
478 let mut long_lived_borrows = Vec::new();
479
480 for events in self.borrow_history.windows(2) {
482 if let [event1, event2] = events {
483 if event1.ptr == event2.ptr
484 && self.has_borrow_conflict(&event1.borrow_info, &event2.borrow_info)
485 {
486 conflicts.push(BorrowConflict {
487 ptr: event1.ptr,
488 first_borrow: event1.borrow_info.clone(),
489 second_borrow: event2.borrow_info.clone(),
490 conflict_type: self
491 .classify_conflict(&event1.borrow_info, &event2.borrow_info),
492 });
493 }
494 }
495 }
496
497 let current_time = current_timestamp();
499 for (ptr, borrows) in &self.active_borrows {
500 for borrow in borrows {
501 if current_time - borrow.start_timestamp > 1_000_000_000 {
502 long_lived_borrows.push(LongLivedBorrow {
504 ptr: *ptr,
505 borrow_info: borrow.clone(),
506 duration_ns: current_time - borrow.start_timestamp,
507 });
508 }
509 }
510 }
511
512 BorrowAnalysis {
513 total_borrows: self.borrow_history.len(),
514 active_borrows: self.active_borrows.len(),
515 conflicts,
516 long_lived_borrows,
517 borrow_patterns: self.analyze_borrow_patterns(),
518 }
519 }
520
521 fn has_borrow_conflict(&self, borrow1: &BorrowInfo, borrow2: &BorrowInfo) -> bool {
522 matches!(
523 (&borrow1.borrow_type, &borrow2.borrow_type),
524 (BorrowType::Mutable, _) | (_, BorrowType::Mutable)
525 )
526 }
527
528 fn classify_conflict(&self, borrow1: &BorrowInfo, borrow2: &BorrowInfo) -> ConflictType {
529 match (&borrow1.borrow_type, &borrow2.borrow_type) {
530 (BorrowType::Mutable, BorrowType::Mutable) => ConflictType::MutableMutable,
531 (BorrowType::Mutable, BorrowType::Immutable)
532 | (BorrowType::Immutable, BorrowType::Mutable) => ConflictType::MutableImmutable,
533 _ => ConflictType::None,
534 }
535 }
536
537 fn analyze_borrow_patterns(&self) -> Vec<BorrowPattern> {
538 let mut patterns = Vec::new();
540
541 let short_borrows = self
543 .borrow_history
544 .iter()
545 .filter(|event| matches!(event.event_type, BorrowEventType::Released))
546 .filter(|event| {
547 self.borrow_history.iter().any(|acquire_event| {
549 acquire_event.borrow_info.borrow_id == event.borrow_info.borrow_id
550 && matches!(acquire_event.event_type, BorrowEventType::Acquired)
551 && event.timestamp >= acquire_event.timestamp
552 && event.timestamp - acquire_event.timestamp < 1_000_000
553 })
555 })
556 .count();
557
558 if short_borrows > 10 {
559 patterns.push(BorrowPattern {
560 pattern_type: BorrowPatternType::FrequentShortBorrows,
561 description: format!("Detected {short_borrows} short-lived borrows (< 1ms)",),
562 impact: if short_borrows > 100 {
563 PatternImpact::High
564 } else {
565 PatternImpact::Medium
566 },
567 suggestion: "Consider batching operations to reduce borrow overhead".to_string(),
568 });
569 }
570
571 patterns
572 }
573}
574
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
577pub enum BorrowType {
578 Immutable,
580 Mutable,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct BorrowInfo {
587 pub borrow_id: u64,
589 pub borrow_type: BorrowType,
591 pub start_timestamp: u64,
593 pub location: String,
595 pub thread_id: String,
597}
598
599#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct BorrowEvent {
602 pub ptr: usize,
604 pub borrow_info: BorrowInfo,
606 pub event_type: BorrowEventType,
608 pub timestamp: u64,
610}
611
612#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
614pub enum BorrowEventType {
615 Acquired,
617 Released,
619}
620
621#[derive(Debug, Clone, Default, Serialize, Deserialize)]
623pub struct BorrowAnalysis {
624 pub total_borrows: usize,
626 pub active_borrows: usize,
628 pub conflicts: Vec<BorrowConflict>,
630 pub long_lived_borrows: Vec<LongLivedBorrow>,
632 pub borrow_patterns: Vec<BorrowPattern>,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
638pub struct BorrowConflict {
639 pub ptr: usize,
641 pub first_borrow: BorrowInfo,
643 pub second_borrow: BorrowInfo,
645 pub conflict_type: ConflictType,
647}
648
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
651pub enum ConflictType {
652 MutableMutable,
654 MutableImmutable,
656 None,
658}
659
660#[derive(Debug, Clone, Serialize, Deserialize)]
662pub struct LongLivedBorrow {
663 pub ptr: usize,
665 pub borrow_info: BorrowInfo,
667 pub duration_ns: u64,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize)]
673pub struct BorrowPattern {
674 pub pattern_type: BorrowPatternType,
676 pub description: String,
678 pub impact: PatternImpact,
680 pub suggestion: String,
682}
683
684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
686pub enum BorrowPatternType {
687 FrequentShortBorrows,
689 LongLivedBorrows,
691 ConflictProne,
693}
694
695#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
697pub enum PatternImpact {
698 Low,
700 Medium,
702 High,
704}
705
706#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct ClosureCapture {
709 pub closure_ptr: usize,
711 pub captured_vars: Vec<CapturedVariable>,
713 pub capture_timestamp: u64,
715 pub thread_id: String,
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
721pub struct CapturedVariable {
722 pub var_name: String,
724 pub var_ptr: usize,
726 pub capture_mode: CaptureMode,
728 pub var_type: String,
730 pub size: usize,
732}
733
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
736pub enum CaptureMode {
737 ByValue,
739 ByReference,
741 ByMutableReference,
743}
744
745#[derive(Debug, Clone, Default, Serialize, Deserialize)]
747pub struct LifecycleAnalysisReport {
748 pub drop_events: Vec<DropEvent>,
750 pub raii_patterns: Vec<RAIIPattern>,
752 pub borrow_analysis: BorrowAnalysis,
754 pub closure_captures: Vec<ClosureCapture>,
756 pub analysis_timestamp: u64,
758}
759
760fn current_timestamp() -> u64 {
762 SystemTime::now()
763 .duration_since(UNIX_EPOCH)
764 .unwrap_or_default()
765 .as_nanos() as u64
766}
767
768fn capture_call_stack() -> Vec<String> {
770 #[cfg(feature = "backtrace")]
772 {
773 let bt = backtrace::Backtrace::new();
774 bt.frames()
775 .iter()
776 .skip(2) .filter_map(|frame| {
778 frame
779 .symbols()
780 .first()
781 .and_then(|sym| sym.name())
782 .map(|name| name.to_string())
783 })
784 .collect()
785 }
786
787 #[cfg(not(feature = "backtrace"))]
788 {
789 Vec::new()
791 }
792}
793
794impl Default for LifecycleAnalyzer {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn test_lifecycle_analyzer_creation() {
806 let analyzer = LifecycleAnalyzer::new();
807
808 assert!(analyzer.drop_events.lock().is_ok());
810 assert!(analyzer.raii_patterns.lock().is_ok());
811 assert!(analyzer.borrow_tracker.lock().is_ok());
812 assert!(analyzer.closure_captures.lock().is_ok());
813 }
814
815 #[test]
816 fn test_lifecycle_analyzer_singleton_behavior() {
817 let analyzer1 = LifecycleAnalyzer::new();
819 let analyzer2 = LifecycleAnalyzer::new();
820
821 assert!(analyzer1.drop_events.lock().is_ok());
823 assert!(analyzer2.drop_events.lock().is_ok());
824 }
825
826 #[test]
827 fn test_record_drop_event() {
828 let analyzer = LifecycleAnalyzer::new();
829
830 analyzer.record_drop_event(0x1000, "Vec<i32>", true);
831
832 let events = analyzer
834 .drop_events
835 .lock()
836 .expect("Drop events lock should not be poisoned");
837 assert_eq!(events.len(), 1);
838 assert_eq!(events[0].ptr, 0x1000);
839 assert_eq!(events[0].type_name, "Vec<i32>");
840 assert!(events[0].custom_drop);
841 assert!(events[0].timestamp > 0);
842 assert!(!events[0].thread_id.is_empty());
843 #[cfg(all(target_os = "linux", feature = "backtrace"))]
845 assert!(!events[0].call_stack.is_empty());
846 }
847
848 #[test]
849 fn test_identify_resource_type() {
850 let analyzer = LifecycleAnalyzer::new();
851
852 assert_eq!(
854 analyzer.identify_resource_type("std::fs::File"),
855 Some(ResourceType::FileHandle)
856 );
857 assert_eq!(
858 analyzer.identify_resource_type("BufReader<File>"),
859 Some(ResourceType::FileHandle)
860 );
861
862 assert_eq!(
864 analyzer.identify_resource_type("TcpStream"),
865 Some(ResourceType::NetworkSocket)
866 );
867 assert_eq!(
868 analyzer.identify_resource_type("UdpSocket"),
869 Some(ResourceType::NetworkSocket)
870 );
871
872 assert_eq!(
874 analyzer.identify_resource_type("Mutex<T>"),
875 Some(ResourceType::SynchronizationPrimitive)
876 );
877 assert_eq!(
878 analyzer.identify_resource_type("RwLock<T>"),
879 Some(ResourceType::SynchronizationPrimitive)
880 );
881
882 assert_eq!(
884 analyzer.identify_resource_type("JoinHandle<()>"),
885 Some(ResourceType::ThreadHandle)
886 );
887
888 assert_eq!(
890 analyzer.identify_resource_type("Box<dyn Trait>"),
891 Some(ResourceType::Memory)
892 );
893 assert_eq!(
894 analyzer.identify_resource_type("Vec<i32>"),
895 Some(ResourceType::Memory)
896 );
897 assert_eq!(
898 analyzer.identify_resource_type("String"),
899 Some(ResourceType::Memory)
900 );
901
902 assert_eq!(
904 analyzer.identify_resource_type("MutexGuard<T>"),
905 Some(ResourceType::SynchronizationPrimitive)
906 );
907
908 assert_eq!(analyzer.identify_resource_type("SomeCustomType"), None);
910 }
911
912 #[test]
913 fn test_identify_acquisition_method() {
914 let analyzer = LifecycleAnalyzer::new();
915
916 assert_eq!(
917 analyzer.identify_acquisition_method("Box::new"),
918 AcquisitionMethod::Constructor
919 );
920 assert_eq!(
921 analyzer.identify_acquisition_method("File::open"),
922 AcquisitionMethod::SystemCall
923 );
924 assert_eq!(
925 analyzer.identify_acquisition_method("mutex.lock()"),
926 AcquisitionMethod::Lock
927 );
928 assert_eq!(
929 analyzer.identify_acquisition_method("Vec::with_capacity"),
930 AcquisitionMethod::Allocation
931 );
932 assert_eq!(
933 analyzer.identify_acquisition_method("unknown_method"),
934 AcquisitionMethod::Unknown
935 );
936 }
937
938 #[test]
939 fn test_identify_release_method() {
940 let analyzer = LifecycleAnalyzer::new();
941
942 assert_eq!(
943 analyzer.identify_release_method("MutexGuard"),
944 ReleaseMethod::AutomaticDrop
945 );
946 assert_eq!(
947 analyzer.identify_release_method("File"),
948 ReleaseMethod::AutomaticDrop
949 );
950 assert_eq!(
951 analyzer.identify_release_method("Box<T>"),
952 ReleaseMethod::Deallocation
953 );
954 assert_eq!(
955 analyzer.identify_release_method("Vec<T>"),
956 ReleaseMethod::Deallocation
957 );
958 assert_eq!(
959 analyzer.identify_release_method("CustomType"),
960 ReleaseMethod::CustomDrop
961 );
962 }
963
964 #[test]
965 fn test_infer_scope_type() {
966 let analyzer = LifecycleAnalyzer::new();
967
968 assert_eq!(
969 analyzer.infer_scope_type(&Some("fn main()".to_string())),
970 ScopeType::Function
971 );
972 assert_eq!(
973 analyzer.infer_scope_type(&Some("impl MyStruct".to_string())),
974 ScopeType::Method
975 );
976 assert_eq!(
977 analyzer.infer_scope_type(&Some("for i in 0..10".to_string())),
978 ScopeType::Loop
979 );
980 assert_eq!(
981 analyzer.infer_scope_type(&Some("while condition".to_string())),
982 ScopeType::Loop
983 );
984 assert_eq!(
985 analyzer.infer_scope_type(&Some("if condition".to_string())),
986 ScopeType::Conditional
987 );
988 assert_eq!(
989 analyzer.infer_scope_type(&Some("match value".to_string())),
990 ScopeType::Conditional
991 );
992 assert_eq!(
993 analyzer.infer_scope_type(&Some("{ block }".to_string())),
994 ScopeType::Block
995 );
996 assert_eq!(analyzer.infer_scope_type(&None), ScopeType::Unknown);
997 }
998
999 #[test]
1000 fn test_calculate_nesting_level() {
1001 let analyzer = LifecycleAnalyzer::new();
1002
1003 assert_eq!(
1004 analyzer.calculate_nesting_level(&Some("fn main() { { } }".to_string())),
1005 2
1006 );
1007 assert_eq!(
1008 analyzer.calculate_nesting_level(&Some("simple".to_string())),
1009 0
1010 );
1011 assert_eq!(analyzer.calculate_nesting_level(&None), 0);
1012 }
1013
1014 #[test]
1015 fn test_is_exception_safe() {
1016 let analyzer = LifecycleAnalyzer::new();
1017
1018 assert!(analyzer.is_exception_safe("Vec<i32>"));
1019 assert!(analyzer.is_exception_safe("String"));
1020 assert!(!analyzer.is_exception_safe("unsafe fn"));
1021 assert!(!analyzer.is_exception_safe("ffi::CString"));
1022 }
1023
1024 #[test]
1025 fn test_detect_raii_patterns() {
1026 let analyzer = LifecycleAnalyzer::new();
1027
1028 let mut allocation = AllocationInfo::new(0x1000, 1024);
1029 allocation.type_name = Some("std::fs::File".to_string());
1030 allocation.scope_name = Some("fn main()".to_string());
1031
1032 let allocations = vec![allocation];
1033 let patterns = analyzer.detect_raii_patterns(&allocations);
1034
1035 assert_eq!(patterns.len(), 1);
1036 let pattern = &patterns[0];
1037 assert_eq!(pattern.ptr, 0x1000);
1038 assert_eq!(pattern.type_name, "std::fs::File");
1039 assert_eq!(pattern.resource_type, ResourceType::FileHandle);
1040 assert_eq!(pattern.acquisition_method, AcquisitionMethod::Unknown);
1041 assert_eq!(pattern.release_method, ReleaseMethod::AutomaticDrop);
1042 assert!(pattern.is_exception_safe);
1043 assert_eq!(pattern.scope_info.scope_name, "fn main()");
1044 assert_eq!(pattern.scope_info.scope_type, ScopeType::Function);
1045 }
1046
1047 #[test]
1048 fn test_borrow_tracker_creation() {
1049 let tracker = BorrowTracker::new();
1050
1051 assert!(tracker.active_borrows.is_empty());
1052 assert!(tracker.borrow_history.is_empty());
1053 assert_eq!(tracker.next_borrow_id, 1);
1054 }
1055
1056 #[test]
1057 fn test_track_borrow() {
1058 let mut tracker = BorrowTracker::new();
1059
1060 let borrow_id = tracker.track_borrow(0x1000, BorrowType::Immutable, "test.rs:10");
1061
1062 assert_eq!(borrow_id, 1);
1063 assert_eq!(tracker.next_borrow_id, 2);
1064 assert!(tracker.active_borrows.contains_key(&0x1000));
1065 assert_eq!(tracker.active_borrows[&0x1000].len(), 1);
1066 assert_eq!(tracker.borrow_history.len(), 1);
1067
1068 let borrow_info = &tracker.active_borrows[&0x1000][0];
1069 assert_eq!(borrow_info.borrow_id, 1);
1070 assert_eq!(borrow_info.borrow_type, BorrowType::Immutable);
1071 assert_eq!(borrow_info.location, "test.rs:10");
1072 assert!(borrow_info.start_timestamp > 0);
1073 }
1074
1075 #[test]
1076 fn test_release_borrow() {
1077 let mut tracker = BorrowTracker::new();
1078
1079 let borrow_id = tracker.track_borrow(0x1000, BorrowType::Mutable, "test.rs:15");
1080 tracker.release_borrow(0x1000, borrow_id);
1081
1082 assert!(!tracker.active_borrows.contains_key(&0x1000));
1083 assert_eq!(tracker.borrow_history.len(), 2); let release_event = &tracker.borrow_history[1];
1086 assert_eq!(release_event.event_type, BorrowEventType::Released);
1087 assert_eq!(release_event.ptr, 0x1000);
1088 }
1089
1090 #[test]
1091 fn test_borrow_conflict_detection() {
1092 let mut tracker = BorrowTracker::new();
1093
1094 tracker.track_borrow(0x1000, BorrowType::Mutable, "test.rs:20");
1096 tracker.track_borrow(0x1000, BorrowType::Immutable, "test.rs:21");
1097
1098 let analysis = tracker.get_analysis();
1099
1100 assert_eq!(analysis.total_borrows, 2);
1101 assert_eq!(analysis.active_borrows, 1); assert_eq!(analysis.conflicts.len(), 1);
1103
1104 let conflict = &analysis.conflicts[0];
1105 assert_eq!(conflict.ptr, 0x1000);
1106 assert_eq!(conflict.conflict_type, ConflictType::MutableImmutable);
1107 }
1108
1109 #[test]
1110 fn test_has_borrow_conflict() {
1111 let tracker = BorrowTracker::new();
1112
1113 let mutable_borrow = BorrowInfo {
1114 borrow_id: 1,
1115 borrow_type: BorrowType::Mutable,
1116 start_timestamp: 1000,
1117 location: "test.rs:1".to_string(),
1118 thread_id: "thread-1".to_string(),
1119 };
1120
1121 let immutable_borrow = BorrowInfo {
1122 borrow_id: 2,
1123 borrow_type: BorrowType::Immutable,
1124 start_timestamp: 2000,
1125 location: "test.rs:2".to_string(),
1126 thread_id: "thread-1".to_string(),
1127 };
1128
1129 let another_immutable = BorrowInfo {
1130 borrow_id: 3,
1131 borrow_type: BorrowType::Immutable,
1132 start_timestamp: 3000,
1133 location: "test.rs:3".to_string(),
1134 thread_id: "thread-1".to_string(),
1135 };
1136
1137 assert!(tracker.has_borrow_conflict(&mutable_borrow, &immutable_borrow));
1139 assert!(tracker.has_borrow_conflict(&immutable_borrow, &mutable_borrow));
1140
1141 assert!(!tracker.has_borrow_conflict(&immutable_borrow, &another_immutable));
1143 }
1144
1145 #[test]
1146 fn test_classify_conflict() {
1147 let tracker = BorrowTracker::new();
1148
1149 let mutable1 = BorrowInfo {
1150 borrow_id: 1,
1151 borrow_type: BorrowType::Mutable,
1152 start_timestamp: 1000,
1153 location: "test.rs:1".to_string(),
1154 thread_id: "thread-1".to_string(),
1155 };
1156
1157 let mutable2 = BorrowInfo {
1158 borrow_id: 2,
1159 borrow_type: BorrowType::Mutable,
1160 start_timestamp: 2000,
1161 location: "test.rs:2".to_string(),
1162 thread_id: "thread-1".to_string(),
1163 };
1164
1165 let immutable = BorrowInfo {
1166 borrow_id: 3,
1167 borrow_type: BorrowType::Immutable,
1168 start_timestamp: 3000,
1169 location: "test.rs:3".to_string(),
1170 thread_id: "thread-1".to_string(),
1171 };
1172
1173 assert_eq!(
1174 tracker.classify_conflict(&mutable1, &mutable2),
1175 ConflictType::MutableMutable
1176 );
1177 assert_eq!(
1178 tracker.classify_conflict(&mutable1, &immutable),
1179 ConflictType::MutableImmutable
1180 );
1181 assert_eq!(
1182 tracker.classify_conflict(&immutable, &mutable1),
1183 ConflictType::MutableImmutable
1184 );
1185 }
1186
1187 #[test]
1188 fn test_analyze_closure_capture() {
1189 let analyzer = LifecycleAnalyzer::new();
1190
1191 let captured_vars = vec![
1192 CapturedVariable {
1193 var_name: "x".to_string(),
1194 var_ptr: 0x1000,
1195 capture_mode: CaptureMode::ByValue,
1196 var_type: "i32".to_string(),
1197 size: 4,
1198 },
1199 CapturedVariable {
1200 var_name: "y".to_string(),
1201 var_ptr: 0x2000,
1202 capture_mode: CaptureMode::ByReference,
1203 var_type: "&str".to_string(),
1204 size: 8,
1205 },
1206 ];
1207
1208 analyzer.analyze_closure_capture(0x5000, captured_vars.clone());
1209
1210 let captures = analyzer
1212 .closure_captures
1213 .lock()
1214 .expect("Closure captures lock should not be poisoned");
1215 assert_eq!(captures.len(), 1);
1216
1217 let capture = &captures[0];
1218 assert_eq!(capture.closure_ptr, 0x5000);
1219 assert_eq!(capture.captured_vars.len(), 2);
1220 assert_eq!(capture.captured_vars[0].var_name, "x");
1221 assert_eq!(capture.captured_vars[0].capture_mode, CaptureMode::ByValue);
1222 assert_eq!(capture.captured_vars[1].var_name, "y");
1223 assert_eq!(
1224 capture.captured_vars[1].capture_mode,
1225 CaptureMode::ByReference
1226 );
1227 assert!(capture.capture_timestamp > 0);
1228 assert!(!capture.thread_id.is_empty());
1229 }
1230
1231 #[test]
1232 fn test_track_borrow_operations() {
1233 let analyzer = LifecycleAnalyzer::new();
1234
1235 analyzer.track_borrow(0x1000, BorrowType::Immutable, "test.rs:30");
1236 analyzer.track_borrow_release(0x1000, 1);
1237
1238 let tracker = analyzer
1240 .borrow_tracker
1241 .lock()
1242 .expect("Borrow tracker lock should not be poisoned");
1243 assert_eq!(tracker.borrow_history.len(), 2);
1244 assert!(!tracker.active_borrows.contains_key(&0x1000));
1245 }
1246
1247 #[test]
1248 fn test_get_lifecycle_report() {
1249 let analyzer = LifecycleAnalyzer::new();
1250
1251 analyzer.record_drop_event(0x1000, "Vec<i32>", true);
1253
1254 let mut allocation = AllocationInfo::new(0x2000, 512);
1255 allocation.type_name = Some("std::fs::File".to_string());
1256 analyzer.detect_raii_patterns(&[allocation]);
1257
1258 analyzer.track_borrow(0x3000, BorrowType::Mutable, "test.rs:40");
1259
1260 let captured_vars = vec![CapturedVariable {
1261 var_name: "data".to_string(),
1262 var_ptr: 0x4000,
1263 capture_mode: CaptureMode::ByValue,
1264 var_type: "String".to_string(),
1265 size: 24,
1266 }];
1267 analyzer.analyze_closure_capture(0x5000, captured_vars);
1268
1269 let report = analyzer.get_lifecycle_report();
1270
1271 assert_eq!(report.drop_events.len(), 1);
1272 assert_eq!(report.raii_patterns.len(), 1);
1273 assert_eq!(report.borrow_analysis.total_borrows, 1);
1274 assert_eq!(report.closure_captures.len(), 1);
1275 assert!(report.analysis_timestamp > 0);
1276 }
1277
1278 #[test]
1279 fn test_borrow_pattern_analysis() {
1280 let mut tracker = BorrowTracker::new();
1281
1282 for i in 0..15 {
1284 let borrow_id = tracker.track_borrow(0x1000 + i, BorrowType::Immutable, "test.rs");
1285 tracker.borrow_history.push(BorrowEvent {
1287 ptr: 0x1000 + i,
1288 borrow_info: BorrowInfo {
1289 borrow_id,
1290 borrow_type: BorrowType::Immutable,
1291 start_timestamp: 1000, location: "test.rs".to_string(),
1293 thread_id: "thread-1".to_string(),
1294 },
1295 event_type: BorrowEventType::Released,
1296 timestamp: 1000 + 500_000, });
1298 }
1299
1300 let analysis = tracker.get_analysis();
1301
1302 if !analysis.borrow_patterns.is_empty() {
1305 let pattern = &analysis.borrow_patterns[0];
1306 assert_eq!(
1307 pattern.pattern_type,
1308 BorrowPatternType::FrequentShortBorrows
1309 );
1310 assert!(pattern.description.contains("short-lived borrows"));
1311 assert!(!pattern.suggestion.is_empty());
1312 }
1313
1314 assert_eq!(analysis.total_borrows, 30); }
1317
1318 #[test]
1319 fn test_enum_variants() {
1320 let resource_types = vec![
1322 ResourceType::Memory,
1323 ResourceType::FileHandle,
1324 ResourceType::NetworkSocket,
1325 ResourceType::SynchronizationPrimitive,
1326 ResourceType::ThreadHandle,
1327 ResourceType::LockGuard,
1328 ResourceType::Other("custom".to_string()),
1329 ];
1330
1331 for resource_type in resource_types {
1332 assert!(!format!("{resource_type:?}").is_empty());
1333 }
1334
1335 let acquisition_methods = vec![
1336 AcquisitionMethod::Constructor,
1337 AcquisitionMethod::SystemCall,
1338 AcquisitionMethod::Lock,
1339 AcquisitionMethod::Allocation,
1340 AcquisitionMethod::Unknown,
1341 ];
1342
1343 for method in acquisition_methods {
1344 assert!(!format!("{method:?}").is_empty());
1345 }
1346
1347 let release_methods = vec![
1348 ReleaseMethod::AutomaticDrop,
1349 ReleaseMethod::CustomDrop,
1350 ReleaseMethod::Deallocation,
1351 ReleaseMethod::SystemCall,
1352 ];
1353
1354 for method in release_methods {
1355 assert!(!format!("{method:?}").is_empty());
1356 }
1357
1358 let scope_types = vec![
1359 ScopeType::Function,
1360 ScopeType::Method,
1361 ScopeType::Block,
1362 ScopeType::Loop,
1363 ScopeType::Conditional,
1364 ScopeType::Unknown,
1365 ];
1366
1367 for scope_type in scope_types {
1368 assert!(!format!("{scope_type:?}").is_empty());
1369 }
1370
1371 let borrow_types = vec![BorrowType::Immutable, BorrowType::Mutable];
1372
1373 for borrow_type in borrow_types {
1374 assert!(!format!("{borrow_type:?}").is_empty());
1375 }
1376
1377 let capture_modes = vec![
1378 CaptureMode::ByValue,
1379 CaptureMode::ByReference,
1380 CaptureMode::ByMutableReference,
1381 ];
1382
1383 for mode in capture_modes {
1384 assert!(!format!("{mode:?}").is_empty());
1385 }
1386 }
1387
1388 #[test]
1389 fn test_current_timestamp() {
1390 let timestamp1 = current_timestamp();
1391 std::thread::sleep(std::time::Duration::from_millis(1));
1392 let timestamp2 = current_timestamp();
1393
1394 assert!(timestamp2 > timestamp1);
1395 assert!(timestamp1 > 0);
1396 }
1397
1398 #[test]
1399 #[cfg(target_os = "linux")]
1400 fn test_capture_call_stack() {
1401 let call_stack = capture_call_stack();
1402
1403 #[cfg(feature = "backtrace")]
1404 {
1405 assert!(!call_stack.is_empty());
1406 assert!(
1408 call_stack[0].contains("placeholder") || !call_stack[0].is_empty(),
1409 "call_stack[0] should be non-empty, got: {}",
1410 call_stack[0]
1411 );
1412 }
1413
1414 #[cfg(not(feature = "backtrace"))]
1415 {
1416 assert!(call_stack.is_empty());
1418 }
1419 }
1420
1421 #[test]
1422 fn test_default_implementations() {
1423 let _analyzer = LifecycleAnalyzer::default();
1424 let _tracker = BorrowTracker::default();
1425 let _analysis = BorrowAnalysis::default();
1426 let _report = LifecycleAnalysisReport::default();
1427 }
1428}