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