Skip to main content

memscope_rs/analysis/detectors/
lifecycle_detector.rs

1//! Lifecycle pattern detection
2//!
3//! This module provides functionality for detecting lifecycle pattern issues in Rust programs.
4//!
5//! # Example
6//!
7//! ```rust
8//! use memscope_rs::analysis::detectors::{LifecycleDetector, LifecycleDetectorConfig, Detector};
9//! use memscope_rs::capture::types::AllocationInfo;
10//!
11//! fn main() {
12//!     let config = LifecycleDetectorConfig::default();
13//!     let detector = LifecycleDetector::new(config);
14//!
15//!     let allocations = vec![];
16//!     let result = detector.detect(&allocations);
17//!
18//!     println!("Found {} lifecycle issues", result.issues.len());
19//! }
20//! ```
21
22use crate::analysis::detectors::{
23    DetectionResult, DetectionStatistics, Detector, DetectorConfig, DetectorError, Issue,
24    IssueCategory, IssueSeverity,
25};
26use crate::capture::types::AllocationInfo;
27
28/// Configuration for lifecycle detector
29#[derive(Debug, Clone)]
30pub struct LifecycleDetectorConfig {
31    /// Enable drop trait analysis
32    pub enable_drop_trait_analysis: bool,
33
34    /// Enable borrow violation detection
35    pub enable_borrow_violation_detection: bool,
36
37    /// Enable lifetime violation detection
38    pub enable_lifetime_violation_detection: bool,
39
40    /// Enable ownership pattern detection
41    pub enable_ownership_pattern_detection: bool,
42
43    /// Maximum depth for lifetime analysis
44    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/// Lifecycle pattern detector
60///
61/// Detects lifecycle pattern issues by analyzing memory lifecycle and borrow patterns.
62///
63/// # Detection Methods
64///
65/// - **Drop trait**: Analyzes Drop trait implementations
66/// - **Borrow violations**: Detects borrow checker violations
67/// - **Lifetime violations**: Detects lifetime annotation issues
68/// - **Ownership patterns**: Analyzes ownership transfer and clone patterns
69#[derive(Debug)]
70pub struct LifecycleDetector {
71    config: LifecycleDetectorConfig,
72    base_config: DetectorConfig,
73}
74
75impl LifecycleDetector {
76    /// Create a new lifecycle detector
77    ///
78    /// # Arguments
79    ///
80    /// * `config` - Configuration for the lifecycle detector
81    ///
82    /// # Example
83    ///
84    /// ```rust
85    /// use memscope_rs::analysis::detectors::{LifecycleDetector, LifecycleDetectorConfig};
86    ///
87    /// let config = LifecycleDetectorConfig::default();
88    /// let detector = LifecycleDetector::new(config);
89    /// ```
90    pub fn new(config: LifecycleDetectorConfig) -> Self {
91        Self {
92            config,
93            base_config: DetectorConfig::default(),
94        }
95    }
96
97    /// Get the lifecycle detector configuration
98    pub fn lifecycle_config(&self) -> &LifecycleDetectorConfig {
99        &self.config
100    }
101
102    /// Update the lifecycle detector configuration
103    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        // Detect lifecycle issues
126        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        // Detect ownership patterns
132        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        // Detect drop trait issues
138        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        // Detect borrow violations
144        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    /// Detect lifetime issues
171    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            // Check for dangling references
180            if let Some(lifecycle) = &alloc.lifecycle_tracking {
181                // Check for lifecycle events after deallocation
182                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                // Check for excessive lifecycle events
209                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            // Check for temporary object misuse
233            if let Some(temp_info) = &alloc.temporary_object {
234                if let Some(actual_lifetime) = alloc.lifetime_ms {
235                    // Estimate expected lifetime
236                    let expected_lifetime = match temp_info.lifetime_ns {
237                        Some(ns) => ns / 1_000_000, // Convert ns to ms
238                        None => 100,                // Default 100ms
239                    };
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            // Check for lifetime scope violations
266            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    /// Detect ownership patterns
298    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            // Check for excessive cloning
307            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                // Check for expensive clone operations
332                if clone_info.clone_count > 0 && alloc.size > 1024 * 1024 {
333                    // >1MB
334                    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            // Check for smart pointer patterns
357            if let Some(smart_ptr_info) = &alloc.smart_pointer_info {
358                // Check for excessive ref count history
359                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                // Check for reference cycles
384                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            // Check for ownership transfer issues
406            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    /// Detect drop trait issues
430    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            // Check for missing drop implementation on large allocations
439            if alloc.size > 10 * 1024 * 1024 {
440                // >10MB
441                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            // Check for panics during drop using lifecycle events
463            if let Some(lifecycle) = &alloc.lifecycle_tracking {
464                for event in &lifecycle.lifecycle_events {
465                    // Check if event indicates a problematic state
466                    if let crate::capture::types::LifecycleEventType::Drop = event.event_type {
467                        // Check if there are lifecycle events after drop
468                        if let Some(dealloc_time) = alloc.timestamp_dealloc {
469                            if event.timestamp > dealloc_time + 1000 {
470                                // Event more than 1s after deallocation
471                                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    /// Detect borrow violations
501    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            // Check for concurrent mutable borrows
510            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                // Check for excessive borrow count
534                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            // Check for borrow after move
560            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    /// Assess clone severity
585    fn assess_clone_severity(&self, clone_count: usize) -> IssueSeverity {
586        // Critical for very high clone counts
587        if clone_count > 1000 {
588            return IssueSeverity::Critical;
589        }
590
591        // High for high clone counts
592        if clone_count > 100 {
593            return IssueSeverity::High;
594        }
595
596        // Medium for moderate clone counts
597        IssueSeverity::Medium
598    }
599
600    /// Estimate expected lifetime for a scope
601    fn estimate_scope_lifetime(&self, scope_name: &str) -> u64 {
602        if scope_name.contains("fn ") {
603            100 // 100ms for function-local
604        } else if scope_name.contains("::") {
605            1000 // 1 second for module-level
606        } else {
607            500 // 500ms for block-level
608        }
609    }
610
611    /// Check if smart pointer has reference cycle
612    fn has_reference_cycle(
613        &self,
614        smart_ptr_info: &crate::capture::types::SmartPointerInfo,
615    ) -> bool {
616        // Check if reference count history shows cycles
617        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                // Strong count seen before - potential cycle
621                return true;
622            }
623            counts.insert(snapshot.strong_count);
624        }
625        false
626    }
627
628    /// Check if move semantics are violated
629    fn is_move_semantics_violation(&self, alloc: &AllocationInfo) -> bool {
630        // Check for use after move (indicated by access after ownership transfer)
631        if let Some(clone_info) = &alloc.clone_info {
632            if clone_info.is_clone && clone_info.original_ptr.is_some() {
633                // Clone after potential move
634                return true;
635            }
636        }
637        false
638    }
639
640    /// Check if there's a borrow after move
641    fn is_borrow_after_move(&self, alloc: &AllocationInfo) -> bool {
642        // Check if there are borrows after the last ownership transfer
643        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, // Excessive cloning
712            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, // Concurrent mutable borrows
739            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); // 5 seconds, much longer than expected
762
763        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)]; // 20MB
817
818        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)]; // 2MB
889
890        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); // Should be fast
1127    }
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}