Skip to main content

memscope_rs/analysis/closure/
analyzer.rs

1use crate::analysis::closure::types::*;
2use crate::capture::types::AllocationInfo;
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex, OnceLock};
5
6static GLOBAL_CLOSURE_ANALYZER: OnceLock<Arc<ClosureAnalyzer>> = OnceLock::new();
7
8pub fn get_global_closure_analyzer() -> Arc<ClosureAnalyzer> {
9    GLOBAL_CLOSURE_ANALYZER
10        .get_or_init(|| Arc::new(ClosureAnalyzer::new()))
11        .clone()
12}
13
14pub struct ClosureAnalyzer {
15    closures: Mutex<HashMap<usize, ClosureInfo>>,
16    capture_events: Mutex<Vec<CaptureEvent>>,
17    lifetime_graph: Mutex<LifetimeGraph>,
18}
19
20impl ClosureAnalyzer {
21    pub fn new() -> Self {
22        Self {
23            closures: Mutex::new(HashMap::new()),
24            capture_events: Mutex::new(Vec::new()),
25            lifetime_graph: Mutex::new(LifetimeGraph::new()),
26        }
27    }
28
29    pub fn register_closure(&self, closure_ptr: usize, captures: Vec<CaptureInfo>) {
30        let closure_info = ClosureInfo {
31            ptr: closure_ptr,
32            captures: captures.clone(),
33            creation_timestamp: current_timestamp(),
34            thread_id: format!("{:?}", std::thread::current().id()),
35            call_site: capture_call_site(),
36            memory_footprint: self.calculate_closure_footprint(&captures),
37            optimization_potential: self.analyze_optimization_potential(&captures),
38        };
39
40        for capture in &captures {
41            let event = CaptureEvent {
42                closure_ptr,
43                captured_var: capture.clone(),
44                event_type: CaptureEventType::Captured,
45                timestamp: current_timestamp(),
46            };
47
48            if let Ok(mut events) = self.capture_events.lock() {
49                events.push(event);
50            }
51        }
52
53        if let Ok(mut graph) = self.lifetime_graph.lock() {
54            graph.add_closure_relationships(closure_ptr, &captures);
55        }
56
57        if let Ok(mut closures) = self.closures.lock() {
58            closures.insert(closure_ptr, closure_info);
59        }
60    }
61
62    pub fn track_closure_drop(&self, closure_ptr: usize) {
63        if let Ok(mut closures) = self.closures.lock() {
64            if let Some(closure_info) = closures.get_mut(&closure_ptr) {
65                for capture in &closure_info.captures {
66                    let event = CaptureEvent {
67                        closure_ptr,
68                        captured_var: capture.clone(),
69                        event_type: CaptureEventType::Released,
70                        timestamp: current_timestamp(),
71                    };
72
73                    if let Ok(mut events) = self.capture_events.lock() {
74                        events.push(event);
75                    }
76                }
77            }
78            closures.remove(&closure_ptr);
79        }
80
81        if let Ok(mut graph) = self.lifetime_graph.lock() {
82            graph.remove_closure(closure_ptr);
83        }
84    }
85
86    pub fn analyze_closure_patterns(
87        &self,
88        allocations: &[AllocationInfo],
89    ) -> ClosureAnalysisReport {
90        let mut detected_closures = Vec::new();
91        let mut capture_statistics = CaptureStatistics::default();
92
93        for allocation in allocations {
94            if let Some(type_name) = &allocation.type_name {
95                if self.is_closure_type(type_name) {
96                    if let Some(analysis) = self.analyze_closure_allocation(allocation) {
97                        detected_closures.push(analysis);
98                    }
99                }
100            }
101        }
102
103        if let Ok(closures) = self.closures.lock() {
104            capture_statistics = self.calculate_capture_statistics(&closures);
105        }
106
107        let optimization_suggestions = self.generate_optimization_suggestions(&detected_closures);
108        let lifetime_analysis = self.analyze_capture_lifetimes();
109
110        ClosureAnalysisReport {
111            detected_closures,
112            capture_statistics,
113            optimization_suggestions,
114            lifetime_analysis,
115            analysis_timestamp: current_timestamp(),
116        }
117    }
118
119    fn is_closure_type(&self, type_name: &str) -> bool {
120        type_name.contains("closure")
121            || type_name.contains("{{closure}}")
122            || type_name.starts_with("fn(")
123            || type_name.contains("dyn Fn")
124            || type_name.contains("impl Fn")
125    }
126
127    fn analyze_closure_allocation(&self, allocation: &AllocationInfo) -> Option<DetectedClosure> {
128        let type_name = allocation.type_name.as_ref()?;
129
130        Some(DetectedClosure {
131            ptr: allocation.ptr,
132            type_name: type_name.clone(),
133            size: allocation.size,
134            estimated_captures: self.estimate_captures_from_size(allocation.size),
135            closure_type: self.classify_closure_type(type_name),
136            creation_context: CreationContext {
137                scope_name: allocation.scope_name.clone(),
138                thread_id: format!("{:?}", allocation.thread_id),
139                timestamp: allocation.timestamp_alloc,
140            },
141            memory_impact: self.assess_memory_impact(allocation.size),
142        })
143    }
144
145    fn estimate_captures_from_size(&self, size: usize) -> usize {
146        if size <= 8 {
147            0
148        } else if size <= 32 {
149            2
150        } else if size <= 128 {
151            8
152        } else {
153            size / 16
154        }
155    }
156
157    fn classify_closure_type(&self, type_name: &str) -> ClosureType {
158        if type_name.contains("FnOnce") {
159            ClosureType::FnOnce
160        } else if type_name.contains("FnMut") {
161            ClosureType::FnMut
162        } else if type_name.contains("Fn") {
163            ClosureType::Fn
164        } else {
165            ClosureType::Unknown
166        }
167    }
168
169    fn assess_memory_impact(&self, size: usize) -> MemoryImpact {
170        match size {
171            0..=16 => MemoryImpact::Minimal,
172            17..=64 => MemoryImpact::Low,
173            65..=256 => MemoryImpact::Medium,
174            257..=1024 => MemoryImpact::High,
175            _ => MemoryImpact::VeryHigh,
176        }
177    }
178
179    fn calculate_closure_footprint(&self, captures: &[CaptureInfo]) -> ClosureFootprint {
180        let total_size = captures.iter().map(|c| c.size).sum();
181        let by_value_count = captures
182            .iter()
183            .filter(|c| c.mode == CaptureMode::ByValue)
184            .count();
185        let by_ref_count = captures
186            .iter()
187            .filter(|c| c.mode == CaptureMode::ByReference)
188            .count();
189        let by_mut_ref_count = captures
190            .iter()
191            .filter(|c| c.mode == CaptureMode::ByMutableReference)
192            .count();
193
194        ClosureFootprint {
195            total_size,
196            capture_count: captures.len(),
197            by_value_count,
198            by_ref_count,
199            by_mut_ref_count,
200            estimated_heap_usage: self.estimate_heap_usage(captures),
201        }
202    }
203
204    fn estimate_heap_usage(&self, captures: &[CaptureInfo]) -> usize {
205        captures
206            .iter()
207            .filter(|c| c.mode == CaptureMode::ByValue)
208            .filter(|c| self.is_heap_allocated_type(&c.var_type))
209            .map(|c| c.size)
210            .sum()
211    }
212
213    fn is_heap_allocated_type(&self, type_name: &str) -> bool {
214        type_name.contains("Vec")
215            || type_name.contains("String")
216            || type_name.contains("HashMap")
217            || type_name.contains("Box")
218            || type_name.contains("Arc")
219            || type_name.contains("Rc")
220    }
221
222    fn analyze_optimization_potential(&self, captures: &[CaptureInfo]) -> OptimizationPotential {
223        let mut suggestions = Vec::new();
224        let mut potential_savings = 0;
225
226        for capture in captures {
227            if capture.mode == CaptureMode::ByValue && capture.size > 64 {
228                suggestions.push(format!(
229                    "Consider capturing '{}' by reference instead of by value to save {} bytes",
230                    capture.var_name, capture.size
231                ));
232                potential_savings += capture.size;
233            }
234        }
235
236        let mut_captures = captures
237            .iter()
238            .filter(|c| c.mode == CaptureMode::ByMutableReference)
239            .count();
240        if mut_captures > captures.len() / 2 {
241            suggestions.push("Consider if all mutable captures are necessary".to_string());
242        }
243
244        let heap_captures = captures
245            .iter()
246            .filter(|c| c.mode == CaptureMode::ByValue && self.is_heap_allocated_type(&c.var_type))
247            .count();
248
249        if heap_captures > 0 {
250            suggestions
251                .push("Consider using move semantics for heap-allocated captures".to_string());
252        }
253
254        OptimizationPotential {
255            level: if potential_savings > 256 {
256                OptimizationLevel::High
257            } else if potential_savings > 64 {
258                OptimizationLevel::Medium
259            } else if !suggestions.is_empty() {
260                OptimizationLevel::Low
261            } else {
262                OptimizationLevel::None
263            },
264            potential_savings,
265            suggestions,
266        }
267    }
268
269    fn calculate_capture_statistics(
270        &self,
271        closures: &HashMap<usize, ClosureInfo>,
272    ) -> CaptureStatistics {
273        let total_closures = closures.len();
274        let total_captures = closures.values().map(|c| c.captures.len()).sum();
275
276        let mut by_mode = HashMap::new();
277        let mut by_type = HashMap::new();
278        let mut total_memory = 0;
279
280        for closure in closures.values() {
281            total_memory += closure.memory_footprint.total_size;
282
283            for capture in &closure.captures {
284                *by_mode.entry(capture.mode.clone()).or_insert(0) += 1;
285                *by_type.entry(capture.var_type.clone()).or_insert(0) += 1;
286            }
287        }
288
289        let avg_captures_per_closure = if total_closures > 0 {
290            total_captures as f64 / total_closures as f64
291        } else {
292            0.0
293        };
294
295        CaptureStatistics {
296            total_closures,
297            total_captures,
298            avg_captures_per_closure,
299            total_memory_usage: total_memory,
300            captures_by_mode: by_mode,
301            captures_by_type: by_type,
302        }
303    }
304
305    fn generate_optimization_suggestions(
306        &self,
307        closures: &[DetectedClosure],
308    ) -> Vec<OptimizationSuggestion> {
309        let mut suggestions = Vec::new();
310
311        let high_memory_closures = closures
312            .iter()
313            .filter(|c| matches!(c.memory_impact, MemoryImpact::High | MemoryImpact::VeryHigh))
314            .count();
315
316        if high_memory_closures > 0 {
317            suggestions.push(OptimizationSuggestion {
318                category: OptimizationCategory::Memory,
319                priority: SuggestionPriority::High,
320                description: format!(
321                    "Found {high_memory_closures} closures with high memory usage",
322                ),
323                recommendation: "Consider reducing capture size or using references".to_string(),
324                estimated_impact: "20-50% memory reduction".to_string(),
325            });
326        }
327
328        let fnonce_count = closures
329            .iter()
330            .filter(|c| c.closure_type == ClosureType::FnOnce)
331            .count();
332
333        if fnonce_count > closures.len() / 2 {
334            suggestions.push(OptimizationSuggestion {
335                category: OptimizationCategory::Performance,
336                priority: SuggestionPriority::Medium,
337                description: "Many FnOnce closures detected".to_string(),
338                recommendation: "Consider if Fn or FnMut traits would be more appropriate"
339                    .to_string(),
340                estimated_impact: "Improved reusability".to_string(),
341            });
342        }
343
344        suggestions
345    }
346
347    fn analyze_capture_lifetimes(&self) -> LifetimeAnalysis {
348        if let Ok(graph) = self.lifetime_graph.lock() {
349            graph.analyze_lifetimes()
350        } else {
351            LifetimeAnalysis::default()
352        }
353    }
354}
355
356impl Default for ClosureAnalyzer {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362fn current_timestamp() -> u64 {
363    std::time::SystemTime::now()
364        .duration_since(std::time::UNIX_EPOCH)
365        .unwrap_or_default()
366        .as_nanos() as u64
367}
368
369fn capture_call_site() -> String {
370    format!("{}:{}", file!(), line!())
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use std::thread;
377
378    /// Objective: Verify ClosureAnalyzer creation with default values
379    /// Invariants: New analyzer should have empty collections
380    #[test]
381    fn test_closure_analyzer_creation() {
382        let analyzer = ClosureAnalyzer::new();
383
384        let closures = analyzer.closures.lock().unwrap();
385        let events = analyzer.capture_events.lock().unwrap();
386
387        assert!(closures.is_empty(), "New analyzer should have no closures");
388        assert!(
389            events.is_empty(),
390            "New analyzer should have no capture events"
391        );
392    }
393
394    /// Objective: Verify Default trait implementation
395    /// Invariants: Default should create same as new()
396    #[test]
397    fn test_closure_analyzer_default() {
398        let analyzer = ClosureAnalyzer::default();
399
400        let closures = analyzer.closures.lock().unwrap();
401        assert!(
402            closures.is_empty(),
403            "Default analyzer should have no closures"
404        );
405    }
406
407    /// Objective: Verify register_closure functionality
408    /// Invariants: Should add closure and capture events correctly
409    #[test]
410    fn test_register_closure() {
411        let analyzer = ClosureAnalyzer::new();
412
413        let captures = vec![CaptureInfo {
414            var_name: "x".to_string(),
415            var_ptr: 0x1000,
416            mode: CaptureMode::ByValue,
417            var_type: "i32".to_string(),
418            size: 4,
419            lifetime_bound: None,
420        }];
421
422        analyzer.register_closure(0x5000, captures);
423
424        let closures = analyzer.closures.lock().unwrap();
425        assert!(
426            closures.contains_key(&0x5000),
427            "Should contain registered closure"
428        );
429
430        let events = analyzer.capture_events.lock().unwrap();
431        assert_eq!(events.len(), 1, "Should have one capture event");
432        assert_eq!(
433            events[0].event_type,
434            CaptureEventType::Captured,
435            "Event should be Captured"
436        );
437    }
438
439    /// Objective: Verify register_closure with multiple captures
440    /// Invariants: Should create capture events for each capture
441    #[test]
442    fn test_register_closure_multiple_captures() {
443        let analyzer = ClosureAnalyzer::new();
444
445        let captures = vec![
446            CaptureInfo {
447                var_name: "a".to_string(),
448                var_ptr: 0x1000,
449                mode: CaptureMode::ByValue,
450                var_type: "i32".to_string(),
451                size: 4,
452                lifetime_bound: None,
453            },
454            CaptureInfo {
455                var_name: "b".to_string(),
456                var_ptr: 0x2000,
457                mode: CaptureMode::ByReference,
458                var_type: "String".to_string(),
459                size: 24,
460                lifetime_bound: None,
461            },
462            CaptureInfo {
463                var_name: "c".to_string(),
464                var_ptr: 0x3000,
465                mode: CaptureMode::ByMutableReference,
466                var_type: "Vec<u8>".to_string(),
467                size: 24,
468                lifetime_bound: None,
469            },
470        ];
471
472        analyzer.register_closure(0x6000, captures);
473
474        let events = analyzer.capture_events.lock().unwrap();
475        assert_eq!(events.len(), 3, "Should have three capture events");
476    }
477
478    /// Objective: Verify track_closure_drop functionality
479    /// Invariants: Should remove closure and create release events
480    #[test]
481    fn test_track_closure_drop() {
482        let analyzer = ClosureAnalyzer::new();
483
484        let captures = vec![CaptureInfo {
485            var_name: "x".to_string(),
486            var_ptr: 0x1000,
487            mode: CaptureMode::ByValue,
488            var_type: "i32".to_string(),
489            size: 4,
490            lifetime_bound: None,
491        }];
492
493        analyzer.register_closure(0x5000, captures);
494        analyzer.track_closure_drop(0x5000);
495
496        let closures = analyzer.closures.lock().unwrap();
497        assert!(
498            !closures.contains_key(&0x5000),
499            "Should not contain dropped closure"
500        );
501
502        let events = analyzer.capture_events.lock().unwrap();
503        assert_eq!(events.len(), 2, "Should have captured and released events");
504        assert_eq!(
505            events[1].event_type,
506            CaptureEventType::Released,
507            "Second event should be Released"
508        );
509    }
510
511    /// Objective: Verify track_closure_drop for non-existent closure
512    /// Invariants: Should handle gracefully without error
513    #[test]
514    fn test_track_closure_drop_nonexistent() {
515        let analyzer = ClosureAnalyzer::new();
516
517        analyzer.track_closure_drop(0xdead);
518
519        let closures = analyzer.closures.lock().unwrap();
520        assert!(closures.is_empty(), "Should still be empty");
521    }
522
523    /// Objective: Verify is_closure_type detection
524    /// Invariants: Should correctly identify closure types
525    #[test]
526    fn test_is_closure_type() {
527        let analyzer = ClosureAnalyzer::new();
528
529        assert!(
530            analyzer.is_closure_type("closure"),
531            "Should detect 'closure'"
532        );
533        assert!(
534            analyzer.is_closure_type("{{closure}}"),
535            "Should detect '{{closure}}'"
536        );
537        assert!(
538            analyzer.is_closure_type("fn() -> i32"),
539            "Should detect 'fn('"
540        );
541        assert!(
542            analyzer.is_closure_type("dyn Fn()"),
543            "Should detect 'dyn Fn'"
544        );
545        assert!(
546            analyzer.is_closure_type("impl FnMut()"),
547            "Should detect 'impl Fn'"
548        );
549        assert!(
550            !analyzer.is_closure_type("String"),
551            "Should not detect 'String'"
552        );
553        assert!(
554            !analyzer.is_closure_type("Vec<u8>"),
555            "Should not detect 'Vec<u8>'"
556        );
557    }
558
559    /// Objective: Verify estimate_captures_from_size
560    /// Invariants: Should estimate based on closure size
561    #[test]
562    fn test_estimate_captures_from_size() {
563        let analyzer = ClosureAnalyzer::new();
564
565        assert_eq!(
566            analyzer.estimate_captures_from_size(0),
567            0,
568            "Size 0 should estimate 0 captures"
569        );
570        assert_eq!(
571            analyzer.estimate_captures_from_size(8),
572            0,
573            "Size <= 8 should estimate 0 captures"
574        );
575        assert_eq!(
576            analyzer.estimate_captures_from_size(16),
577            2,
578            "Size 9-32 should estimate 2 captures"
579        );
580        assert_eq!(
581            analyzer.estimate_captures_from_size(32),
582            2,
583            "Size 32 should estimate 2 captures"
584        );
585        assert_eq!(
586            analyzer.estimate_captures_from_size(64),
587            8,
588            "Size 33-128 should estimate 8 captures"
589        );
590        assert_eq!(
591            analyzer.estimate_captures_from_size(128),
592            8,
593            "Size 128 should estimate 8 captures"
594        );
595        assert_eq!(
596            analyzer.estimate_captures_from_size(256),
597            16,
598            "Size > 128 should estimate size/16 captures"
599        );
600    }
601
602    /// Objective: Verify classify_closure_type
603    /// Invariants: Should correctly classify Fn/FnMut/FnOnce
604    #[test]
605    fn test_classify_closure_type() {
606        let analyzer = ClosureAnalyzer::new();
607
608        assert_eq!(
609            analyzer.classify_closure_type("FnOnce"),
610            ClosureType::FnOnce,
611            "Should classify FnOnce"
612        );
613        assert_eq!(
614            analyzer.classify_closure_type("impl FnOnce"),
615            ClosureType::FnOnce,
616            "Should classify impl FnOnce"
617        );
618        assert_eq!(
619            analyzer.classify_closure_type("FnMut"),
620            ClosureType::FnMut,
621            "Should classify FnMut"
622        );
623        assert_eq!(
624            analyzer.classify_closure_type("dyn FnMut"),
625            ClosureType::FnMut,
626            "Should classify dyn FnMut"
627        );
628        assert_eq!(
629            analyzer.classify_closure_type("Fn"),
630            ClosureType::Fn,
631            "Should classify Fn"
632        );
633        assert_eq!(
634            analyzer.classify_closure_type("dyn Fn"),
635            ClosureType::Fn,
636            "Should classify dyn Fn"
637        );
638        assert_eq!(
639            analyzer.classify_closure_type("SomeType"),
640            ClosureType::Unknown,
641            "Should classify unknown"
642        );
643    }
644
645    /// Objective: Verify assess_memory_impact
646    /// Invariants: Should correctly assess impact levels
647    #[test]
648    fn test_assess_memory_impact() {
649        let analyzer = ClosureAnalyzer::new();
650
651        assert_eq!(
652            analyzer.assess_memory_impact(0),
653            MemoryImpact::Minimal,
654            "Size 0 should be Minimal"
655        );
656        assert_eq!(
657            analyzer.assess_memory_impact(16),
658            MemoryImpact::Minimal,
659            "Size 16 should be Minimal"
660        );
661        assert_eq!(
662            analyzer.assess_memory_impact(32),
663            MemoryImpact::Low,
664            "Size 32 should be Low"
665        );
666        assert_eq!(
667            analyzer.assess_memory_impact(64),
668            MemoryImpact::Low,
669            "Size 64 should be Low"
670        );
671        assert_eq!(
672            analyzer.assess_memory_impact(128),
673            MemoryImpact::Medium,
674            "Size 128 should be Medium"
675        );
676        assert_eq!(
677            analyzer.assess_memory_impact(256),
678            MemoryImpact::Medium,
679            "Size 256 should be Medium"
680        );
681        assert_eq!(
682            analyzer.assess_memory_impact(512),
683            MemoryImpact::High,
684            "Size 512 should be High"
685        );
686        assert_eq!(
687            analyzer.assess_memory_impact(1024),
688            MemoryImpact::High,
689            "Size 1024 should be High"
690        );
691        assert_eq!(
692            analyzer.assess_memory_impact(2048),
693            MemoryImpact::VeryHigh,
694            "Size 2048 should be VeryHigh"
695        );
696    }
697
698    /// Objective: Verify calculate_closure_footprint
699    /// Invariants: Should correctly calculate footprint metrics
700    #[test]
701    fn test_calculate_closure_footprint() {
702        let analyzer = ClosureAnalyzer::new();
703
704        let captures = vec![
705            CaptureInfo {
706                var_name: "a".to_string(),
707                var_ptr: 0x1000,
708                mode: CaptureMode::ByValue,
709                var_type: "i32".to_string(),
710                size: 4,
711                lifetime_bound: None,
712            },
713            CaptureInfo {
714                var_name: "b".to_string(),
715                var_ptr: 0x2000,
716                mode: CaptureMode::ByReference,
717                var_type: "String".to_string(),
718                size: 8,
719                lifetime_bound: None,
720            },
721            CaptureInfo {
722                var_name: "c".to_string(),
723                var_ptr: 0x3000,
724                mode: CaptureMode::ByMutableReference,
725                var_type: "Vec<u8>".to_string(),
726                size: 8,
727                lifetime_bound: None,
728            },
729        ];
730
731        let footprint = analyzer.calculate_closure_footprint(&captures);
732
733        assert_eq!(footprint.total_size, 20, "Total size should be 20");
734        assert_eq!(footprint.capture_count, 3, "Capture count should be 3");
735        assert_eq!(footprint.by_value_count, 1, "By value count should be 1");
736        assert_eq!(footprint.by_ref_count, 1, "By ref count should be 1");
737        assert_eq!(
738            footprint.by_mut_ref_count, 1,
739            "By mut ref count should be 1"
740        );
741    }
742
743    /// Objective: Verify is_heap_allocated_type
744    /// Invariants: Should correctly identify heap types
745    #[test]
746    fn test_is_heap_allocated_type() {
747        let analyzer = ClosureAnalyzer::new();
748
749        assert!(
750            analyzer.is_heap_allocated_type("Vec<u8>"),
751            "Vec should be heap type"
752        );
753        assert!(
754            analyzer.is_heap_allocated_type("String"),
755            "String should be heap type"
756        );
757        assert!(
758            analyzer.is_heap_allocated_type("HashMap<K, V>"),
759            "HashMap should be heap type"
760        );
761        assert!(
762            analyzer.is_heap_allocated_type("Box<T>"),
763            "Box should be heap type"
764        );
765        assert!(
766            analyzer.is_heap_allocated_type("Arc<T>"),
767            "Arc should be heap type"
768        );
769        assert!(
770            analyzer.is_heap_allocated_type("Rc<T>"),
771            "Rc should be heap type"
772        );
773        assert!(
774            !analyzer.is_heap_allocated_type("i32"),
775            "i32 should not be heap type"
776        );
777        assert!(
778            !analyzer.is_heap_allocated_type("&str"),
779            "&str should not be heap type"
780        );
781    }
782
783    /// Objective: Verify estimate_heap_usage
784    /// Invariants: Should sum sizes of heap-allocated by-value captures
785    #[test]
786    fn test_estimate_heap_usage() {
787        let analyzer = ClosureAnalyzer::new();
788
789        let captures = vec![
790            CaptureInfo {
791                var_name: "vec".to_string(),
792                var_ptr: 0x1000,
793                mode: CaptureMode::ByValue,
794                var_type: "Vec<u8>".to_string(),
795                size: 24,
796                lifetime_bound: None,
797            },
798            CaptureInfo {
799                var_name: "s".to_string(),
800                var_ptr: 0x2000,
801                mode: CaptureMode::ByValue,
802                var_type: "String".to_string(),
803                size: 24,
804                lifetime_bound: None,
805            },
806            CaptureInfo {
807                var_name: "num".to_string(),
808                var_ptr: 0x3000,
809                mode: CaptureMode::ByValue,
810                var_type: "i32".to_string(),
811                size: 4,
812                lifetime_bound: None,
813            },
814            CaptureInfo {
815                var_name: "ref_vec".to_string(),
816                var_ptr: 0x4000,
817                mode: CaptureMode::ByReference,
818                var_type: "Vec<u8>".to_string(),
819                size: 8,
820                lifetime_bound: None,
821            },
822        ];
823
824        let heap_usage = analyzer.estimate_heap_usage(&captures);
825
826        assert_eq!(heap_usage, 48, "Should sum Vec and String sizes (24+24)");
827    }
828
829    /// Objective: Verify analyze_optimization_potential with large by-value captures
830    /// Invariants: Should suggest reference capture for large values
831    #[test]
832    fn test_analyze_optimization_potential_large_value() {
833        let analyzer = ClosureAnalyzer::new();
834
835        let captures = vec![CaptureInfo {
836            var_name: "large_data".to_string(),
837            var_ptr: 0x1000,
838            mode: CaptureMode::ByValue,
839            var_type: "[u8; 128]".to_string(),
840            size: 128,
841            lifetime_bound: None,
842        }];
843
844        let potential = analyzer.analyze_optimization_potential(&captures);
845
846        assert_eq!(
847            potential.level,
848            OptimizationLevel::Medium,
849            "Large capture should have Medium optimization level"
850        );
851        assert_eq!(
852            potential.potential_savings, 128,
853            "Potential savings should be 128"
854        );
855        assert!(
856            potential
857                .suggestions
858                .iter()
859                .any(|s| s.contains("reference")),
860            "Should suggest reference capture"
861        );
862    }
863
864    /// Objective: Verify analyze_optimization_potential with high savings
865    /// Invariants: Should have High optimization level for >256 bytes
866    #[test]
867    fn test_analyze_optimization_potential_high_savings() {
868        let analyzer = ClosureAnalyzer::new();
869
870        let captures = vec![
871            CaptureInfo {
872                var_name: "data1".to_string(),
873                var_ptr: 0x1000,
874                mode: CaptureMode::ByValue,
875                var_type: "[u8; 128]".to_string(),
876                size: 128,
877                lifetime_bound: None,
878            },
879            CaptureInfo {
880                var_name: "data2".to_string(),
881                var_ptr: 0x2000,
882                mode: CaptureMode::ByValue,
883                var_type: "[u8; 128]".to_string(),
884                size: 128,
885                lifetime_bound: None,
886            },
887            CaptureInfo {
888                var_name: "data3".to_string(),
889                var_ptr: 0x3000,
890                mode: CaptureMode::ByValue,
891                var_type: "[u8; 128]".to_string(),
892                size: 128,
893                lifetime_bound: None,
894            },
895        ];
896
897        let potential = analyzer.analyze_optimization_potential(&captures);
898
899        assert_eq!(
900            potential.level,
901            OptimizationLevel::High,
902            "Savings > 256 should have High optimization level"
903        );
904        assert_eq!(
905            potential.potential_savings, 384,
906            "Potential savings should be 384 (128 * 3)"
907        );
908    }
909
910    /// Objective: Verify analyze_optimization_potential with many mutable captures
911    /// Invariants: Should suggest reviewing mutable captures
912    #[test]
913    fn test_analyze_optimization_potential_many_mutable() {
914        let analyzer = ClosureAnalyzer::new();
915
916        let captures = vec![
917            CaptureInfo {
918                var_name: "a".to_string(),
919                var_ptr: 0x1000,
920                mode: CaptureMode::ByMutableReference,
921                var_type: "i32".to_string(),
922                size: 8,
923                lifetime_bound: None,
924            },
925            CaptureInfo {
926                var_name: "b".to_string(),
927                var_ptr: 0x2000,
928                mode: CaptureMode::ByMutableReference,
929                var_type: "i32".to_string(),
930                size: 8,
931                lifetime_bound: None,
932            },
933            CaptureInfo {
934                var_name: "c".to_string(),
935                var_ptr: 0x3000,
936                mode: CaptureMode::ByReference,
937                var_type: "i32".to_string(),
938                size: 8,
939                lifetime_bound: None,
940            },
941        ];
942
943        let potential = analyzer.analyze_optimization_potential(&captures);
944
945        assert!(
946            potential.suggestions.iter().any(|s| s.contains("mutable")),
947            "Should suggest reviewing mutable captures"
948        );
949    }
950
951    /// Objective: Verify analyze_optimization_potential with heap captures
952    /// Invariants: Should suggest move semantics
953    #[test]
954    fn test_analyze_optimization_potential_heap_captures() {
955        let analyzer = ClosureAnalyzer::new();
956
957        let captures = vec![CaptureInfo {
958            var_name: "vec".to_string(),
959            var_ptr: 0x1000,
960            mode: CaptureMode::ByValue,
961            var_type: "Vec<u8>".to_string(),
962            size: 24,
963            lifetime_bound: None,
964        }];
965
966        let potential = analyzer.analyze_optimization_potential(&captures);
967
968        assert!(
969            potential
970                .suggestions
971                .iter()
972                .any(|s| s.contains("move") || s.contains("heap")),
973            "Should suggest move semantics for heap captures"
974        );
975    }
976
977    /// Objective: Verify analyze_optimization_potential with no issues
978    /// Invariants: Should have None optimization level
979    #[test]
980    fn test_analyze_optimization_potential_none() {
981        let analyzer = ClosureAnalyzer::new();
982
983        let captures = vec![CaptureInfo {
984            var_name: "small".to_string(),
985            var_ptr: 0x1000,
986            mode: CaptureMode::ByReference,
987            var_type: "i32".to_string(),
988            size: 8,
989            lifetime_bound: None,
990        }];
991
992        let potential = analyzer.analyze_optimization_potential(&captures);
993
994        assert_eq!(
995            potential.level,
996            OptimizationLevel::None,
997            "Small reference capture should have None optimization level"
998        );
999        assert_eq!(
1000            potential.potential_savings, 0,
1001            "Potential savings should be 0"
1002        );
1003    }
1004
1005    /// Objective: Verify calculate_capture_statistics
1006    /// Invariants: Should correctly aggregate statistics
1007    #[test]
1008    fn test_calculate_capture_statistics() {
1009        let analyzer = ClosureAnalyzer::new();
1010
1011        let mut closures = HashMap::new();
1012
1013        closures.insert(
1014            0x1000,
1015            ClosureInfo {
1016                ptr: 0x1000,
1017                captures: vec![
1018                    CaptureInfo {
1019                        var_name: "a".to_string(),
1020                        var_ptr: 0x1000,
1021                        mode: CaptureMode::ByValue,
1022                        var_type: "i32".to_string(),
1023                        size: 4,
1024                        lifetime_bound: None,
1025                    },
1026                    CaptureInfo {
1027                        var_name: "b".to_string(),
1028                        var_ptr: 0x2000,
1029                        mode: CaptureMode::ByReference,
1030                        var_type: "String".to_string(),
1031                        size: 8,
1032                        lifetime_bound: None,
1033                    },
1034                ],
1035                creation_timestamp: 1000,
1036                thread_id: "main".to_string(),
1037                call_site: "test.rs:1".to_string(),
1038                memory_footprint: ClosureFootprint {
1039                    total_size: 12,
1040                    capture_count: 2,
1041                    by_value_count: 1,
1042                    by_ref_count: 1,
1043                    by_mut_ref_count: 0,
1044                    estimated_heap_usage: 0,
1045                },
1046                optimization_potential: OptimizationPotential {
1047                    level: OptimizationLevel::None,
1048                    potential_savings: 0,
1049                    suggestions: vec![],
1050                },
1051            },
1052        );
1053
1054        let stats = analyzer.calculate_capture_statistics(&closures);
1055
1056        assert_eq!(stats.total_closures, 1, "Should have 1 closure");
1057        assert_eq!(stats.total_captures, 2, "Should have 2 captures");
1058        assert_eq!(stats.avg_captures_per_closure, 2.0, "Average should be 2.0");
1059        assert_eq!(stats.total_memory_usage, 12, "Total memory should be 12");
1060    }
1061
1062    /// Objective: Verify calculate_capture_statistics with empty closures
1063    /// Invariants: Should return zero statistics
1064    #[test]
1065    fn test_calculate_capture_statistics_empty() {
1066        let analyzer = ClosureAnalyzer::new();
1067
1068        let closures = HashMap::new();
1069        let stats = analyzer.calculate_capture_statistics(&closures);
1070
1071        assert_eq!(stats.total_closures, 0, "Should have 0 closures");
1072        assert_eq!(stats.total_captures, 0, "Should have 0 captures");
1073        assert_eq!(stats.avg_captures_per_closure, 0.0, "Average should be 0.0");
1074    }
1075
1076    /// Objective: Verify generate_optimization_suggestions with high memory closures
1077    /// Invariants: Should suggest memory optimization
1078    #[test]
1079    fn test_generate_optimization_suggestions_high_memory() {
1080        let analyzer = ClosureAnalyzer::new();
1081
1082        let closures = vec![DetectedClosure {
1083            ptr: 0x1000,
1084            type_name: "closure".to_string(),
1085            size: 1024,
1086            estimated_captures: 10,
1087            closure_type: ClosureType::Fn,
1088            creation_context: CreationContext {
1089                scope_name: None,
1090                thread_id: "main".to_string(),
1091                timestamp: 1000,
1092            },
1093            memory_impact: MemoryImpact::High,
1094        }];
1095
1096        let suggestions = analyzer.generate_optimization_suggestions(&closures);
1097
1098        assert!(
1099            suggestions
1100                .iter()
1101                .any(|s| s.category == OptimizationCategory::Memory),
1102            "Should have memory suggestion"
1103        );
1104        assert!(
1105            suggestions
1106                .iter()
1107                .any(|s| s.priority == SuggestionPriority::High),
1108            "Should have high priority suggestion"
1109        );
1110    }
1111
1112    /// Objective: Verify generate_optimization_suggestions with many FnOnce closures
1113    /// Invariants: Should suggest Fn/FnMut alternatives
1114    #[test]
1115    fn test_generate_optimization_suggestions_many_fnonce() {
1116        let analyzer = ClosureAnalyzer::new();
1117
1118        let closures = vec![
1119            DetectedClosure {
1120                ptr: 0x1000,
1121                type_name: "FnOnce".to_string(),
1122                size: 64,
1123                estimated_captures: 2,
1124                closure_type: ClosureType::FnOnce,
1125                creation_context: CreationContext {
1126                    scope_name: None,
1127                    thread_id: "main".to_string(),
1128                    timestamp: 1000,
1129                },
1130                memory_impact: MemoryImpact::Low,
1131            },
1132            DetectedClosure {
1133                ptr: 0x2000,
1134                type_name: "FnOnce".to_string(),
1135                size: 64,
1136                estimated_captures: 2,
1137                closure_type: ClosureType::FnOnce,
1138                creation_context: CreationContext {
1139                    scope_name: None,
1140                    thread_id: "main".to_string(),
1141                    timestamp: 1000,
1142                },
1143                memory_impact: MemoryImpact::Low,
1144            },
1145            DetectedClosure {
1146                ptr: 0x3000,
1147                type_name: "Fn".to_string(),
1148                size: 64,
1149                estimated_captures: 2,
1150                closure_type: ClosureType::Fn,
1151                creation_context: CreationContext {
1152                    scope_name: None,
1153                    thread_id: "main".to_string(),
1154                    timestamp: 1000,
1155                },
1156                memory_impact: MemoryImpact::Low,
1157            },
1158        ];
1159
1160        let suggestions = analyzer.generate_optimization_suggestions(&closures);
1161
1162        assert!(
1163            suggestions.iter().any(|s| s.description.contains("FnOnce")),
1164            "Should mention FnOnce closures"
1165        );
1166    }
1167
1168    /// Objective: Verify analyze_capture_lifetimes
1169    /// Invariants: Should return lifetime analysis
1170    #[test]
1171    fn test_analyze_capture_lifetimes() {
1172        let analyzer = ClosureAnalyzer::new();
1173
1174        let captures = vec![
1175            CaptureInfo {
1176                var_name: "a".to_string(),
1177                var_ptr: 0x1000,
1178                mode: CaptureMode::ByValue,
1179                var_type: "i32".to_string(),
1180                size: 4,
1181                lifetime_bound: None,
1182            },
1183            CaptureInfo {
1184                var_name: "b".to_string(),
1185                var_ptr: 0x2000,
1186                mode: CaptureMode::ByReference,
1187                var_type: "String".to_string(),
1188                size: 8,
1189                lifetime_bound: None,
1190            },
1191        ];
1192
1193        analyzer.register_closure(0x5000, captures);
1194        let analysis = analyzer.analyze_capture_lifetimes();
1195
1196        assert_eq!(
1197            analysis.total_relationships, 1,
1198            "Should have 1 relationship"
1199        );
1200    }
1201
1202    /// Objective: Verify get_global_closure_analyzer singleton
1203    /// Invariants: Should return same instance
1204    #[test]
1205    fn test_global_closure_analyzer_singleton() {
1206        let analyzer1 = get_global_closure_analyzer();
1207        let analyzer2 = get_global_closure_analyzer();
1208
1209        assert!(
1210            Arc::ptr_eq(&analyzer1, &analyzer2),
1211            "Should return same instance"
1212        );
1213    }
1214
1215    /// Objective: Verify current_timestamp returns valid value
1216    /// Invariants: Timestamp should be positive
1217    #[test]
1218    fn test_current_timestamp() {
1219        let ts = current_timestamp();
1220        assert!(ts > 0, "Timestamp should be positive");
1221    }
1222
1223    /// Objective: Verify capture_call_site returns valid string
1224    /// Invariants: Should contain file and line info
1225    #[test]
1226    fn test_capture_call_site() {
1227        let site = capture_call_site();
1228        assert!(!site.is_empty(), "Call site should not be empty");
1229        assert!(
1230            site.contains(".rs"),
1231            "Call site should contain .rs extension"
1232        );
1233    }
1234
1235    /// Objective: Verify analyze_closure_patterns with allocations
1236    /// Invariants: Should detect closures in allocations
1237    #[test]
1238    fn test_analyze_closure_patterns_with_allocations() {
1239        let analyzer = ClosureAnalyzer::new();
1240
1241        let allocations = vec![AllocationInfo {
1242            ptr: 0x1000,
1243            size: 64,
1244            var_name: Some("closure_var".to_string()),
1245            type_name: Some("closure".to_string()),
1246            scope_name: Some("test_scope".to_string()),
1247            timestamp_alloc: 1000,
1248            timestamp_dealloc: None,
1249            thread_id: thread::current().id(),
1250            thread_id_u64: 1,
1251            borrow_count: 0,
1252            stack_trace: None,
1253            is_leaked: false,
1254            lifetime_ms: None,
1255            borrow_info: None,
1256            clone_info: None,
1257            ownership_history_available: false,
1258            smart_pointer_info: None,
1259            memory_layout: None,
1260            generic_info: None,
1261            dynamic_type_info: None,
1262            runtime_state: None,
1263            stack_allocation: None,
1264            temporary_object: None,
1265            fragmentation_analysis: None,
1266            generic_instantiation: None,
1267            type_relationships: None,
1268            type_usage: None,
1269            function_call_tracking: None,
1270            lifecycle_tracking: None,
1271            access_tracking: None,
1272            drop_chain_analysis: None,
1273        }];
1274
1275        let report = analyzer.analyze_closure_patterns(&allocations);
1276
1277        assert_eq!(report.detected_closures.len(), 1, "Should detect 1 closure");
1278        assert!(report.analysis_timestamp > 0, "Should have timestamp");
1279    }
1280
1281    /// Objective: Verify analyze_closure_patterns with non-closure allocations
1282    /// Invariants: Should not detect non-closure types
1283    #[test]
1284    fn test_analyze_closure_patterns_non_closure() {
1285        let analyzer = ClosureAnalyzer::new();
1286
1287        let allocations = vec![AllocationInfo {
1288            ptr: 0x1000,
1289            size: 64,
1290            var_name: Some("string_var".to_string()),
1291            type_name: Some("String".to_string()),
1292            scope_name: Some("test_scope".to_string()),
1293            timestamp_alloc: 1000,
1294            timestamp_dealloc: None,
1295            thread_id: thread::current().id(),
1296            thread_id_u64: 1,
1297            borrow_count: 0,
1298            stack_trace: None,
1299            is_leaked: false,
1300            lifetime_ms: None,
1301            borrow_info: None,
1302            clone_info: None,
1303            ownership_history_available: false,
1304            smart_pointer_info: None,
1305            memory_layout: None,
1306            generic_info: None,
1307            dynamic_type_info: None,
1308            runtime_state: None,
1309            stack_allocation: None,
1310            temporary_object: None,
1311            fragmentation_analysis: None,
1312            generic_instantiation: None,
1313            type_relationships: None,
1314            type_usage: None,
1315            function_call_tracking: None,
1316            lifecycle_tracking: None,
1317            access_tracking: None,
1318            drop_chain_analysis: None,
1319        }];
1320
1321        let report = analyzer.analyze_closure_patterns(&allocations);
1322
1323        assert_eq!(
1324            report.detected_closures.len(),
1325            0,
1326            "Should not detect non-closure"
1327        );
1328    }
1329
1330    /// Objective: Verify analyze_closure_allocation
1331    /// Invariants: Should correctly analyze closure allocation
1332    #[test]
1333    fn test_analyze_closure_allocation() {
1334        let analyzer = ClosureAnalyzer::new();
1335
1336        let allocation = AllocationInfo {
1337            ptr: 0x1000,
1338            size: 128,
1339            var_name: Some("my_closure".to_string()),
1340            type_name: Some("dyn FnMut()".to_string()),
1341            scope_name: Some("test_scope".to_string()),
1342            timestamp_alloc: 1000,
1343            timestamp_dealloc: None,
1344            thread_id: thread::current().id(),
1345            thread_id_u64: 1,
1346            borrow_count: 0,
1347            stack_trace: None,
1348            is_leaked: false,
1349            lifetime_ms: None,
1350            borrow_info: None,
1351            clone_info: None,
1352            ownership_history_available: false,
1353            smart_pointer_info: None,
1354            memory_layout: None,
1355            generic_info: None,
1356            dynamic_type_info: None,
1357            runtime_state: None,
1358            stack_allocation: None,
1359            temporary_object: None,
1360            fragmentation_analysis: None,
1361            generic_instantiation: None,
1362            type_relationships: None,
1363            type_usage: None,
1364            function_call_tracking: None,
1365            lifecycle_tracking: None,
1366            access_tracking: None,
1367            drop_chain_analysis: None,
1368        };
1369
1370        let detected = analyzer.analyze_closure_allocation(&allocation);
1371
1372        assert!(detected.is_some(), "Should detect closure");
1373        let closure = detected.unwrap();
1374        assert_eq!(closure.ptr, 0x1000, "Pointer should match");
1375        assert_eq!(
1376            closure.closure_type,
1377            ClosureType::FnMut,
1378            "Should classify as FnMut"
1379        );
1380        assert_eq!(
1381            closure.memory_impact,
1382            MemoryImpact::Medium,
1383            "Size 128 should be Medium impact"
1384        );
1385    }
1386
1387    /// Objective: Verify concurrent access to ClosureAnalyzer
1388    /// Invariants: Should handle concurrent operations safely
1389    #[test]
1390    fn test_concurrent_access() {
1391        let analyzer = Arc::new(ClosureAnalyzer::new());
1392        let mut handles = vec![];
1393
1394        for i in 0..5 {
1395            let analyzer_clone = analyzer.clone();
1396            let handle = thread::spawn(move || {
1397                let captures = vec![CaptureInfo {
1398                    var_name: format!("var{}", i),
1399                    var_ptr: 0x1000 + i,
1400                    mode: CaptureMode::ByValue,
1401                    var_type: "i32".to_string(),
1402                    size: 4,
1403                    lifetime_bound: None,
1404                }];
1405                analyzer_clone.register_closure(0x5000 + i, captures);
1406            });
1407            handles.push(handle);
1408        }
1409
1410        for handle in handles {
1411            handle.join().unwrap();
1412        }
1413
1414        let closures = analyzer.closures.lock().unwrap();
1415        assert_eq!(closures.len(), 5, "Should have 5 closures from 5 threads");
1416    }
1417}