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//! let config = LifecycleDetectorConfig::default();
12//! let detector = LifecycleDetector::new(config);
13//!
14//! let allocations = vec![];
15//! let result = detector.detect(&allocations);
16//!
17//! println!("Found {} lifecycle issues", result.issues.len());
18//! ```
19
20use crate::analysis::detectors::{
21    DetectionResult, DetectionStatistics, Detector, DetectorConfig, DetectorError, Issue,
22    IssueCategory, IssueSeverity,
23};
24use crate::capture::types::AllocationInfo;
25
26/// Configuration for lifecycle detector
27#[derive(Debug, Clone)]
28pub struct LifecycleDetectorConfig {
29    /// Enable drop trait analysis
30    pub enable_drop_trait_analysis: bool,
31
32    /// Enable borrow violation detection
33    pub enable_borrow_violation_detection: bool,
34
35    /// Enable lifetime violation detection
36    pub enable_lifetime_violation_detection: bool,
37
38    /// Enable ownership pattern detection
39    pub enable_ownership_pattern_detection: bool,
40
41    /// Maximum depth for lifetime analysis
42    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/// Lifecycle pattern detector
58///
59/// Detects lifecycle pattern issues by analyzing memory lifecycle and borrow patterns.
60///
61/// # Detection Methods
62///
63/// - **Drop trait**: Analyzes Drop trait implementations
64/// - **Borrow violations**: Detects borrow checker violations
65/// - **Lifetime violations**: Detects lifetime annotation issues
66/// - **Ownership patterns**: Analyzes ownership transfer and clone patterns
67#[derive(Debug)]
68pub struct LifecycleDetector {
69    config: LifecycleDetectorConfig,
70    base_config: DetectorConfig,
71}
72
73impl LifecycleDetector {
74    /// Create a new lifecycle detector
75    ///
76    /// # Arguments
77    ///
78    /// * `config` - Configuration for the lifecycle detector
79    ///
80    /// # Example
81    ///
82    /// ```rust
83    /// use memscope_rs::analysis::detectors::{LifecycleDetector, LifecycleDetectorConfig};
84    ///
85    /// let config = LifecycleDetectorConfig::default();
86    /// let detector = LifecycleDetector::new(config);
87    /// ```
88    pub fn new(config: LifecycleDetectorConfig) -> Self {
89        Self {
90            config,
91            base_config: DetectorConfig::default(),
92        }
93    }
94
95    /// Get the lifecycle detector configuration
96    pub fn lifecycle_config(&self) -> &LifecycleDetectorConfig {
97        &self.config
98    }
99
100    /// Update the lifecycle detector configuration
101    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        // Detect lifecycle issues
124        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        // Detect ownership patterns
130        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        // Detect drop trait issues
136        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        // Detect borrow violations
142        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    /// Detect lifetime issues
169    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            // Check for dangling references
178            if let Some(lifecycle) = &alloc.lifecycle_tracking {
179                // Check for lifecycle events after deallocation
180                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                // Check for excessive lifecycle events
207                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            // Check for temporary object misuse
231            if let Some(temp_info) = &alloc.temporary_object {
232                if let Some(actual_lifetime) = alloc.lifetime_ms {
233                    // Estimate expected lifetime
234                    let expected_lifetime = match temp_info.lifetime_ns {
235                        Some(ns) => ns / 1_000_000, // Convert ns to ms
236                        None => 100,                // Default 100ms
237                    };
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            // Check for lifetime scope violations
264            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    /// Detect ownership patterns
296    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            // Check for excessive cloning
305            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                // Check for expensive clone operations
330                if clone_info.clone_count > 0 && alloc.size > 1024 * 1024 {
331                    // >1MB
332                    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            // Check for smart pointer patterns
355            if let Some(smart_ptr_info) = &alloc.smart_pointer_info {
356                // Check for excessive ref count history
357                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                // Check for reference cycles
382                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            // Check for ownership transfer issues
404            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    /// Detect drop trait issues
428    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            // Check for missing drop implementation on large allocations
437            if alloc.size > 10 * 1024 * 1024 {
438                // >10MB
439                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            // Check for panics during drop using lifecycle events
461            if let Some(lifecycle) = &alloc.lifecycle_tracking {
462                for event in &lifecycle.lifecycle_events {
463                    // Check if event indicates a problematic state
464                    if let crate::capture::types::LifecycleEventType::Drop = event.event_type {
465                        // Check if there are lifecycle events after drop
466                        if let Some(dealloc_time) = alloc.timestamp_dealloc {
467                            if event.timestamp > dealloc_time + 1000 {
468                                // Event more than 1s after deallocation
469                                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    /// Detect borrow violations
499    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            // Check for concurrent mutable borrows
508            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                // Check for excessive borrow count
532                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            // Check for borrow after move
558            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    /// Assess clone severity
583    fn assess_clone_severity(&self, clone_count: usize) -> IssueSeverity {
584        // Critical for very high clone counts
585        if clone_count > 1000 {
586            return IssueSeverity::Critical;
587        }
588
589        // High for high clone counts
590        if clone_count > 100 {
591            return IssueSeverity::High;
592        }
593
594        // Medium for moderate clone counts
595        IssueSeverity::Medium
596    }
597
598    /// Estimate expected lifetime for a scope
599    fn estimate_scope_lifetime(&self, scope_name: &str) -> u64 {
600        if scope_name.contains("fn ") {
601            100 // 100ms for function-local
602        } else if scope_name.contains("::") {
603            1000 // 1 second for module-level
604        } else {
605            500 // 500ms for block-level
606        }
607    }
608
609    /// Check if smart pointer has reference cycle
610    fn has_reference_cycle(
611        &self,
612        smart_ptr_info: &crate::capture::types::SmartPointerInfo,
613    ) -> bool {
614        // Check if reference count history shows cycles
615        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                // Strong count seen before - potential cycle
619                return true;
620            }
621            counts.insert(snapshot.strong_count);
622        }
623        false
624    }
625
626    /// Check if move semantics are violated
627    fn is_move_semantics_violation(&self, alloc: &AllocationInfo) -> bool {
628        // Check for use after move (indicated by access after ownership transfer)
629        if let Some(clone_info) = &alloc.clone_info {
630            if clone_info.is_clone && clone_info.original_ptr.is_some() {
631                // Clone after potential move
632                return true;
633            }
634        }
635        false
636    }
637
638    /// Check if there's a borrow after move
639    fn is_borrow_after_move(&self, alloc: &AllocationInfo) -> bool {
640        // Check if there are borrows after the last ownership transfer
641        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, // Excessive cloning
710            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, // Concurrent mutable borrows
737            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); // 5 seconds, much longer than expected
760
761        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)]; // 20MB
815
816        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)]; // 2MB
887
888        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); // Should be fast
1125    }
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}