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