memscope_rs/smart_pointers/
analyzer.rs

1use super::{PointerType, SmartPointerTracker};
2use std::collections::HashMap;
3use std::time::Duration;
4
5#[derive(Debug, Clone)]
6pub enum LeakPattern {
7    CircularReference,
8    WeakReferenceHold,
9    LongLivedBox,
10    HighRefCount,
11    SynchronizationOveruse,
12}
13
14#[derive(Debug, Clone)]
15pub struct AnalysisResult {
16    pub total_active_pointers: usize,
17    pub memory_usage_by_type: HashMap<PointerType, usize>,
18    pub detected_patterns: Vec<(LeakPattern, Vec<u64>)>,
19    pub recommendations: Vec<String>,
20    pub health_score: f64,
21}
22
23pub struct SmartPointerAnalyzer {
24    leak_thresholds: LeakThresholds,
25}
26
27#[derive(Debug)]
28struct LeakThresholds {
29    long_lived_secs: u64,
30    high_ref_count: usize,
31    max_sync_objects: usize,
32    circular_ref_timeout: Duration,
33}
34
35impl Default for LeakThresholds {
36    fn default() -> Self {
37        Self {
38            long_lived_secs: 3600, // 1 hour
39            high_ref_count: 10,
40            max_sync_objects: 100,
41            circular_ref_timeout: Duration::from_secs(300), // 5 minutes
42        }
43    }
44}
45
46impl SmartPointerAnalyzer {
47    pub fn new() -> Self {
48        Self {
49            leak_thresholds: LeakThresholds::default(),
50        }
51    }
52
53    pub fn with_thresholds(
54        long_lived_secs: u64,
55        high_ref_count: usize,
56        max_sync_objects: usize,
57    ) -> Self {
58        Self {
59            leak_thresholds: LeakThresholds {
60                long_lived_secs,
61                high_ref_count,
62                max_sync_objects,
63                circular_ref_timeout: Duration::from_secs(300),
64            },
65        }
66    }
67
68    pub fn analyze(&self, tracker: &SmartPointerTracker) -> AnalysisResult {
69        let total_active_pointers = tracker.get_active_count();
70        let memory_usage_by_type = tracker.get_memory_usage_by_type();
71        let mut detected_patterns = Vec::new();
72        let mut recommendations = Vec::new();
73
74        // Detect long-lived Box allocations
75        let long_lived_boxes = self.detect_long_lived_boxes(tracker);
76        if !long_lived_boxes.is_empty() {
77            detected_patterns.push((LeakPattern::LongLivedBox, long_lived_boxes));
78            recommendations.push(
79                "Consider using Rc/Arc for shared data instead of long-lived Box".to_string(),
80            );
81        }
82
83        // Detect high reference counts
84        let high_ref_counts = self.detect_high_ref_counts(tracker);
85        if !high_ref_counts.is_empty() {
86            detected_patterns.push((LeakPattern::HighRefCount, high_ref_counts));
87            recommendations.push(
88                "Review objects with high reference counts for potential circular references"
89                    .to_string(),
90            );
91        }
92
93        // Detect synchronization overuse
94        let sync_overuse = self.detect_sync_overuse(tracker);
95        if !sync_overuse.is_empty() {
96            detected_patterns.push((LeakPattern::SynchronizationOveruse, sync_overuse));
97            recommendations.push(
98                "Consider reducing synchronization overhead or using lock-free alternatives"
99                    .to_string(),
100            );
101        }
102
103        // Detect circular references using enhanced heuristics
104        let circular_refs = self.detect_circular_references(tracker);
105        if !circular_refs.is_empty() {
106            detected_patterns.push((LeakPattern::CircularReference, circular_refs));
107            recommendations.push(
108                "Circular reference detected - consider using Weak references to break cycles"
109                    .to_string(),
110            );
111        }
112
113        // Detect weak reference holds
114        let weak_holds = self.detect_weak_reference_holds(tracker);
115        if !weak_holds.is_empty() {
116            detected_patterns.push((LeakPattern::WeakReferenceHold, weak_holds));
117            recommendations
118                .push("Check if weak references are being held longer than necessary".to_string());
119        }
120
121        let health_score = self.calculate_health_score(&detected_patterns, total_active_pointers);
122
123        AnalysisResult {
124            total_active_pointers,
125            memory_usage_by_type,
126            detected_patterns,
127            recommendations,
128            health_score,
129        }
130    }
131
132    fn detect_long_lived_boxes(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
133        tracker
134            .find_long_lived_pointers(self.leak_thresholds.long_lived_secs)
135            .iter()
136            .filter(|info| info.ptr_type == PointerType::Box)
137            .map(|info| info.allocation_id)
138            .collect()
139    }
140
141    fn detect_high_ref_counts(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
142        let mut high_ref_ids = Vec::new();
143
144        for ptr_type in &[PointerType::Rc, PointerType::Arc] {
145            let pointers = tracker.get_active_by_type(ptr_type);
146            for info in pointers {
147                if let Some(ref_count) = info.ref_count {
148                    // Enhanced detection: lower threshold for better recall
149                    if ref_count >= self.leak_thresholds.high_ref_count
150                        || (ref_count >= 3 && self.is_likely_circular_reference(info, tracker))
151                    {
152                        high_ref_ids.push(info.allocation_id);
153                    }
154                }
155            }
156        }
157
158        high_ref_ids
159    }
160
161    fn detect_sync_overuse(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
162        let sync_types = [PointerType::Mutex, PointerType::RwLock, PointerType::Arc];
163        let mut sync_count = 0;
164        let mut overuse_ids = Vec::new();
165
166        for sync_type in &sync_types {
167            let pointers = tracker.get_active_by_type(sync_type);
168            sync_count += pointers.len();
169
170            if sync_count > self.leak_thresholds.max_sync_objects {
171                overuse_ids.extend(pointers.iter().map(|info| info.allocation_id));
172            }
173        }
174
175        overuse_ids
176    }
177
178    fn detect_weak_reference_holds(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
179        tracker
180            .get_active_by_type(&PointerType::Weak)
181            .iter()
182            .filter(|info| {
183                // Enhanced weak reference leak detection
184                info.age() > self.leak_thresholds.circular_ref_timeout ||
185                // Also detect weak refs that might be part of circular patterns
186                (info.age() > Duration::from_secs(30) && self.is_suspicious_weak_ref(info, tracker))
187            })
188            .map(|info| info.allocation_id)
189            .collect()
190    }
191
192    /// Enhanced circular reference detection using heuristics
193    fn detect_circular_references(&self, tracker: &SmartPointerTracker) -> Vec<u64> {
194        let mut circular_ids = Vec::new();
195
196        // Look for pairs of Rc/Arc with similar lifetimes and ref counts
197        let rc_pointers = tracker.get_active_by_type(&PointerType::Rc);
198        let arc_pointers = tracker.get_active_by_type(&PointerType::Arc);
199
200        // Check Rc circular patterns
201        circular_ids.extend(self.find_circular_patterns(&rc_pointers));
202        // Check Arc circular patterns
203        circular_ids.extend(self.find_circular_patterns(&arc_pointers));
204
205        circular_ids
206    }
207
208    fn find_circular_patterns(&self, pointers: &[&super::PointerInfo]) -> Vec<u64> {
209        let mut patterns = Vec::new();
210
211        for (i, info_a) in pointers.iter().enumerate() {
212            for info_b in pointers.iter().skip(i + 1) {
213                // Heuristic: two pointers with ref_count >= 2, similar age, similar size
214                if let (Some(ref_a), Some(ref_b)) = (info_a.ref_count, info_b.ref_count) {
215                    if ref_a >= 2 && ref_b >= 2 {
216                        let age_diff = info_a.age().as_secs().abs_diff(info_b.age().as_secs());
217                        let size_ratio =
218                            info_a.allocation_size as f64 / info_b.allocation_size as f64;
219
220                        // Strong indicators of circular reference
221                        if age_diff <= 5 && // Created within 5 seconds of each other
222                           (0.5..=2.0).contains(&size_ratio) && // Similar sizes
223                           ref_a == ref_b
224                        {
225                            // Same reference count (suspicious)
226                            patterns.push(info_a.allocation_id);
227                            patterns.push(info_b.allocation_id);
228                        }
229                    }
230                }
231            }
232        }
233
234        patterns
235    }
236
237    fn is_likely_circular_reference(
238        &self,
239        info: &super::PointerInfo,
240        tracker: &SmartPointerTracker,
241    ) -> bool {
242        // Heuristic: check if there are other similar allocations that might form a cycle
243        let same_type_pointers = tracker.get_active_by_type(&info.ptr_type);
244
245        for other in same_type_pointers {
246            if other.allocation_id != info.allocation_id {
247                if let (Some(ref_a), Some(ref_b)) = (info.ref_count, other.ref_count) {
248                    // Both have ref count >= 2 and were created close in time
249                    if ref_a >= 2 && ref_b >= 2 {
250                        let age_diff = info.age().as_secs().abs_diff(other.age().as_secs());
251                        if age_diff <= 10 {
252                            // Created within 10 seconds
253                            return true;
254                        }
255                    }
256                }
257            }
258        }
259
260        false
261    }
262
263    fn is_suspicious_weak_ref(
264        &self,
265        info: &super::PointerInfo,
266        _tracker: &SmartPointerTracker,
267    ) -> bool {
268        // Weak references that live longer than 30 seconds might indicate
269        // they're being held to break circular references but never cleaned up
270        info.age() > Duration::from_secs(30)
271    }
272
273    fn calculate_health_score(
274        &self,
275        patterns: &[(LeakPattern, Vec<u64>)],
276        total_pointers: usize,
277    ) -> f64 {
278        if total_pointers == 0 {
279            return 1.0;
280        }
281
282        let mut penalty = 0.0;
283
284        for (pattern, ids) in patterns {
285            let severity = match pattern {
286                LeakPattern::CircularReference => 0.3,
287                LeakPattern::WeakReferenceHold => 0.1,
288                LeakPattern::LongLivedBox => 0.15,
289                LeakPattern::HighRefCount => 0.2,
290                LeakPattern::SynchronizationOveruse => 0.1,
291            };
292
293            let ratio = ids.len() as f64 / total_pointers as f64;
294            penalty += severity * ratio;
295        }
296
297        (1.0 - penalty).max(0.0)
298    }
299
300    pub fn generate_report(&self, result: &AnalysisResult) -> String {
301        let mut report = String::new();
302
303        report.push_str("Smart Pointer Analysis Report\n");
304        report.push_str("============================\n\n");
305
306        report.push_str(&format!(
307            "Total Active Pointers: {}\n",
308            result.total_active_pointers
309        ));
310        report.push_str(&format!(
311            "Health Score: {:.2}/1.00\n\n",
312            result.health_score
313        ));
314
315        report.push_str("Memory Usage by Type:\n");
316        for (ptr_type, usage) in &result.memory_usage_by_type {
317            report.push_str(&format!("  {:?}: {} bytes\n", ptr_type, usage));
318        }
319
320        if !result.detected_patterns.is_empty() {
321            report.push_str("\nDetected Patterns:\n");
322            for (pattern, ids) in &result.detected_patterns {
323                report.push_str(&format!("  {:?}: {} instances\n", pattern, ids.len()));
324            }
325        }
326
327        if !result.recommendations.is_empty() {
328            report.push_str("\nRecommendations:\n");
329            for (i, rec) in result.recommendations.iter().enumerate() {
330                report.push_str(&format!("  {}. {}\n", i + 1, rec));
331            }
332        }
333
334        report
335    }
336}
337
338impl Default for SmartPointerAnalyzer {
339    fn default() -> Self {
340        Self::new()
341    }
342}
343
344impl AnalysisResult {
345    pub fn is_healthy(&self) -> bool {
346        self.health_score >= 0.8
347    }
348
349    pub fn has_critical_issues(&self) -> bool {
350        self.detected_patterns.iter().any(|(pattern, ids)| {
351            matches!(pattern, LeakPattern::CircularReference) && !ids.is_empty()
352        })
353    }
354
355    pub fn total_memory_usage(&self) -> usize {
356        self.memory_usage_by_type.values().sum()
357    }
358
359    pub fn pattern_count(&self, pattern: &LeakPattern) -> usize {
360        self.detected_patterns
361            .iter()
362            .find(|(p, _)| std::mem::discriminant(p) == std::mem::discriminant(pattern))
363            .map(|(_, ids)| ids.len())
364            .unwrap_or(0)
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn test_basic_analysis() {
374        let mut tracker = SmartPointerTracker::new();
375        let analyzer = SmartPointerAnalyzer::new();
376
377        tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
378        tracker.track_allocation(0x2000, PointerType::Arc, 128, "Data".to_string(), Some(1));
379
380        let result = analyzer.analyze(&tracker);
381
382        assert_eq!(result.total_active_pointers, 2);
383        assert!(result.health_score > 0.8);
384        assert!(!result.has_critical_issues());
385    }
386
387    #[test]
388    fn test_high_ref_count_detection() {
389        let mut tracker = SmartPointerTracker::new();
390        let analyzer = SmartPointerAnalyzer::with_thresholds(3600, 2, 100);
391
392        tracker.track_allocation(0x1000, PointerType::Rc, 64, "String".to_string(), Some(5));
393
394        let result = analyzer.analyze(&tracker);
395
396        assert_eq!(result.pattern_count(&LeakPattern::HighRefCount), 1);
397        assert!(result.health_score < 1.0);
398    }
399
400    #[test]
401    fn test_health_score_calculation() {
402        let tracker = SmartPointerTracker::new();
403        let analyzer = SmartPointerAnalyzer::new();
404
405        let result = analyzer.analyze(&tracker);
406
407        // Empty tracker should have perfect health score
408        assert_eq!(result.health_score, 1.0);
409        assert!(result.is_healthy());
410    }
411
412    #[test]
413    fn test_report_generation() {
414        let mut tracker = SmartPointerTracker::new();
415        let analyzer = SmartPointerAnalyzer::new();
416
417        tracker.track_allocation(0x1000, PointerType::Box, 64, "String".to_string(), None);
418
419        let result = analyzer.analyze(&tracker);
420        let report = analyzer.generate_report(&result);
421
422        assert!(report.contains("Smart Pointer Analysis Report"));
423        assert!(report.contains("Total Active Pointers: 1"));
424        assert!(report.contains("Health Score:"));
425    }
426}