1use crate::analysis::detectors::{
23 DetectionResult, DetectionStatistics, Detector, DetectorConfig, DetectorError, Issue,
24 IssueCategory, IssueSeverity,
25};
26use crate::capture::types::AllocationInfo;
27
28#[derive(Debug, Clone)]
30pub struct LifecycleDetectorConfig {
31 pub enable_drop_trait_analysis: bool,
33
34 pub enable_borrow_violation_detection: bool,
36
37 pub enable_lifetime_violation_detection: bool,
39
40 pub enable_ownership_pattern_detection: bool,
42
43 pub max_lifetime_analysis_depth: usize,
45}
46
47impl Default for LifecycleDetectorConfig {
48 fn default() -> Self {
49 Self {
50 enable_drop_trait_analysis: true,
51 enable_borrow_violation_detection: true,
52 enable_lifetime_violation_detection: true,
53 enable_ownership_pattern_detection: true,
54 max_lifetime_analysis_depth: 100,
55 }
56 }
57}
58
59#[derive(Debug)]
70pub struct LifecycleDetector {
71 config: LifecycleDetectorConfig,
72 base_config: DetectorConfig,
73}
74
75impl LifecycleDetector {
76 pub fn new(config: LifecycleDetectorConfig) -> Self {
91 Self {
92 config,
93 base_config: DetectorConfig::default(),
94 }
95 }
96
97 pub fn lifecycle_config(&self) -> &LifecycleDetectorConfig {
99 &self.config
100 }
101
102 pub fn update_lifecycle_config(&mut self, config: LifecycleDetectorConfig) {
104 self.config = config;
105 }
106}
107
108impl Detector for LifecycleDetector {
109 fn name(&self) -> &str {
110 "LifecycleDetector"
111 }
112
113 fn version(&self) -> &str {
114 "1.0.0"
115 }
116
117 fn detect(&self, allocations: &[AllocationInfo]) -> DetectionResult {
118 let start_time = std::time::Instant::now();
119
120 let mut statistics = DetectionStatistics::new();
121 statistics.total_allocations = allocations.len();
122
123 let mut issues = Vec::new();
124
125 if self.config.enable_lifetime_violation_detection {
127 let lifecycle_issues = self.detect_lifetime_issues(allocations, &mut statistics);
128 issues.extend(lifecycle_issues);
129 }
130
131 if self.config.enable_ownership_pattern_detection {
133 let ownership_issues = self.detect_ownership_patterns(allocations, &mut statistics);
134 issues.extend(ownership_issues);
135 }
136
137 if self.config.enable_drop_trait_analysis {
139 let drop_issues = self.detect_drop_trait_issues(allocations, &mut statistics);
140 issues.extend(drop_issues);
141 }
142
143 if self.config.enable_borrow_violation_detection {
145 let borrow_issues = self.detect_borrow_violations(allocations, &mut statistics);
146 issues.extend(borrow_issues);
147 }
148
149 let detection_time_ms = start_time.elapsed().as_millis() as u64;
150
151 DetectionResult {
152 detector_name: self.name().to_string(),
153 issues,
154 statistics,
155 detection_time_ms,
156 }
157 }
158
159 fn config(&self) -> &DetectorConfig {
160 &self.base_config
161 }
162
163 fn update_config(&mut self, config: DetectorConfig) -> Result<(), DetectorError> {
164 self.base_config = config;
165 Ok(())
166 }
167}
168
169impl LifecycleDetector {
170 fn detect_lifetime_issues(
172 &self,
173 allocations: &[AllocationInfo],
174 statistics: &mut DetectionStatistics,
175 ) -> Vec<Issue> {
176 let mut issues = Vec::new();
177
178 for (index, alloc) in allocations.iter().enumerate() {
179 if let Some(lifecycle) = &alloc.lifecycle_tracking {
181 if let Some(dealloc_time) = alloc.timestamp_dealloc {
183 for event in &lifecycle.lifecycle_events {
184 if event.timestamp > dealloc_time {
185 let issue_id = format!("lifetime_post_dealloc_{}", index);
186 let severity = IssueSeverity::Critical;
187
188 let issue = Issue::new(
189 issue_id,
190 severity,
191 IssueCategory::Safety,
192 format!(
193 "Lifetime violation: access at 0x{:x} after deallocation at {}",
194 alloc.ptr, dealloc_time
195 ),
196 )
197 .with_allocation_ptr(alloc.ptr)
198 .with_suggested_fix(
199 "Review lifetime annotations and ensure references are properly scoped".to_string(),
200 );
201
202 issues.push(issue);
203 statistics.allocations_with_issues += 1;
204 }
205 }
206 }
207
208 if lifecycle.lifecycle_events.len() > self.config.max_lifetime_analysis_depth {
210 let issue_id = format!("lifetime_complexity_{}", index);
211 let severity = IssueSeverity::Medium;
212
213 let issue = Issue::new(
214 issue_id,
215 severity,
216 IssueCategory::Performance,
217 format!(
218 "High lifecycle complexity detected at 0x{:x}: {} events",
219 alloc.ptr, lifecycle.lifecycle_events.len()
220 ),
221 )
222 .with_allocation_ptr(alloc.ptr)
223 .with_suggested_fix(
224 "Consider simplifying lifetime patterns or breaking into smaller components".to_string(),
225 );
226
227 issues.push(issue);
228 statistics.allocations_with_issues += 1;
229 }
230 }
231
232 if let Some(temp_info) = &alloc.temporary_object {
234 if let Some(actual_lifetime) = alloc.lifetime_ms {
235 let expected_lifetime = match temp_info.lifetime_ns {
237 Some(ns) => ns / 1_000_000, None => 100, };
240
241 if actual_lifetime > expected_lifetime * 10 {
242 let issue_id = format!("temporary_lifetime_{}", index);
243 let severity = IssueSeverity::Low;
244
245 let issue = Issue::new(
246 issue_id,
247 severity,
248 IssueCategory::Other,
249 format!(
250 "Temporary object at 0x{:x} lives longer than expected: {}ms vs expected < {}ms",
251 alloc.ptr, actual_lifetime, expected_lifetime
252 ),
253 )
254 .with_allocation_ptr(alloc.ptr)
255 .with_suggested_fix(
256 "Store temporary in named variable with explicit lifetime".to_string(),
257 );
258
259 issues.push(issue);
260 statistics.allocations_with_issues += 1;
261 }
262 }
263 }
264
265 if let Some(scope_name) = &alloc.scope_name {
267 if let Some(lifetime_ms) = alloc.lifetime_ms {
268 let expected_lifetime = self.estimate_scope_lifetime(scope_name);
269 if lifetime_ms > expected_lifetime * 5 {
270 let issue_id = format!("scope_lifetime_violation_{}", index);
271 let severity = IssueSeverity::Medium;
272
273 let issue = Issue::new(
274 issue_id,
275 severity,
276 IssueCategory::Lifetime,
277 format!(
278 "Scope lifetime violation at 0x{:x}: {}ms lifetime in scope '{}', expected < {}ms",
279 alloc.ptr, lifetime_ms, scope_name, expected_lifetime
280 ),
281 )
282 .with_allocation_ptr(alloc.ptr)
283 .with_suggested_fix(
284 "Move allocation to outer scope or reduce lifetime".to_string(),
285 );
286
287 issues.push(issue);
288 statistics.allocations_with_issues += 1;
289 }
290 }
291 }
292 }
293
294 issues
295 }
296
297 fn detect_ownership_patterns(
299 &self,
300 allocations: &[AllocationInfo],
301 statistics: &mut DetectionStatistics,
302 ) -> Vec<Issue> {
303 let mut issues = Vec::new();
304
305 for (index, alloc) in allocations.iter().enumerate() {
306 if let Some(clone_info) = &alloc.clone_info {
308 if clone_info.clone_count > 10 {
309 let issue_id = format!("excessive_cloning_{}", index);
310 let severity = self.assess_clone_severity(clone_info.clone_count);
311
312 let issue = Issue::new(
313 issue_id,
314 severity,
315 IssueCategory::Performance,
316 format!(
317 "Excessive cloning detected at 0x{:x}: {} clones",
318 alloc.ptr, clone_info.clone_count
319 ),
320 )
321 .with_allocation_ptr(alloc.ptr)
322 .with_suggested_fix(
323 "Consider using Arc for shared ownership or redesign to reduce cloning"
324 .to_string(),
325 );
326
327 issues.push(issue);
328 statistics.allocations_with_issues += 1;
329 }
330
331 if clone_info.clone_count > 0 && alloc.size > 1024 * 1024 {
333 let issue_id = format!("expensive_clone_{}", index);
335 let severity = IssueSeverity::High;
336
337 let issue = Issue::new(
338 issue_id,
339 severity,
340 IssueCategory::Performance,
341 format!(
342 "Expensive clone operation at 0x{:x}: cloning {} bytes",
343 alloc.ptr, alloc.size
344 ),
345 )
346 .with_allocation_ptr(alloc.ptr)
347 .with_suggested_fix(
348 "Use Arc for shared ownership or reference instead of cloning".to_string(),
349 );
350
351 issues.push(issue);
352 statistics.allocations_with_issues += 1;
353 }
354 }
355
356 if let Some(smart_ptr_info) = &alloc.smart_pointer_info {
358 if smart_ptr_info.ref_count_history.len() > 100 {
360 let issue_id = format!("high_ref_count_history_{}", index);
361 let severity = IssueSeverity::Medium;
362
363 let issue = Issue::new(
364 issue_id,
365 severity,
366 IssueCategory::Performance,
367 format!(
368 "High reference count history at 0x{:x}: {} snapshots",
369 alloc.ptr,
370 smart_ptr_info.ref_count_history.len()
371 ),
372 )
373 .with_allocation_ptr(alloc.ptr)
374 .with_suggested_fix(
375 "Review reference counting pattern and consider using Weak references"
376 .to_string(),
377 );
378
379 issues.push(issue);
380 statistics.allocations_with_issues += 1;
381 }
382
383 if self.has_reference_cycle(smart_ptr_info) {
385 let issue_id = format!("reference_cycle_{}", index);
386 let severity = IssueSeverity::High;
387
388 let issue = Issue::new(
389 issue_id,
390 severity,
391 IssueCategory::Safety,
392 format!("Potential reference cycle detected at 0x{:x}", alloc.ptr),
393 )
394 .with_allocation_ptr(alloc.ptr)
395 .with_suggested_fix(
396 "Break reference cycles using Weak references or explicit cleanup"
397 .to_string(),
398 );
399
400 issues.push(issue);
401 statistics.allocations_with_issues += 1;
402 }
403 }
404
405 if self.is_move_semantics_violation(alloc) {
407 let issue_id = format!("move_semantics_violation_{}", index);
408 let severity = IssueSeverity::High;
409
410 let issue = Issue::new(
411 issue_id,
412 severity,
413 IssueCategory::Safety,
414 format!("Move semantics violation detected at 0x{:x}", alloc.ptr),
415 )
416 .with_allocation_ptr(alloc.ptr)
417 .with_suggested_fix(
418 "Review ownership transfer and ensure proper move semantics".to_string(),
419 );
420
421 issues.push(issue);
422 statistics.allocations_with_issues += 1;
423 }
424 }
425
426 issues
427 }
428
429 fn detect_drop_trait_issues(
431 &self,
432 allocations: &[AllocationInfo],
433 statistics: &mut DetectionStatistics,
434 ) -> Vec<Issue> {
435 let mut issues = Vec::new();
436
437 for (index, alloc) in allocations.iter().enumerate() {
438 if alloc.size > 10 * 1024 * 1024 {
440 let issue_id = format!("large_allocation_no_drop_{}", index);
442 let severity = IssueSeverity::Medium;
443
444 let issue = Issue::new(
445 issue_id,
446 severity,
447 IssueCategory::Other,
448 format!(
449 "Large allocation at 0x{:x} without custom Drop: {} bytes",
450 alloc.ptr, alloc.size
451 ),
452 )
453 .with_allocation_ptr(alloc.ptr)
454 .with_suggested_fix(
455 "Consider implementing custom Drop for proper resource cleanup".to_string(),
456 );
457
458 issues.push(issue);
459 statistics.allocations_with_issues += 1;
460 }
461
462 if let Some(lifecycle) = &alloc.lifecycle_tracking {
464 for event in &lifecycle.lifecycle_events {
465 if let crate::capture::types::LifecycleEventType::Drop = event.event_type {
467 if let Some(dealloc_time) = alloc.timestamp_dealloc {
469 if event.timestamp > dealloc_time + 1000 {
470 let issue_id = format!("slow_drop_{}", index);
472 let severity = IssueSeverity::Medium;
473
474 let issue = Issue::new(
475 issue_id,
476 severity,
477 IssueCategory::Performance,
478 format!(
479 "Slow drop detected at 0x{:x}: {}ms delay",
480 alloc.ptr, event.timestamp - dealloc_time
481 ),
482 )
483 .with_allocation_ptr(alloc.ptr)
484 .with_suggested_fix(
485 "Optimize Drop implementation or use async cleanup for expensive operations".to_string(),
486 );
487
488 issues.push(issue);
489 statistics.allocations_with_issues += 1;
490 }
491 }
492 }
493 }
494 }
495 }
496
497 issues
498 }
499
500 fn detect_borrow_violations(
502 &self,
503 allocations: &[AllocationInfo],
504 statistics: &mut DetectionStatistics,
505 ) -> Vec<Issue> {
506 let mut issues = Vec::new();
507
508 for (index, alloc) in allocations.iter().enumerate() {
509 if let Some(borrow_info) = &alloc.borrow_info {
511 if borrow_info.mutable_borrows > 1 {
512 let issue_id = format!("concurrent_mutable_borrows_{}", index);
513 let severity = IssueSeverity::High;
514
515 let issue = Issue::new(
516 issue_id,
517 severity,
518 IssueCategory::Safety,
519 format!(
520 "Concurrent mutable borrows detected at 0x{:x}: {} mutable borrows",
521 alloc.ptr, borrow_info.mutable_borrows
522 ),
523 )
524 .with_allocation_ptr(alloc.ptr)
525 .with_suggested_fix(
526 "Ensure at most one mutable borrow exists at any time".to_string(),
527 );
528
529 issues.push(issue);
530 statistics.allocations_with_issues += 1;
531 }
532
533 let total_borrows = borrow_info.mutable_borrows + borrow_info.immutable_borrows;
535 if total_borrows > 50 {
536 let issue_id = format!("excessive_borrows_{}", index);
537 let severity = IssueSeverity::Low;
538
539 let issue = Issue::new(
540 issue_id,
541 severity,
542 IssueCategory::Other,
543 format!(
544 "Excessive borrows detected at 0x{:x}: {} total borrows",
545 alloc.ptr, total_borrows
546 ),
547 )
548 .with_allocation_ptr(alloc.ptr)
549 .with_suggested_fix(
550 "Reduce borrow count or consider using references more efficiently"
551 .to_string(),
552 );
553
554 issues.push(issue);
555 statistics.allocations_with_issues += 1;
556 }
557 }
558
559 if self.is_borrow_after_move(alloc) {
561 let issue_id = format!("borrow_after_move_{}", index);
562 let severity = IssueSeverity::High;
563
564 let issue = Issue::new(
565 issue_id,
566 severity,
567 IssueCategory::Safety,
568 format!("Borrow after move detected at 0x{:x}", alloc.ptr),
569 )
570 .with_allocation_ptr(alloc.ptr)
571 .with_suggested_fix(
572 "Review move semantics and ensure borrows are before move operations"
573 .to_string(),
574 );
575
576 issues.push(issue);
577 statistics.allocations_with_issues += 1;
578 }
579 }
580
581 issues
582 }
583
584 fn assess_clone_severity(&self, clone_count: usize) -> IssueSeverity {
586 if clone_count > 1000 {
588 return IssueSeverity::Critical;
589 }
590
591 if clone_count > 100 {
593 return IssueSeverity::High;
594 }
595
596 IssueSeverity::Medium
598 }
599
600 fn estimate_scope_lifetime(&self, scope_name: &str) -> u64 {
602 if scope_name.contains("fn ") {
603 100 } else if scope_name.contains("::") {
605 1000 } else {
607 500 }
609 }
610
611 fn has_reference_cycle(
613 &self,
614 smart_ptr_info: &crate::capture::types::SmartPointerInfo,
615 ) -> bool {
616 let mut counts: std::collections::HashSet<usize> = std::collections::HashSet::new();
618 for snapshot in &smart_ptr_info.ref_count_history {
619 if counts.contains(&snapshot.strong_count) {
620 return true;
622 }
623 counts.insert(snapshot.strong_count);
624 }
625 false
626 }
627
628 fn is_move_semantics_violation(&self, alloc: &AllocationInfo) -> bool {
630 if let Some(clone_info) = &alloc.clone_info {
632 if clone_info.is_clone && clone_info.original_ptr.is_some() {
633 return true;
635 }
636 }
637 false
638 }
639
640 fn is_borrow_after_move(&self, alloc: &AllocationInfo) -> bool {
642 if let Some(clone_info) = &alloc.clone_info {
644 if clone_info.is_clone && alloc.borrow_count > 0 {
645 return true;
646 }
647 }
648 false
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655 use crate::capture::types::AllocationInfo;
656
657 #[test]
658 fn test_lifecycle_detector_creation() {
659 let config = LifecycleDetectorConfig::default();
660 let detector = LifecycleDetector::new(config);
661
662 assert_eq!(detector.name(), "LifecycleDetector");
663 assert_eq!(detector.version(), "1.0.0");
664 }
665
666 #[test]
667 fn test_lifecycle_detector_config() {
668 let config = LifecycleDetectorConfig {
669 enable_drop_trait_analysis: false,
670 enable_borrow_violation_detection: false,
671 enable_lifetime_violation_detection: false,
672 enable_ownership_pattern_detection: false,
673 max_lifetime_analysis_depth: 50,
674 };
675
676 let detector = LifecycleDetector::new(config);
677 let lifecycle_config = detector.lifecycle_config();
678
679 assert!(!lifecycle_config.enable_drop_trait_analysis);
680 assert!(!lifecycle_config.enable_borrow_violation_detection);
681 assert!(!lifecycle_config.enable_lifetime_violation_detection);
682 assert!(!lifecycle_config.enable_ownership_pattern_detection);
683 assert_eq!(lifecycle_config.max_lifetime_analysis_depth, 50);
684 }
685
686 #[test]
687 fn test_lifecycle_detector_detect() {
688 let config = LifecycleDetectorConfig::default();
689 let detector = LifecycleDetector::new(config);
690
691 let allocations = vec![
692 AllocationInfo::new(0x1000, 1024),
693 AllocationInfo::new(0x2000, 2048),
694 ];
695
696 let result = detector.detect(&allocations);
697
698 assert_eq!(result.detector_name, "LifecycleDetector");
699 assert_eq!(result.statistics.total_allocations, 2);
700 }
701
702 #[test]
703 fn test_detect_excessive_cloning() {
704 let config = LifecycleDetectorConfig::default();
705 let detector = LifecycleDetector::new(config);
706
707 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
708
709 use crate::capture::types::CloneInfo;
710 allocations[0].clone_info = Some(CloneInfo {
711 clone_count: 50, is_clone: true,
713 original_ptr: Some(0x1000),
714 _source: None,
715 _confidence: None,
716 });
717
718 let issues =
719 detector.detect_ownership_patterns(&allocations, &mut DetectionStatistics::new());
720
721 assert!(!issues.is_empty());
722 assert!(issues
723 .iter()
724 .any(|i| i.description.contains("Excessive cloning")));
725 }
726
727 #[test]
728 fn test_detect_concurrent_mutable_borrows() {
729 let config = LifecycleDetectorConfig::default();
730 let detector = LifecycleDetector::new(config);
731
732 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
733 allocations[0].borrow_count = 2;
734
735 use crate::capture::types::BorrowInfo;
736 allocations[0].borrow_info = Some(BorrowInfo {
737 immutable_borrows: 0,
738 mutable_borrows: 2, max_concurrent_borrows: 2,
740 last_borrow_timestamp: Some(1000),
741 _source: None,
742 _confidence: None,
743 });
744
745 let issues =
746 detector.detect_borrow_violations(&allocations, &mut DetectionStatistics::new());
747
748 assert!(!issues.is_empty());
749 assert!(issues
750 .iter()
751 .any(|i| i.description.contains("Concurrent mutable borrows")));
752 }
753
754 #[test]
755 fn test_detect_scope_lifetime_violation() {
756 let config = LifecycleDetectorConfig::default();
757 let detector = LifecycleDetector::new(config);
758
759 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
760 allocations[0].scope_name = Some("fn main".to_string());
761 allocations[0].lifetime_ms = Some(5000); let issues = detector.detect_lifetime_issues(&allocations, &mut DetectionStatistics::new());
764
765 assert!(!issues.is_empty());
766 assert!(issues
767 .iter()
768 .any(|i| i.description.contains("Scope lifetime violation")));
769 }
770
771 #[test]
772 fn test_assess_clone_severity() {
773 let config = LifecycleDetectorConfig::default();
774 let detector = LifecycleDetector::new(config);
775
776 assert_eq!(
777 detector.assess_clone_severity(1500),
778 IssueSeverity::Critical
779 );
780 assert_eq!(detector.assess_clone_severity(150), IssueSeverity::High);
781 assert_eq!(detector.assess_clone_severity(50), IssueSeverity::Medium);
782 }
783
784 #[test]
785 fn test_estimate_scope_lifetime() {
786 let config = LifecycleDetectorConfig::default();
787 let detector = LifecycleDetector::new(config);
788
789 assert_eq!(detector.estimate_scope_lifetime("fn main"), 100);
790 assert_eq!(detector.estimate_scope_lifetime("module::function"), 1000);
791 assert_eq!(detector.estimate_scope_lifetime("block"), 500);
792 }
793
794 #[test]
795 fn test_lifecycle_detector_disabled() {
796 let config = LifecycleDetectorConfig {
797 enable_drop_trait_analysis: false,
798 enable_borrow_violation_detection: false,
799 enable_lifetime_violation_detection: false,
800 enable_ownership_pattern_detection: false,
801 max_lifetime_analysis_depth: 100,
802 };
803 let detector = LifecycleDetector::new(config);
804
805 let allocations = vec![AllocationInfo::new(0x1000, 1024)];
806 let result = detector.detect(&allocations);
807
808 assert_eq!(result.issues.len(), 0);
809 }
810
811 #[test]
812 fn test_detect_large_allocation_no_drop() {
813 let config = LifecycleDetectorConfig::default();
814 let detector = LifecycleDetector::new(config);
815
816 let allocations = vec![AllocationInfo::new(0x1000, 20 * 1024 * 1024)]; let issues =
819 detector.detect_drop_trait_issues(&allocations, &mut DetectionStatistics::new());
820
821 assert!(!issues.is_empty());
822 assert!(issues
823 .iter()
824 .any(|i| i.description.contains("Large allocation")));
825 }
826
827 #[test]
828 fn test_lifecycle_detector_config_default() {
829 let config = LifecycleDetectorConfig::default();
830
831 assert!(config.enable_drop_trait_analysis);
832 assert!(config.enable_borrow_violation_detection);
833 assert!(config.enable_lifetime_violation_detection);
834 assert!(config.enable_ownership_pattern_detection);
835 assert_eq!(config.max_lifetime_analysis_depth, 100);
836 }
837
838 #[test]
839 fn test_lifecycle_detector_update_config() {
840 let config = LifecycleDetectorConfig::default();
841 let mut detector = LifecycleDetector::new(config);
842
843 let new_config = LifecycleDetectorConfig {
844 enable_drop_trait_analysis: false,
845 enable_borrow_violation_detection: true,
846 enable_lifetime_violation_detection: true,
847 enable_ownership_pattern_detection: false,
848 max_lifetime_analysis_depth: 200,
849 };
850
851 detector.update_lifecycle_config(new_config.clone());
852 assert!(!detector.lifecycle_config().enable_drop_trait_analysis);
853 assert_eq!(detector.lifecycle_config().max_lifetime_analysis_depth, 200);
854 }
855
856 #[test]
857 fn test_detector_config_update() {
858 let config = LifecycleDetectorConfig::default();
859 let mut detector = LifecycleDetector::new(config);
860
861 let new_base_config = DetectorConfig {
862 enabled: false,
863 ..Default::default()
864 };
865
866 let result = detector.update_config(new_base_config);
867 assert!(result.is_ok());
868 assert!(!detector.config().enabled);
869 }
870
871 #[test]
872 fn test_detect_empty_allocations() {
873 let config = LifecycleDetectorConfig::default();
874 let detector = LifecycleDetector::new(config);
875
876 let allocations: Vec<AllocationInfo> = vec![];
877 let result = detector.detect(&allocations);
878
879 assert_eq!(result.statistics.total_allocations, 0);
880 assert!(result.issues.is_empty());
881 }
882
883 #[test]
884 fn test_detect_expensive_clone() {
885 let config = LifecycleDetectorConfig::default();
886 let detector = LifecycleDetector::new(config);
887
888 let mut allocations = vec![AllocationInfo::new(0x1000, 2 * 1024 * 1024)]; use crate::capture::types::CloneInfo;
891 allocations[0].clone_info = Some(CloneInfo {
892 clone_count: 5,
893 is_clone: true,
894 original_ptr: Some(0x2000),
895 _source: None,
896 _confidence: None,
897 });
898
899 let issues =
900 detector.detect_ownership_patterns(&allocations, &mut DetectionStatistics::new());
901
902 assert!(issues
903 .iter()
904 .any(|i| i.description.contains("Expensive clone")));
905 }
906
907 #[test]
908 fn test_detect_excessive_borrows() {
909 let config = LifecycleDetectorConfig::default();
910 let detector = LifecycleDetector::new(config);
911
912 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
913
914 use crate::capture::types::BorrowInfo;
915 allocations[0].borrow_info = Some(BorrowInfo {
916 immutable_borrows: 40,
917 mutable_borrows: 20,
918 max_concurrent_borrows: 60,
919 last_borrow_timestamp: Some(1000),
920 _source: None,
921 _confidence: None,
922 });
923
924 let issues =
925 detector.detect_borrow_violations(&allocations, &mut DetectionStatistics::new());
926
927 assert!(issues
928 .iter()
929 .any(|i| i.description.contains("Excessive borrows")));
930 }
931
932 #[test]
933 fn test_detect_high_ref_count_history() {
934 let config = LifecycleDetectorConfig::default();
935 let detector = LifecycleDetector::new(config);
936
937 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
938
939 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo};
940 let history: Vec<RefCountSnapshot> = (0..150)
941 .map(|i| RefCountSnapshot {
942 timestamp: i as u64 * 100,
943 strong_count: i + 1,
944 weak_count: 0,
945 })
946 .collect();
947
948 allocations[0].smart_pointer_info = Some(SmartPointerInfo {
949 data_ptr: 0x1000,
950 cloned_from: None,
951 clones: vec![],
952 ref_count_history: history,
953 weak_count: Some(0),
954 is_weak_reference: false,
955 is_data_owner: true,
956 is_implicitly_deallocated: false,
957 pointer_type: crate::capture::types::SmartPointerType::Arc,
958 });
959
960 let issues =
961 detector.detect_ownership_patterns(&allocations, &mut DetectionStatistics::new());
962
963 assert!(issues
964 .iter()
965 .any(|i| i.description.contains("High reference count history")));
966 }
967
968 #[test]
969 fn test_detect_reference_cycle() {
970 let config = LifecycleDetectorConfig::default();
971 let detector = LifecycleDetector::new(config);
972
973 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
974
975 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo};
976 allocations[0].smart_pointer_info = Some(SmartPointerInfo {
977 data_ptr: 0x1000,
978 cloned_from: None,
979 clones: vec![],
980 ref_count_history: vec![
981 RefCountSnapshot {
982 timestamp: 0,
983 strong_count: 1,
984 weak_count: 0,
985 },
986 RefCountSnapshot {
987 timestamp: 100,
988 strong_count: 2,
989 weak_count: 0,
990 },
991 RefCountSnapshot {
992 timestamp: 200,
993 strong_count: 1,
994 weak_count: 0,
995 },
996 RefCountSnapshot {
997 timestamp: 300,
998 strong_count: 2,
999 weak_count: 0,
1000 },
1001 ],
1002 weak_count: Some(0),
1003 is_weak_reference: false,
1004 is_data_owner: true,
1005 is_implicitly_deallocated: false,
1006 pointer_type: crate::capture::types::SmartPointerType::Rc,
1007 });
1008
1009 let issues =
1010 detector.detect_ownership_patterns(&allocations, &mut DetectionStatistics::new());
1011
1012 assert!(issues
1013 .iter()
1014 .any(|i| i.description.contains("reference cycle")));
1015 }
1016
1017 #[test]
1018 fn test_detect_move_semantics_violation() {
1019 let config = LifecycleDetectorConfig::default();
1020 let detector = LifecycleDetector::new(config);
1021
1022 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
1023
1024 use crate::capture::types::CloneInfo;
1025 allocations[0].clone_info = Some(CloneInfo {
1026 clone_count: 1,
1027 is_clone: true,
1028 original_ptr: Some(0x2000),
1029 _source: None,
1030 _confidence: None,
1031 });
1032
1033 let issues =
1034 detector.detect_ownership_patterns(&allocations, &mut DetectionStatistics::new());
1035
1036 assert!(issues
1037 .iter()
1038 .any(|i| i.description.contains("Move semantics violation")));
1039 }
1040
1041 #[test]
1042 fn test_detect_borrow_after_move() {
1043 let config = LifecycleDetectorConfig::default();
1044 let detector = LifecycleDetector::new(config);
1045
1046 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
1047 allocations[0].borrow_count = 5;
1048
1049 use crate::capture::types::CloneInfo;
1050 allocations[0].clone_info = Some(CloneInfo {
1051 clone_count: 1,
1052 is_clone: true,
1053 original_ptr: Some(0x2000),
1054 _source: None,
1055 _confidence: None,
1056 });
1057
1058 let issues =
1059 detector.detect_borrow_violations(&allocations, &mut DetectionStatistics::new());
1060
1061 assert!(issues
1062 .iter()
1063 .any(|i| i.description.contains("Borrow after move")));
1064 }
1065
1066 #[test]
1067 fn test_assess_clone_severity_critical() {
1068 let config = LifecycleDetectorConfig::default();
1069 let detector = LifecycleDetector::new(config);
1070
1071 assert_eq!(
1072 detector.assess_clone_severity(2000),
1073 IssueSeverity::Critical
1074 );
1075 }
1076
1077 #[test]
1078 fn test_assess_clone_severity_high() {
1079 let config = LifecycleDetectorConfig::default();
1080 let detector = LifecycleDetector::new(config);
1081
1082 assert_eq!(detector.assess_clone_severity(500), IssueSeverity::High);
1083 }
1084
1085 #[test]
1086 fn test_estimate_scope_lifetime_function() {
1087 let config = LifecycleDetectorConfig::default();
1088 let detector = LifecycleDetector::new(config);
1089
1090 assert_eq!(detector.estimate_scope_lifetime("fn test_function"), 100);
1091 assert_eq!(detector.estimate_scope_lifetime("fn main()"), 100);
1092 }
1093
1094 #[test]
1095 fn test_estimate_scope_lifetime_module() {
1096 let config = LifecycleDetectorConfig::default();
1097 let detector = LifecycleDetector::new(config);
1098
1099 assert_eq!(
1100 detector.estimate_scope_lifetime("module::submodule::function"),
1101 1000
1102 );
1103 assert_eq!(detector.estimate_scope_lifetime("crate::module::fn"), 1000);
1104 }
1105
1106 #[test]
1107 fn test_estimate_scope_lifetime_block() {
1108 let config = LifecycleDetectorConfig::default();
1109 let detector = LifecycleDetector::new(config);
1110
1111 assert_eq!(detector.estimate_scope_lifetime("block_scope"), 500);
1112 assert_eq!(detector.estimate_scope_lifetime("unknown"), 500);
1113 }
1114
1115 #[test]
1116 fn test_detection_time_measurement() {
1117 let config = LifecycleDetectorConfig::default();
1118 let detector = LifecycleDetector::new(config);
1119
1120 let allocations: Vec<AllocationInfo> = (0..100)
1121 .map(|i| AllocationInfo::new(0x1000 + i * 1024, 1024))
1122 .collect();
1123
1124 let result = detector.detect(&allocations);
1125
1126 assert!(result.detection_time_ms < 1000); }
1128
1129 #[test]
1130 fn test_lifecycle_detector_debug() {
1131 let config = LifecycleDetectorConfig::default();
1132 let detector = LifecycleDetector::new(config);
1133
1134 let debug_str = format!("{:?}", detector);
1135 assert!(debug_str.contains("LifecycleDetector"));
1136 }
1137
1138 #[test]
1139 fn test_lifecycle_config_debug() {
1140 let config = LifecycleDetectorConfig::default();
1141 let debug_str = format!("{:?}", config);
1142
1143 assert!(debug_str.contains("enable_drop_trait_analysis"));
1144 assert!(debug_str.contains("enable_borrow_violation_detection"));
1145 }
1146
1147 #[test]
1148 fn test_lifecycle_config_clone() {
1149 let config = LifecycleDetectorConfig::default();
1150 let cloned = config.clone();
1151
1152 assert_eq!(
1153 config.enable_drop_trait_analysis,
1154 cloned.enable_drop_trait_analysis
1155 );
1156 assert_eq!(
1157 config.max_lifetime_analysis_depth,
1158 cloned.max_lifetime_analysis_depth
1159 );
1160 }
1161
1162 #[test]
1163 fn test_has_reference_cycle_true() {
1164 let config = LifecycleDetectorConfig::default();
1165 let detector = LifecycleDetector::new(config);
1166
1167 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo};
1168 let info = SmartPointerInfo {
1169 data_ptr: 0x1000,
1170 cloned_from: None,
1171 clones: vec![],
1172 ref_count_history: vec![
1173 RefCountSnapshot {
1174 timestamp: 0,
1175 strong_count: 1,
1176 weak_count: 0,
1177 },
1178 RefCountSnapshot {
1179 timestamp: 100,
1180 strong_count: 2,
1181 weak_count: 0,
1182 },
1183 RefCountSnapshot {
1184 timestamp: 200,
1185 strong_count: 1,
1186 weak_count: 0,
1187 },
1188 ],
1189 weak_count: Some(0),
1190 is_weak_reference: false,
1191 is_data_owner: true,
1192 is_implicitly_deallocated: false,
1193 pointer_type: crate::capture::types::SmartPointerType::Rc,
1194 };
1195
1196 assert!(detector.has_reference_cycle(&info));
1197 }
1198
1199 #[test]
1200 fn test_has_reference_cycle_false() {
1201 let config = LifecycleDetectorConfig::default();
1202 let detector = LifecycleDetector::new(config);
1203
1204 use crate::capture::types::{RefCountSnapshot, SmartPointerInfo};
1205 let info = SmartPointerInfo {
1206 data_ptr: 0x1000,
1207 cloned_from: None,
1208 clones: vec![],
1209 ref_count_history: vec![
1210 RefCountSnapshot {
1211 timestamp: 0,
1212 strong_count: 1,
1213 weak_count: 0,
1214 },
1215 RefCountSnapshot {
1216 timestamp: 100,
1217 strong_count: 2,
1218 weak_count: 0,
1219 },
1220 RefCountSnapshot {
1221 timestamp: 200,
1222 strong_count: 3,
1223 weak_count: 0,
1224 },
1225 ],
1226 weak_count: Some(0),
1227 is_weak_reference: false,
1228 is_data_owner: true,
1229 is_implicitly_deallocated: false,
1230 pointer_type: crate::capture::types::SmartPointerType::Arc,
1231 };
1232
1233 assert!(!detector.has_reference_cycle(&info));
1234 }
1235
1236 #[test]
1237 fn test_is_move_semantics_violation_true() {
1238 let config = LifecycleDetectorConfig::default();
1239 let detector = LifecycleDetector::new(config);
1240
1241 let mut alloc = AllocationInfo::new(0x1000, 1024);
1242 use crate::capture::types::CloneInfo;
1243 alloc.clone_info = Some(CloneInfo {
1244 clone_count: 1,
1245 is_clone: true,
1246 original_ptr: Some(0x2000),
1247 _source: None,
1248 _confidence: None,
1249 });
1250
1251 assert!(detector.is_move_semantics_violation(&alloc));
1252 }
1253
1254 #[test]
1255 fn test_is_move_semantics_violation_false() {
1256 let config = LifecycleDetectorConfig::default();
1257 let detector = LifecycleDetector::new(config);
1258
1259 let alloc = AllocationInfo::new(0x1000, 1024);
1260 assert!(!detector.is_move_semantics_violation(&alloc));
1261 }
1262
1263 #[test]
1264 fn test_is_borrow_after_move_true() {
1265 let config = LifecycleDetectorConfig::default();
1266 let detector = LifecycleDetector::new(config);
1267
1268 let mut alloc = AllocationInfo::new(0x1000, 1024);
1269 alloc.borrow_count = 5;
1270 use crate::capture::types::CloneInfo;
1271 alloc.clone_info = Some(CloneInfo {
1272 clone_count: 1,
1273 is_clone: true,
1274 original_ptr: None,
1275 _source: None,
1276 _confidence: None,
1277 });
1278
1279 assert!(detector.is_borrow_after_move(&alloc));
1280 }
1281
1282 #[test]
1283 fn test_is_borrow_after_move_false() {
1284 let config = LifecycleDetectorConfig::default();
1285 let detector = LifecycleDetector::new(config);
1286
1287 let alloc = AllocationInfo::new(0x1000, 1024);
1288 assert!(!detector.is_borrow_after_move(&alloc));
1289 }
1290
1291 #[test]
1292 fn test_detect_only_lifetime_issues() {
1293 let config = LifecycleDetectorConfig {
1294 enable_lifetime_violation_detection: true,
1295 enable_ownership_pattern_detection: false,
1296 enable_drop_trait_analysis: false,
1297 enable_borrow_violation_detection: false,
1298 max_lifetime_analysis_depth: 100,
1299 };
1300 let detector = LifecycleDetector::new(config);
1301
1302 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
1303 allocations[0].scope_name = Some("fn main".to_string());
1304 allocations[0].lifetime_ms = Some(5000);
1305
1306 let result = detector.detect(&allocations);
1307
1308 assert!(result.issues.iter().all(|i| {
1309 i.description.contains("Scope lifetime violation") || i.description.contains("lifetime")
1310 }));
1311 }
1312
1313 #[test]
1314 fn test_detect_only_ownership_issues() {
1315 let config = LifecycleDetectorConfig {
1316 enable_lifetime_violation_detection: false,
1317 enable_ownership_pattern_detection: true,
1318 enable_drop_trait_analysis: false,
1319 enable_borrow_violation_detection: false,
1320 max_lifetime_analysis_depth: 100,
1321 };
1322 let detector = LifecycleDetector::new(config);
1323
1324 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
1325 use crate::capture::types::CloneInfo;
1326 allocations[0].clone_info = Some(CloneInfo {
1327 clone_count: 50,
1328 is_clone: true,
1329 original_ptr: Some(0x1000),
1330 _source: None,
1331 _confidence: None,
1332 });
1333
1334 let result = detector.detect(&allocations);
1335
1336 assert!(result.issues.iter().all(|i| {
1337 i.description.contains("cloning") || i.description.contains("Move semantics")
1338 }));
1339 }
1340
1341 #[test]
1342 fn test_detect_only_drop_issues() {
1343 let config = LifecycleDetectorConfig {
1344 enable_lifetime_violation_detection: false,
1345 enable_ownership_pattern_detection: false,
1346 enable_drop_trait_analysis: true,
1347 enable_borrow_violation_detection: false,
1348 max_lifetime_analysis_depth: 100,
1349 };
1350 let detector = LifecycleDetector::new(config);
1351
1352 let allocations = vec![AllocationInfo::new(0x1000, 20 * 1024 * 1024)];
1353
1354 let result = detector.detect(&allocations);
1355
1356 assert!(result
1357 .issues
1358 .iter()
1359 .all(|i| i.description.contains("Large allocation") || i.description.contains("drop")));
1360 }
1361
1362 #[test]
1363 fn test_detect_only_borrow_issues() {
1364 let config = LifecycleDetectorConfig {
1365 enable_lifetime_violation_detection: false,
1366 enable_ownership_pattern_detection: false,
1367 enable_drop_trait_analysis: false,
1368 enable_borrow_violation_detection: true,
1369 max_lifetime_analysis_depth: 100,
1370 };
1371 let detector = LifecycleDetector::new(config);
1372
1373 let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
1374 use crate::capture::types::BorrowInfo;
1375 allocations[0].borrow_info = Some(BorrowInfo {
1376 immutable_borrows: 0,
1377 mutable_borrows: 3,
1378 max_concurrent_borrows: 3,
1379 last_borrow_timestamp: Some(1000),
1380 _source: None,
1381 _confidence: None,
1382 });
1383
1384 let result = detector.detect(&allocations);
1385
1386 assert!(result
1387 .issues
1388 .iter()
1389 .all(|i| i.description.contains("borrow")));
1390 }
1391}