Skip to main content

memscope_rs/analysis_engine/
detector_adapter.rs

1//! Detector adapter - Converts Detector trait to Analyzer trait
2//!
3//! This module provides an adapter that allows detectors to be used
4//! with the AnalysisEngine, which expects the Analyzer trait.
5
6use crate::analysis::detectors::Detector;
7use crate::analysis_engine::analyzer::{AnalysisResult, Analyzer, Finding, Severity};
8use crate::capture::types::AllocationInfo;
9use crate::snapshot::ActiveAllocation;
10use std::sync::Arc;
11
12/// Convert ActiveAllocation to AllocationInfo
13fn active_to_allocation_info(active: &ActiveAllocation) -> AllocationInfo {
14    let thread_id_u64 = active.thread_id;
15    // Note: Rust's ThreadId type cannot be reconstructed from u64.
16    // We use the current thread's ID as a placeholder.
17    // The actual thread ID value is available in thread_id_u64 field.
18    // This is a known limitation that requires tracking ThreadId at allocation time.
19    let thread_id = std::thread::current().id();
20
21    AllocationInfo {
22        ptr: active.ptr,
23        size: active.size,
24        var_name: active.var_name.clone(),
25        type_name: active.type_name.clone(),
26        scope_name: None,
27        timestamp_alloc: active.allocated_at,
28        timestamp_dealloc: None,
29        thread_id,
30        thread_id_u64,
31        borrow_count: 0,
32        stack_trace: None,
33        is_leaked: false,
34        lifetime_ms: None,
35        borrow_info: None,
36        clone_info: None,
37        ownership_history_available: false,
38        smart_pointer_info: None,
39        memory_layout: None,
40        generic_info: None,
41        dynamic_type_info: None,
42        runtime_state: None,
43        stack_allocation: None,
44        temporary_object: None,
45        fragmentation_analysis: None,
46        generic_instantiation: None,
47        type_relationships: None,
48        type_usage: None,
49        function_call_tracking: None,
50        lifecycle_tracking: None,
51        access_tracking: None,
52        drop_chain_analysis: None,
53    }
54}
55
56/// Adapter that converts a Detector into an Analyzer
57///
58/// This allows detectors from the analysis module to be used
59/// with the AnalysisEngine, which expects the Analyzer trait.
60pub struct DetectorToAnalyzer<D: Detector + Send + Sync> {
61    detector: Arc<D>,
62}
63
64impl<D: Detector + Send + Sync> DetectorToAnalyzer<D> {
65    /// Create a new adapter from a detector
66    pub fn new(detector: D) -> Self {
67        Self {
68            detector: Arc::new(detector),
69        }
70    }
71
72    /// Create a new adapter from a boxed detector
73    pub fn from_boxed(detector: Box<D>) -> Self {
74        Self {
75            detector: Arc::from(detector),
76        }
77    }
78
79    /// Get a reference to the underlying detector
80    pub fn detector(&self) -> &D {
81        &self.detector
82    }
83}
84
85impl<D: Detector + Send + Sync + 'static> Analyzer for DetectorToAnalyzer<D> {
86    fn name(&self) -> &str {
87        self.detector.name()
88    }
89
90    fn analyze(&self, snapshot: &crate::snapshot::MemorySnapshot) -> AnalysisResult {
91        // Convert active allocations to AllocationInfo
92        let allocations: Vec<AllocationInfo> = snapshot
93            .active_allocations
94            .values()
95            .map(active_to_allocation_info)
96            .collect();
97
98        // Run the detector
99        let detection_result = self.detector.detect(&allocations);
100
101        // Convert DetectionResult to AnalysisResult
102        let findings: Vec<Finding> = detection_result
103            .issues
104            .into_iter()
105            .map(|issue| Finding {
106                issue_type: format!("{:?}", issue.category),
107                description: issue.description,
108                ptr: issue.allocation_ptr,
109                size: None, // Issue doesn't have size info
110                context: issue.suggested_fix.unwrap_or_default(),
111            })
112            .collect();
113
114        // Determine overall severity based on the most severe issue
115        let severity = findings
116            .iter()
117            .map(|f| &f.issue_type)
118            .fold(Severity::Info, |acc, _| acc);
119
120        AnalysisResult {
121            analyzer_name: detection_result.detector_name,
122            issue_count: findings.len(),
123            severity,
124            description: format!(
125                "Found {} issues in {} allocations (took {}ms)",
126                findings.len(),
127                detection_result.statistics.total_allocations,
128                detection_result.detection_time_ms
129            ),
130            findings,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::analysis::detectors::{LeakDetector, LeakDetectorConfig};
139    use crate::snapshot::MemorySnapshot;
140
141    #[test]
142    fn test_detector_adapter_creation() {
143        let detector = LeakDetector::new(LeakDetectorConfig::default());
144        let adapter = DetectorToAnalyzer::new(detector);
145        assert_eq!(adapter.name(), "LeakDetector");
146    }
147
148    #[test]
149    fn test_detector_adapter_analyze() {
150        let detector = LeakDetector::new(LeakDetectorConfig::default());
151        let adapter = DetectorToAnalyzer::new(detector);
152
153        let snapshot = MemorySnapshot::new();
154        let result = adapter.analyze(&snapshot);
155
156        assert_eq!(result.analyzer_name, "LeakDetector");
157        assert_eq!(result.issue_count, 0);
158    }
159
160    #[test]
161    fn test_active_to_allocation_info() {
162        let active = ActiveAllocation {
163            ptr: 0x1000,
164            size: 1024,
165            allocated_at: 1000,
166            var_name: Some("test_var".to_string()),
167            type_name: Some("Vec<u8>".to_string()),
168            thread_id: 42,
169            call_stack_hash: None,
170        };
171
172        let alloc = active_to_allocation_info(&active);
173
174        assert_eq!(alloc.ptr, 0x1000);
175        assert_eq!(alloc.size, 1024);
176        assert_eq!(alloc.timestamp_alloc, 1000);
177        assert_eq!(alloc.var_name, Some("test_var".to_string()));
178        assert_eq!(alloc.type_name, Some("Vec<u8>".to_string()));
179        // thread_id is the current thread ID, not 42
180        assert_eq!(alloc.borrow_count, 0);
181        assert!(!alloc.is_leaked);
182    }
183}