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