Skip to main content

memscope_rs/analyzer/
detect.rs

1//! Detection analysis module.
2
3use crate::analyzer::report::{LeakReport, SafetyReport, UafReport};
4use crate::snapshot::ActiveAllocation;
5use crate::view::MemoryView;
6use tracing::{debug, warn};
7
8/// Detection analysis module.
9///
10/// Provides leak, UAF, and safety detection.
11pub struct DetectionAnalysis {
12    view: MemoryView,
13}
14
15impl DetectionAnalysis {
16    /// Create from view.
17    pub fn from_view(view: &MemoryView) -> Self {
18        debug!("Creating DetectionAnalysis with {} allocations", view.len());
19        Self { view: view.clone() }
20    }
21
22    /// Allocations that are still live at the cut.
23    ///
24    /// This is a **fact**, not a leak verdict: an allocation that was not released
25    /// inside the capture window is simply still live there.
26    pub fn unreleased(&self) -> Vec<&ActiveAllocation> {
27        self.view
28            .allocations()
29            .into_iter()
30            .filter(|a| a.ptr.is_some())
31            .collect()
32    }
33
34    /// Detect memory leaks.
35    ///
36    /// A leak is a **conclusion** that needs an assessment rule plus evidence about
37    /// capture completeness (plan ยง4.5). The view only carries the active set, so no
38    /// allocation can be assessed here and this returns an empty report.
39    ///
40    /// The historical implementation reported *every* live allocation as a leak
41    /// (`filter(|a| a.ptr.is_some())`), which made any normal program look like it was
42    /// leaking everything it held (plan A02). Use [`Self::unreleased`] when the caller
43    /// needs the "still live, no release observed" list.
44    pub fn leaks(&self) -> LeakReport {
45        let unreleased = self.unreleased();
46        if !unreleased.is_empty() {
47            debug!(
48                "Leak assessment not performed: {} allocations are still live at the cut",
49                unreleased.len()
50            );
51        }
52
53        LeakReport {
54            leak_count: 0,
55            total_leaked_bytes: 0,
56            leaked_allocations: Vec::new(),
57        }
58    }
59
60    /// Detect use-after-free.
61    ///
62    /// Analyzes event stream for potential UAF patterns by tracking
63    /// deallocations and checking for subsequent accesses to the same address.
64    ///
65    /// Note:
66    /// - Reallocations are NOT flagged as UAF because realloc on a
67    ///   deallocated pointer is valid behavior (the allocator may reuse the address).
68    /// - Metadata events are NOT flagged as UAF because they represent
69    ///   type/container information, not actual memory access.
70    /// - Only actual memory access events (read/write operations) on freed
71    ///   memory would indicate UAF, but these are not currently tracked.
72    ///
73    /// Returns an empty report as UAF detection requires runtime memory
74    /// access tracking which is not implemented in the current architecture.
75    pub fn uaf(&self) -> UafReport {
76        // UAF detection requires tracking actual memory accesses (read/write)
77        // on freed pointers. The current event system does not track these
78        // operations. Realloc and Metadata events are not UAF indicators:
79        // - Realloc: valid allocator behavior, may reuse addresses
80        // - Metadata: type information only, no memory access
81        //
82        // For proper UAF detection, consider using sanitizers like ASAN
83        // or instrumenting memory access at runtime.
84        debug!("UAF detection: not implemented, returning empty report");
85        UafReport::empty()
86    }
87
88    /// Analyze memory safety.
89    ///
90    /// Checks for common safety issues.
91    pub fn safety(&self) -> SafetyReport {
92        let allocations = self.view.allocations();
93        let report = SafetyReport::from_allocations(&allocations);
94        if report.issue_count > 0 {
95            warn!(
96                "Safety analysis: found {} potential safety issues",
97                report.issue_count
98            );
99        }
100        report
101    }
102
103    /// Get detection summary.
104    pub fn summary(&self) -> DetectionSummary {
105        let leaks = self.leaks();
106        let safety = self.safety();
107        DetectionSummary {
108            leak_count: leaks.leak_count,
109            leaked_bytes: leaks.total_leaked_bytes,
110            uaf_count: 0, // UAF detection not implemented
111            safety_issues: safety.issue_count,
112        }
113    }
114}
115
116/// Detection summary.
117#[derive(Debug, Clone)]
118pub struct DetectionSummary {
119    /// Number of leaks
120    pub leak_count: usize,
121    /// Total leaked bytes
122    pub leaked_bytes: usize,
123    /// Number of UAF issues
124    pub uaf_count: usize,
125    /// Number of safety issues
126    pub safety_issues: usize,
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::event_store::MemoryEvent;
133
134    /// Objective: Verify that a still-live allocation is reported as "not released"
135    /// and *not* as a leak (plan A02).
136    /// Invariants: one unreleased allocation of 128 bytes, zero leak verdicts.
137    #[test]
138    fn test_live_allocation_is_not_a_leak() {
139        let events = vec![
140            MemoryEvent::allocate(0x1000, 64, 1),
141            MemoryEvent::allocate(0x2000, 128, 2),
142            MemoryEvent::deallocate(0x1000, 64, 1),
143        ];
144        let view = MemoryView::from_events(events);
145        let analysis = DetectionAnalysis::from_view(&view);
146
147        let unreleased = analysis.unreleased();
148        assert_eq!(
149            unreleased.len(),
150            1,
151            "one allocation is still live at the cut"
152        );
153        assert_eq!(
154            unreleased[0].size, 128,
155            "the unreleased allocation keeps its recorded size"
156        );
157
158        let leaks = analysis.leaks();
159        assert_eq!(
160            leaks.leak_count, 0,
161            "being alive at the cut is not evidence of a leak"
162        );
163        assert_eq!(
164            leaks.total_leaked_bytes, 0,
165            "no leaked bytes may be reported without an assessment"
166        );
167    }
168
169    #[test]
170    fn test_no_leaks() {
171        let events = vec![
172            MemoryEvent::allocate(0x1000, 64, 1),
173            MemoryEvent::deallocate(0x1000, 64, 1),
174        ];
175        let view = MemoryView::from_events(events);
176        let analysis = DetectionAnalysis::from_view(&view);
177        let leaks = analysis.leaks();
178        assert_eq!(leaks.leak_count, 0);
179    }
180
181    #[test]
182    fn test_uaf_returns_empty() {
183        // UAF detection is not implemented, should return empty report
184        let events = vec![
185            MemoryEvent::allocate(0x1000, 64, 1),
186            MemoryEvent::deallocate(0x1000, 64, 1),
187        ];
188        let view = MemoryView::from_events(events);
189        let analysis = DetectionAnalysis::from_view(&view);
190        let uaf = analysis.uaf();
191        assert_eq!(uaf.uaf_count, 0);
192    }
193}