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