memscope_rs/analysis/
safety_analyzer.rs

1//! Enhanced Safety Analysis for Unsafe Code and FFI Operations
2//!
3//! This module implements comprehensive safety analysis including:
4//! - UnsafeReport generation with risk assessment
5//! - Risk factor detection and classification
6//! - Confidence scoring for safety violations
7//! - Memory passport tracking for FFI boundaries
8
9use crate::analysis::unsafe_ffi_tracker::{RiskLevel, SafetyViolation, StackFrame};
10use crate::core::types::{AllocationInfo, TrackingResult};
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13use std::sync::{Arc, Mutex};
14use std::time::{SystemTime, UNIX_EPOCH};
15
16/// Enhanced risk factor types for comprehensive safety analysis
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
18pub enum RiskFactorType {
19    /// Raw pointer dereference without bounds checking
20    RawPointerDereference,
21    /// Potential unsafe data race condition
22    UnsafeDataRace,
23    /// Invalid transmute operation
24    InvalidTransmute,
25    /// FFI function call with potential risks
26    FfiCall,
27    /// Manual memory management risks
28    ManualMemoryManagement,
29    /// Cross-boundary memory transfer
30    CrossBoundaryTransfer,
31    /// Use after free potential
32    UseAfterFree,
33    /// Buffer overflow potential
34    BufferOverflow,
35    /// Lifetime violation
36    LifetimeViolation,
37}
38
39/// Individual risk factor with detailed assessment
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RiskFactor {
42    /// Type of risk factor
43    pub factor_type: RiskFactorType,
44    /// Severity score (0.0 to 10.0)
45    pub severity: f64,
46    /// Confidence in detection (0.0 to 1.0)
47    pub confidence: f64,
48    /// Human-readable description
49    pub description: String,
50    /// Source location where risk was detected
51    pub source_location: Option<String>,
52    /// Call stack context
53    pub call_stack: Vec<StackFrame>,
54    /// Suggested mitigation
55    pub mitigation: String,
56}
57
58/// Comprehensive risk assessment for unsafe operations
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct RiskAssessment {
61    /// Overall risk level
62    pub risk_level: RiskLevel,
63    /// Numerical risk score (0.0 to 100.0)
64    pub risk_score: f64,
65    /// Individual risk factors identified
66    pub risk_factors: Vec<RiskFactor>,
67    /// Overall confidence score (0.0 to 1.0)
68    pub confidence_score: f64,
69    /// Suggested mitigation strategies
70    pub mitigation_suggestions: Vec<String>,
71    /// Assessment timestamp
72    pub assessment_timestamp: u64,
73}
74
75/// Comprehensive unsafe operation report
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct UnsafeReport {
78    /// Unique report identifier
79    pub report_id: String,
80    /// Source of the unsafe operation
81    pub source: UnsafeSource,
82    /// Comprehensive risk assessment
83    pub risk_assessment: RiskAssessment,
84    /// Dynamic violations detected during runtime
85    pub dynamic_violations: Vec<DynamicViolation>,
86    /// Related memory passports
87    pub related_passports: Vec<String>,
88    /// Memory context at time of analysis
89    pub memory_context: MemoryContext,
90    /// Report generation timestamp
91    pub generated_at: u64,
92}
93
94/// Source information for unsafe operations
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub enum UnsafeSource {
97    /// Unsafe block in Rust code
98    UnsafeBlock {
99        /// Location in source code
100        location: String,
101        /// Function containing the unsafe block
102        function: String,
103        /// File path
104        file_path: Option<String>,
105        /// Line number
106        line_number: Option<u32>,
107    },
108    /// FFI function call
109    FfiFunction {
110        /// Library name
111        library: String,
112        /// Function name
113        function: String,
114        /// Call site location
115        call_site: String,
116    },
117    /// Raw pointer operation
118    RawPointer {
119        /// Operation type (deref, cast, etc.)
120        operation: String,
121        /// Location of operation
122        location: String,
123    },
124    /// Transmute operation
125    Transmute {
126        /// Source type
127        from_type: String,
128        /// Target type
129        to_type: String,
130        /// Location of transmute
131        location: String,
132    },
133}
134
135/// Dynamic violation detected during runtime
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct DynamicViolation {
138    /// Type of violation
139    pub violation_type: ViolationType,
140    /// Memory address involved
141    pub memory_address: usize,
142    /// Size of memory involved
143    pub memory_size: usize,
144    /// Timestamp when violation was detected
145    pub detected_at: u64,
146    /// Call stack at violation time
147    pub call_stack: Vec<StackFrame>,
148    /// Severity of the violation
149    pub severity: RiskLevel,
150    /// Additional context information
151    pub context: String,
152}
153
154/// Types of dynamic violations
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub enum ViolationType {
157    /// Double free violation
158    DoubleFree,
159    /// Use after free violation
160    UseAfterFree,
161    /// Buffer overflow
162    BufferOverflow,
163    /// Invalid memory access
164    InvalidAccess,
165    /// Data race condition
166    DataRace,
167    /// FFI boundary violation
168    FfiBoundaryViolation,
169}
170
171/// Memory context at time of analysis
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct MemoryContext {
174    /// Total allocated memory
175    pub total_allocated: usize,
176    /// Number of active allocations
177    pub active_allocations: usize,
178    /// Memory pressure level
179    pub memory_pressure: MemoryPressureLevel,
180    /// Recent allocation patterns
181    pub allocation_patterns: Vec<AllocationPattern>,
182}
183
184/// Memory pressure levels
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub enum MemoryPressureLevel {
187    Low,
188    Medium,
189    High,
190    Critical,
191}
192
193/// Allocation pattern information
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct AllocationPattern {
196    /// Pattern type
197    pub pattern_type: String,
198    /// Frequency of occurrence
199    pub frequency: u32,
200    /// Average size
201    pub average_size: usize,
202    /// Risk level associated with pattern
203    pub risk_level: RiskLevel,
204}
205
206/// Memory passport for tracking cross-FFI boundary memory
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct MemoryPassport {
209    /// Unique passport identifier
210    pub passport_id: String,
211    /// Memory allocation pointer
212    pub allocation_ptr: usize,
213    /// Size in bytes
214    pub size_bytes: usize,
215    /// Current status at program shutdown
216    pub status_at_shutdown: PassportStatus,
217    /// Lifecycle events recorded
218    pub lifecycle_events: Vec<PassportEvent>,
219    /// Risk assessment for this memory
220    pub risk_assessment: RiskAssessment,
221    /// Creation timestamp
222    pub created_at: u64,
223    /// Last update timestamp
224    pub updated_at: u64,
225}
226
227/// Status of memory passport at program shutdown
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub enum PassportStatus {
230    /// Memory properly freed by Rust
231    FreedByRust,
232    /// Memory handed over to FFI and not returned
233    HandoverToFfi,
234    /// Memory freed by foreign code
235    FreedByForeign,
236    /// Memory reclaimed by Rust from FFI
237    ReclaimedByRust,
238    /// Memory still in foreign custody (potential leak)
239    InForeignCustody,
240    /// Status unknown or corrupted
241    Unknown,
242}
243
244/// Lifecycle event in memory passport
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PassportEvent {
247    /// Event type
248    pub event_type: PassportEventType,
249    /// Timestamp of event
250    pub timestamp: u64,
251    /// Context where event occurred
252    pub context: String,
253    /// Call stack at event time
254    pub call_stack: Vec<StackFrame>,
255    /// Additional metadata
256    pub metadata: HashMap<String, String>,
257}
258
259/// Types of passport events
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub enum PassportEventType {
262    /// Memory allocated in Rust
263    AllocatedInRust,
264    /// Memory handed over to FFI
265    HandoverToFfi,
266    /// Memory freed by foreign code
267    FreedByForeign,
268    /// Memory reclaimed by Rust
269    ReclaimedByRust,
270    /// Memory accessed across boundary
271    BoundaryAccess,
272    /// Memory ownership transferred
273    OwnershipTransfer,
274}
275
276/// Safety analyzer for comprehensive unsafe code analysis
277pub struct SafetyAnalyzer {
278    /// Generated unsafe reports
279    unsafe_reports: Arc<Mutex<HashMap<String, UnsafeReport>>>,
280    /// Memory passport registry
281    memory_passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
282    /// Risk assessment engine
283    risk_engine: RiskAssessmentEngine,
284    /// Configuration for analysis
285    config: SafetyAnalysisConfig,
286    /// Statistics tracking
287    stats: Arc<Mutex<SafetyAnalysisStats>>,
288}
289
290/// Configuration for safety analysis
291#[derive(Debug, Clone)]
292pub struct SafetyAnalysisConfig {
293    /// Enable detailed risk assessment
294    pub detailed_risk_assessment: bool,
295    /// Enable memory passport tracking
296    pub enable_passport_tracking: bool,
297    /// Minimum risk level to report
298    pub min_risk_level: RiskLevel,
299    /// Maximum number of reports to keep
300    pub max_reports: usize,
301    /// Enable dynamic violation detection
302    pub enable_dynamic_violations: bool,
303}
304
305impl Default for SafetyAnalysisConfig {
306    fn default() -> Self {
307        Self {
308            detailed_risk_assessment: true,
309            enable_passport_tracking: true,
310            min_risk_level: RiskLevel::Low,
311            max_reports: 1000,
312            enable_dynamic_violations: true,
313        }
314    }
315}
316
317/// Statistics for safety analysis
318#[derive(Debug, Clone, Default, Serialize, Deserialize)]
319pub struct SafetyAnalysisStats {
320    /// Total reports generated
321    pub total_reports: usize,
322    /// Reports by risk level
323    pub reports_by_risk_level: HashMap<String, usize>,
324    /// Total passports created
325    pub total_passports: usize,
326    /// Passports by status
327    pub passports_by_status: HashMap<String, usize>,
328    /// Dynamic violations detected
329    pub dynamic_violations: usize,
330    /// Analysis start time
331    pub analysis_start_time: u64,
332}
333
334/// Risk assessment engine for evaluating unsafe operations
335pub struct RiskAssessmentEngine {
336    /// Risk factor weights
337    _risk_weights: HashMap<RiskFactorType, f64>,
338    /// Historical risk data
339    _historical_data: HashMap<String, Vec<f64>>,
340}
341
342impl RiskAssessmentEngine {
343    /// Create new risk assessment engine
344    pub fn new() -> Self {
345        let mut risk_weights = HashMap::new();
346        risk_weights.insert(RiskFactorType::RawPointerDereference, 8.5);
347        risk_weights.insert(RiskFactorType::UnsafeDataRace, 9.0);
348        risk_weights.insert(RiskFactorType::InvalidTransmute, 7.5);
349        risk_weights.insert(RiskFactorType::FfiCall, 6.0);
350        risk_weights.insert(RiskFactorType::ManualMemoryManagement, 7.0);
351        risk_weights.insert(RiskFactorType::CrossBoundaryTransfer, 6.5);
352        risk_weights.insert(RiskFactorType::UseAfterFree, 9.5);
353        risk_weights.insert(RiskFactorType::BufferOverflow, 9.0);
354        risk_weights.insert(RiskFactorType::LifetimeViolation, 8.0);
355
356        Self {
357            _risk_weights: risk_weights,
358            _historical_data: HashMap::new(),
359        }
360    }
361
362    /// Assess risk for unsafe operation
363    pub fn assess_risk(
364        &self,
365        source: &UnsafeSource,
366        context: &MemoryContext,
367        call_stack: &[StackFrame],
368    ) -> RiskAssessment {
369        let mut risk_factors = Vec::new();
370        let mut total_risk_score = 0.0;
371        let mut total_confidence = 0.0;
372
373        // Analyze based on source type
374        match source {
375            UnsafeSource::UnsafeBlock { location, .. } => {
376                risk_factors.extend(self.analyze_unsafe_block(location, call_stack));
377            }
378            UnsafeSource::FfiFunction {
379                library, function, ..
380            } => {
381                risk_factors.extend(self.analyze_ffi_function(library, function, call_stack));
382            }
383            UnsafeSource::RawPointer { operation, .. } => {
384                risk_factors.extend(self.analyze_raw_pointer(operation, call_stack));
385            }
386            UnsafeSource::Transmute {
387                from_type, to_type, ..
388            } => {
389                risk_factors.extend(self.analyze_transmute(from_type, to_type, call_stack));
390            }
391        }
392
393        // Calculate overall scores
394        for factor in &risk_factors {
395            total_risk_score += factor.severity * factor.confidence;
396            total_confidence += factor.confidence;
397        }
398
399        let risk_count = risk_factors.len() as f64;
400        let average_confidence = if risk_count > 0.0 {
401            total_confidence / risk_count
402        } else {
403            0.0
404        };
405
406        // Adjust for memory pressure
407        let pressure_multiplier = match context.memory_pressure {
408            MemoryPressureLevel::Critical => 1.5,
409            MemoryPressureLevel::High => 1.2,
410            MemoryPressureLevel::Medium => 1.0,
411            MemoryPressureLevel::Low => 0.8,
412        };
413
414        total_risk_score *= pressure_multiplier;
415
416        // Determine risk level
417        let risk_level = if total_risk_score >= 80.0 {
418            RiskLevel::Critical
419        } else if total_risk_score >= 60.0 {
420            RiskLevel::High
421        } else if total_risk_score >= 40.0 {
422            RiskLevel::Medium
423        } else {
424            RiskLevel::Low
425        };
426
427        // Generate mitigation suggestions
428        let mitigation_suggestions =
429            self.generate_mitigation_suggestions(&risk_factors, &risk_level);
430
431        RiskAssessment {
432            risk_level,
433            risk_score: total_risk_score.min(100.0),
434            risk_factors,
435            confidence_score: average_confidence,
436            mitigation_suggestions,
437            assessment_timestamp: SystemTime::now()
438                .duration_since(UNIX_EPOCH)
439                .unwrap_or_default()
440                .as_secs(),
441        }
442    }
443
444    /// Analyze unsafe block for risk factors
445    fn analyze_unsafe_block(&self, location: &str, call_stack: &[StackFrame]) -> Vec<RiskFactor> {
446        let mut factors = Vec::new();
447
448        // Check for raw pointer operations
449        if location.contains("*") || location.contains("ptr::") {
450            factors.push(RiskFactor {
451                factor_type: RiskFactorType::RawPointerDereference,
452                severity: 7.5,
453                confidence: 0.8,
454                description: "Raw pointer dereference in unsafe block".to_string(),
455                source_location: Some(location.to_string()),
456                call_stack: call_stack.to_vec(),
457                mitigation: "Add bounds checking and null pointer validation".to_string(),
458            });
459        }
460
461        // Check for manual memory management
462        if location.contains("alloc") || location.contains("dealloc") || location.contains("free") {
463            factors.push(RiskFactor {
464                factor_type: RiskFactorType::ManualMemoryManagement,
465                severity: 6.5,
466                confidence: 0.9,
467                description: "Manual memory management in unsafe block".to_string(),
468                source_location: Some(location.to_string()),
469                call_stack: call_stack.to_vec(),
470                mitigation: "Use RAII patterns and smart pointers where possible".to_string(),
471            });
472        }
473
474        factors
475    }
476
477    /// Analyze FFI function call for risk factors
478    fn analyze_ffi_function(
479        &self,
480        library: &str,
481        function: &str,
482        call_stack: &[StackFrame],
483    ) -> Vec<RiskFactor> {
484        let mut factors = Vec::new();
485
486        // Base FFI risk
487        factors.push(RiskFactor {
488            factor_type: RiskFactorType::FfiCall,
489            severity: 5.5,
490            confidence: 0.7,
491            description: format!("FFI call to {library}::{function}"),
492            source_location: Some(format!("{library}::{function}")),
493            call_stack: call_stack.to_vec(),
494            mitigation: "Validate all parameters and return values".to_string(),
495        });
496
497        // Check for known risky functions
498        let risky_functions = ["malloc", "free", "strcpy", "strcat", "sprintf", "gets"];
499        if risky_functions.iter().any(|&f| function.contains(f)) {
500            factors.push(RiskFactor {
501                factor_type: RiskFactorType::BufferOverflow,
502                severity: 8.0,
503                confidence: 0.9,
504                description: format!("Call to potentially unsafe function: {function}"),
505                source_location: Some(format!("{library}::{function}")),
506                call_stack: call_stack.to_vec(),
507                mitigation: "Use safer alternatives or add explicit bounds checking".to_string(),
508            });
509        }
510
511        factors
512    }
513
514    /// Analyze raw pointer operation for risk factors
515    fn analyze_raw_pointer(&self, operation: &str, call_stack: &[StackFrame]) -> Vec<RiskFactor> {
516        let mut factors = Vec::new();
517
518        factors.push(RiskFactor {
519            factor_type: RiskFactorType::RawPointerDereference,
520            severity: 8.0,
521            confidence: 0.85,
522            description: format!("Raw pointer operation: {operation}"),
523            source_location: Some(operation.to_string()),
524            call_stack: call_stack.to_vec(),
525            mitigation: "Add null checks and bounds validation".to_string(),
526        });
527
528        factors
529    }
530
531    /// Analyze transmute operation for risk factors
532    fn analyze_transmute(
533        &self,
534        from_type: &str,
535        to_type: &str,
536        call_stack: &[StackFrame],
537    ) -> Vec<RiskFactor> {
538        let mut factors = Vec::new();
539
540        let severity = if from_type.contains("*") || to_type.contains("*") {
541            9.0 // Pointer transmutes are very risky
542        } else {
543            7.0 // Regular transmutes are moderately risky
544        };
545
546        factors.push(RiskFactor {
547            factor_type: RiskFactorType::InvalidTransmute,
548            severity,
549            confidence: 0.8,
550            description: format!("Transmute from {from_type} to {to_type}"),
551            source_location: Some(format!("{from_type} -> {to_type}")),
552            call_stack: call_stack.to_vec(),
553            mitigation: "Verify size and alignment compatibility".to_string(),
554        });
555
556        factors
557    }
558
559    /// Generate mitigation suggestions based on risk factors
560    fn generate_mitigation_suggestions(
561        &self,
562        risk_factors: &[RiskFactor],
563        risk_level: &RiskLevel,
564    ) -> Vec<String> {
565        let mut suggestions = Vec::new();
566
567        // Add general suggestions based on risk level
568        match risk_level {
569            RiskLevel::Critical => {
570                suggestions.push(
571                    "URGENT: Critical safety issues detected - immediate review required"
572                        .to_string(),
573                );
574                suggestions.push(
575                    "Consider refactoring to eliminate unsafe code where possible".to_string(),
576                );
577            }
578            RiskLevel::High => {
579                suggestions.push(
580                    "High-risk operations detected - thorough testing recommended".to_string(),
581                );
582                suggestions.push("Add comprehensive error handling and validation".to_string());
583            }
584            RiskLevel::Medium => {
585                suggestions
586                    .push("Moderate risks detected - review and add safety checks".to_string());
587            }
588            RiskLevel::Low => {
589                suggestions.push("Low-level risks detected - monitor for issues".to_string());
590            }
591        }
592
593        // Add specific suggestions based on risk factors
594        let mut factor_types: HashSet<RiskFactorType> = HashSet::new();
595        for factor in risk_factors {
596            factor_types.insert(factor.factor_type.clone());
597        }
598
599        for factor_type in factor_types {
600            match factor_type {
601                RiskFactorType::RawPointerDereference => {
602                    suggestions.push("Add null pointer checks before dereferencing".to_string());
603                    suggestions.push("Validate pointer bounds and alignment".to_string());
604                }
605                RiskFactorType::UnsafeDataRace => {
606                    suggestions.push("Use proper synchronization primitives".to_string());
607                    suggestions.push("Consider using atomic operations".to_string());
608                }
609                RiskFactorType::FfiCall => {
610                    suggestions.push("Validate all FFI parameters and return values".to_string());
611                    suggestions.push("Handle FFI errors gracefully".to_string());
612                }
613                RiskFactorType::ManualMemoryManagement => {
614                    suggestions.push("Use RAII patterns to ensure cleanup".to_string());
615                    suggestions.push("Consider using smart pointers".to_string());
616                }
617                _ => {}
618            }
619        }
620
621        suggestions
622    }
623}
624
625impl Default for RiskAssessmentEngine {
626    fn default() -> Self {
627        Self::new()
628    }
629}
630
631impl SafetyAnalyzer {
632    /// Create new safety analyzer
633    pub fn new(config: SafetyAnalysisConfig) -> Self {
634        tracing::info!("🔒 Initializing Safety Analyzer");
635        tracing::info!(
636            "   • Detailed risk assessment: {}",
637            config.detailed_risk_assessment
638        );
639        tracing::info!(
640            "   • Passport tracking: {}",
641            config.enable_passport_tracking
642        );
643        tracing::info!("   • Min risk level: {:?}", config.min_risk_level);
644
645        Self {
646            unsafe_reports: Arc::new(Mutex::new(HashMap::new())),
647            memory_passports: Arc::new(Mutex::new(HashMap::new())),
648            risk_engine: RiskAssessmentEngine::new(),
649            config,
650            stats: Arc::new(Mutex::new(SafetyAnalysisStats {
651                analysis_start_time: SystemTime::now()
652                    .duration_since(UNIX_EPOCH)
653                    .unwrap_or_default()
654                    .as_secs(),
655                ..Default::default()
656            })),
657        }
658    }
659
660    /// Generate unsafe report for detected unsafe operation
661    pub fn generate_unsafe_report(
662        &self,
663        source: UnsafeSource,
664        allocations: &[AllocationInfo],
665        violations: &[SafetyViolation],
666    ) -> TrackingResult<String> {
667        let report_id = self.generate_report_id(&source);
668
669        tracing::info!("🔍 Generating unsafe report: {}", report_id);
670
671        // Create memory context
672        let memory_context = self.create_memory_context(allocations);
673
674        // Capture call stack
675        let call_stack = self.capture_call_stack()?;
676
677        // Perform risk assessment
678        let risk_assessment = if self.config.detailed_risk_assessment {
679            self.risk_engine
680                .assess_risk(&source, &memory_context, &call_stack)
681        } else {
682            self.create_basic_risk_assessment(&source)
683        };
684
685        // Skip report if below minimum risk level
686        if !self.should_generate_report(&risk_assessment.risk_level) {
687            return Ok(report_id);
688        }
689
690        // Convert safety violations to dynamic violations
691        let dynamic_violations = self.convert_safety_violations(violations);
692
693        // Find related passports
694        let related_passports = if self.config.enable_passport_tracking {
695            self.find_related_passports(&source, allocations)
696        } else {
697            Vec::new()
698        };
699
700        // Create comprehensive report
701        let report = UnsafeReport {
702            report_id: report_id.clone(),
703            source,
704            risk_assessment: risk_assessment.clone(),
705            dynamic_violations,
706            related_passports,
707            memory_context,
708            generated_at: SystemTime::now()
709                .duration_since(UNIX_EPOCH)
710                .unwrap_or_default()
711                .as_secs(),
712        };
713
714        // Store report
715        if let Ok(mut reports) = self.unsafe_reports.lock() {
716            // Maintain maximum report limit
717            if reports.len() >= self.config.max_reports {
718                // Remove oldest report
719                if let Some(oldest_id) = reports.keys().next().cloned() {
720                    reports.remove(&oldest_id);
721                }
722            }
723            reports.insert(report_id.clone(), report);
724        }
725
726        // Update statistics
727        self.update_stats(&report_id, &risk_assessment.risk_level);
728
729        tracing::info!(
730            "✅ Generated unsafe report: {} (risk: {:?})",
731            report_id,
732            risk_assessment.risk_level
733        );
734
735        Ok(report_id)
736    }
737
738    /// Create or update memory passport for FFI boundary tracking
739    pub fn create_memory_passport(
740        &self,
741        allocation_ptr: usize,
742        size_bytes: usize,
743        initial_event: PassportEventType,
744    ) -> TrackingResult<String> {
745        if !self.config.enable_passport_tracking {
746            return Ok(String::new());
747        }
748
749        let passport_id = format!(
750            "passport_{:x}_{}",
751            allocation_ptr,
752            SystemTime::now()
753                .duration_since(UNIX_EPOCH)
754                .unwrap_or_default()
755                .as_nanos()
756        );
757
758        let call_stack = self.capture_call_stack()?;
759        let current_time = SystemTime::now()
760            .duration_since(UNIX_EPOCH)
761            .unwrap_or_default()
762            .as_secs();
763
764        // Create initial event
765        let initial_passport_event = PassportEvent {
766            event_type: initial_event,
767            timestamp: current_time,
768            context: "SafetyAnalyzer".to_string(),
769            call_stack,
770            metadata: HashMap::new(),
771        };
772
773        // Create risk assessment for this memory
774        let memory_context = MemoryContext {
775            total_allocated: size_bytes,
776            active_allocations: 1,
777            memory_pressure: MemoryPressureLevel::Low,
778            allocation_patterns: Vec::new(),
779        };
780
781        let source = UnsafeSource::RawPointer {
782            operation: "passport_creation".to_string(),
783            location: format!("0x{allocation_ptr:x}"),
784        };
785
786        let risk_assessment = self.risk_engine.assess_risk(&source, &memory_context, &[]);
787
788        let passport = MemoryPassport {
789            passport_id: passport_id.clone(),
790            allocation_ptr,
791            size_bytes,
792            status_at_shutdown: PassportStatus::Unknown,
793            lifecycle_events: vec![initial_passport_event],
794            risk_assessment,
795            created_at: current_time,
796            updated_at: current_time,
797        };
798
799        if let Ok(mut passports) = self.memory_passports.lock() {
800            passports.insert(allocation_ptr, passport);
801        }
802
803        // Update statistics
804        if let Ok(mut stats) = self.stats.lock() {
805            stats.total_passports += 1;
806        }
807
808        tracing::info!(
809            "📋 Created memory passport: {} for 0x{:x}",
810            passport_id,
811            allocation_ptr
812        );
813
814        Ok(passport_id)
815    }
816
817    /// Record passport event for memory lifecycle tracking
818    pub fn record_passport_event(
819        &self,
820        allocation_ptr: usize,
821        event_type: PassportEventType,
822        context: String,
823    ) -> TrackingResult<()> {
824        if !self.config.enable_passport_tracking {
825            return Ok(());
826        }
827
828        let call_stack = self.capture_call_stack()?;
829        let current_time = SystemTime::now()
830            .duration_since(UNIX_EPOCH)
831            .unwrap_or_default()
832            .as_secs();
833
834        let event = PassportEvent {
835            event_type,
836            timestamp: current_time,
837            context,
838            call_stack,
839            metadata: HashMap::new(),
840        };
841
842        if let Ok(mut passports) = self.memory_passports.lock() {
843            if let Some(passport) = passports.get_mut(&allocation_ptr) {
844                passport.lifecycle_events.push(event);
845                passport.updated_at = current_time;
846
847                tracing::info!("📝 Recorded passport event for 0x{:x}", allocation_ptr);
848            }
849        }
850
851        Ok(())
852    }
853
854    /// Finalize passports at program shutdown and detect leaks
855    pub fn finalize_passports_at_shutdown(&self) -> Vec<String> {
856        let mut leaked_passports = Vec::new();
857
858        if let Ok(mut passports) = self.memory_passports.lock() {
859            for (ptr, passport) in passports.iter_mut() {
860                // Determine final status based on lifecycle events
861                let final_status = self.determine_final_passport_status(&passport.lifecycle_events);
862                passport.status_at_shutdown = final_status.clone();
863
864                // Check for leaks
865                if matches!(final_status, PassportStatus::InForeignCustody) {
866                    leaked_passports.push(passport.passport_id.clone());
867                    tracing::warn!(
868                        "🚨 Memory leak detected: passport {} (0x{:x}) in foreign custody",
869                        passport.passport_id,
870                        ptr
871                    );
872                }
873            }
874
875            // Update statistics
876            if let Ok(mut stats) = self.stats.lock() {
877                for passport in passports.values() {
878                    let status_key = format!("{:?}", passport.status_at_shutdown);
879                    *stats.passports_by_status.entry(status_key).or_insert(0) += 1;
880                }
881            }
882        }
883
884        tracing::info!(
885            "🏁 Finalized {} passports, {} leaks detected",
886            self.get_passport_count(),
887            leaked_passports.len()
888        );
889
890        leaked_passports
891    }
892
893    /// Get all unsafe reports
894    pub fn get_unsafe_reports(&self) -> HashMap<String, UnsafeReport> {
895        self.unsafe_reports
896            .lock()
897            .unwrap_or_else(|_| {
898                tracing::error!("Failed to lock unsafe reports");
899                std::process::exit(1);
900            })
901            .clone()
902    }
903
904    /// Get all memory passports
905    pub fn get_memory_passports(&self) -> HashMap<usize, MemoryPassport> {
906        self.memory_passports
907            .lock()
908            .unwrap_or_else(|_| {
909                tracing::error!("Failed to lock memory passports");
910                std::process::exit(1);
911            })
912            .clone()
913    }
914
915    /// Get analysis statistics
916    pub fn get_stats(&self) -> SafetyAnalysisStats {
917        self.stats
918            .lock()
919            .unwrap_or_else(|_| {
920                tracing::error!("Failed to lock stats");
921                std::process::exit(1);
922            })
923            .clone()
924    }
925
926    // Private helper methods
927
928    fn generate_report_id(&self, source: &UnsafeSource) -> String {
929        let timestamp = SystemTime::now()
930            .duration_since(UNIX_EPOCH)
931            .unwrap_or_default()
932            .as_nanos();
933
934        let source_type = match source {
935            UnsafeSource::UnsafeBlock { .. } => "UB",
936            UnsafeSource::FfiFunction { .. } => "FFI",
937            UnsafeSource::RawPointer { .. } => "PTR",
938            UnsafeSource::Transmute { .. } => "TX",
939        };
940
941        format!("UNSAFE-{}-{}", source_type, timestamp % 1000000)
942    }
943
944    fn create_memory_context(&self, allocations: &[AllocationInfo]) -> MemoryContext {
945        let total_allocated = allocations.iter().map(|a| a.size).sum();
946        let active_allocations = allocations
947            .iter()
948            .filter(|a| a.timestamp_dealloc.is_none())
949            .count();
950
951        let memory_pressure = if total_allocated > 1024 * 1024 * 1024 {
952            MemoryPressureLevel::Critical
953        } else if total_allocated > 512 * 1024 * 1024 {
954            MemoryPressureLevel::High
955        } else if total_allocated > 256 * 1024 * 1024 {
956            MemoryPressureLevel::Medium
957        } else {
958            MemoryPressureLevel::Low
959        };
960
961        MemoryContext {
962            total_allocated,
963            active_allocations,
964            memory_pressure,
965            allocation_patterns: Vec::new(), // Could be enhanced with pattern analysis
966        }
967    }
968
969    fn capture_call_stack(&self) -> TrackingResult<Vec<StackFrame>> {
970        // Simplified call stack capture
971        // In a real implementation, this would use backtrace or similar
972        Ok(vec![StackFrame {
973            function_name: "safety_analyzer".to_string(),
974            file_name: Some("src/analysis/safety_analyzer.rs".to_string()),
975            line_number: Some(1),
976            is_unsafe: false,
977        }])
978    }
979
980    fn create_basic_risk_assessment(&self, source: &UnsafeSource) -> RiskAssessment {
981        let (risk_level, risk_score) = match source {
982            UnsafeSource::UnsafeBlock { .. } => (RiskLevel::Medium, 50.0),
983            UnsafeSource::FfiFunction { .. } => (RiskLevel::Medium, 45.0),
984            UnsafeSource::RawPointer { .. } => (RiskLevel::High, 70.0),
985            UnsafeSource::Transmute { .. } => (RiskLevel::High, 65.0),
986        };
987
988        RiskAssessment {
989            risk_level,
990            risk_score,
991            risk_factors: Vec::new(),
992            confidence_score: 0.5,
993            mitigation_suggestions: vec!["Review unsafe operation for safety".to_string()],
994            assessment_timestamp: SystemTime::now()
995                .duration_since(UNIX_EPOCH)
996                .unwrap_or_default()
997                .as_secs(),
998        }
999    }
1000
1001    fn should_generate_report(&self, risk_level: &RiskLevel) -> bool {
1002        match (&self.config.min_risk_level, risk_level) {
1003            (RiskLevel::Low, _) => true,
1004            (RiskLevel::Medium, RiskLevel::Low) => false,
1005            (RiskLevel::Medium, _) => true,
1006            (RiskLevel::High, RiskLevel::Low | RiskLevel::Medium) => false,
1007            (RiskLevel::High, _) => true,
1008            (RiskLevel::Critical, RiskLevel::Critical) => true,
1009            (RiskLevel::Critical, _) => false,
1010        }
1011    }
1012
1013    fn convert_safety_violations(&self, violations: &[SafetyViolation]) -> Vec<DynamicViolation> {
1014        violations
1015            .iter()
1016            .map(|v| {
1017                match v {
1018                    SafetyViolation::DoubleFree { timestamp, .. } => DynamicViolation {
1019                        violation_type: ViolationType::DoubleFree,
1020                        memory_address: 0, // Would need to extract from violation
1021                        memory_size: 0,
1022                        detected_at: (*timestamp as u64),
1023                        call_stack: Vec::new(),
1024                        severity: RiskLevel::Critical,
1025                        context: "Double free detected".to_string(),
1026                    },
1027                    SafetyViolation::InvalidFree {
1028                        attempted_pointer,
1029                        timestamp,
1030                        ..
1031                    } => DynamicViolation {
1032                        violation_type: ViolationType::InvalidAccess,
1033                        memory_address: *attempted_pointer,
1034                        memory_size: 0,
1035                        detected_at: (*timestamp as u64),
1036                        call_stack: Vec::new(),
1037                        severity: RiskLevel::High,
1038                        context: "Invalid free attempted".to_string(),
1039                    },
1040                    SafetyViolation::PotentialLeak {
1041                        leak_detection_timestamp,
1042                        ..
1043                    } => DynamicViolation {
1044                        violation_type: ViolationType::InvalidAccess,
1045                        memory_address: 0,
1046                        memory_size: 0,
1047                        detected_at: (*leak_detection_timestamp as u64),
1048                        call_stack: Vec::new(),
1049                        severity: RiskLevel::Medium,
1050                        context: "Potential memory leak".to_string(),
1051                    },
1052                    SafetyViolation::CrossBoundaryRisk { .. } => DynamicViolation {
1053                        violation_type: ViolationType::FfiBoundaryViolation,
1054                        memory_address: 0,
1055                        memory_size: 0,
1056                        detected_at: SystemTime::now()
1057                            .duration_since(UNIX_EPOCH)
1058                            .unwrap_or_default()
1059                            .as_secs(),
1060                        call_stack: Vec::new(),
1061                        severity: RiskLevel::Medium,
1062                        context: "Cross-boundary risk detected".to_string(),
1063                    },
1064                }
1065            })
1066            .collect()
1067    }
1068
1069    fn find_related_passports(
1070        &self,
1071        _source: &UnsafeSource,
1072        _allocations: &[AllocationInfo],
1073    ) -> Vec<String> {
1074        // Simplified implementation - could be enhanced with actual correlation logic
1075        Vec::new()
1076    }
1077
1078    fn update_stats(&self, _report_id: &str, risk_level: &RiskLevel) {
1079        if let Ok(mut stats) = self.stats.lock() {
1080            stats.total_reports += 1;
1081            let risk_key = format!("{risk_level:?}");
1082            *stats.reports_by_risk_level.entry(risk_key).or_insert(0) += 1;
1083        }
1084    }
1085
1086    fn determine_final_passport_status(&self, events: &[PassportEvent]) -> PassportStatus {
1087        // Analyze lifecycle events to determine final status
1088        let mut has_handover = false;
1089        let mut has_reclaim = false;
1090        let mut has_foreign_free = false;
1091
1092        for event in events {
1093            match event.event_type {
1094                PassportEventType::HandoverToFfi => has_handover = true,
1095                PassportEventType::ReclaimedByRust => has_reclaim = true,
1096                PassportEventType::FreedByForeign => has_foreign_free = true,
1097                _ => {}
1098            }
1099        }
1100
1101        if has_handover && !has_reclaim && !has_foreign_free {
1102            PassportStatus::InForeignCustody
1103        } else if has_foreign_free {
1104            PassportStatus::FreedByForeign
1105        } else if has_reclaim {
1106            PassportStatus::ReclaimedByRust
1107        } else if has_handover {
1108            PassportStatus::HandoverToFfi
1109        } else {
1110            PassportStatus::FreedByRust
1111        }
1112    }
1113
1114    fn get_passport_count(&self) -> usize {
1115        self.memory_passports.lock().map(|p| p.len()).unwrap_or(0)
1116    }
1117}
1118
1119impl Default for SafetyAnalyzer {
1120    fn default() -> Self {
1121        Self::new(SafetyAnalysisConfig::default())
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use crate::core::types::AllocationInfo;
1129    use crate::core::CallStackRef;
1130
1131    #[test]
1132    fn test_risk_factor_type_equality() {
1133        assert_eq!(
1134            RiskFactorType::RawPointerDereference,
1135            RiskFactorType::RawPointerDereference
1136        );
1137        assert_ne!(
1138            RiskFactorType::RawPointerDereference,
1139            RiskFactorType::UnsafeDataRace
1140        );
1141    }
1142
1143    #[test]
1144    fn test_risk_factor_creation() {
1145        let factor = RiskFactor {
1146            factor_type: RiskFactorType::BufferOverflow,
1147            severity: 8.5,
1148            confidence: 0.9,
1149            description: "Test buffer overflow".to_string(),
1150            source_location: Some("test.rs:42".to_string()),
1151            call_stack: vec![],
1152            mitigation: "Add bounds checking".to_string(),
1153        };
1154
1155        assert_eq!(factor.factor_type, RiskFactorType::BufferOverflow);
1156        assert_eq!(factor.severity, 8.5);
1157        assert_eq!(factor.confidence, 0.9);
1158        assert_eq!(factor.description, "Test buffer overflow");
1159    }
1160
1161    #[test]
1162    fn test_risk_assessment_creation() {
1163        let assessment = RiskAssessment {
1164            risk_level: RiskLevel::High,
1165            risk_score: 75.0,
1166            risk_factors: vec![],
1167            confidence_score: 0.8,
1168            mitigation_suggestions: vec!["Test mitigation".to_string()],
1169            assessment_timestamp: 1234567890,
1170        };
1171
1172        matches!(assessment.risk_level, RiskLevel::High);
1173        assert_eq!(assessment.risk_score, 75.0);
1174        assert_eq!(assessment.confidence_score, 0.8);
1175        assert_eq!(assessment.mitigation_suggestions.len(), 1);
1176    }
1177
1178    #[test]
1179    fn test_unsafe_source_variants() {
1180        let unsafe_block = UnsafeSource::UnsafeBlock {
1181            location: "test.rs:10".to_string(),
1182            function: "test_function".to_string(),
1183            file_path: Some("test.rs".to_string()),
1184            line_number: Some(10),
1185        };
1186
1187        let ffi_function = UnsafeSource::FfiFunction {
1188            library: "libc".to_string(),
1189            function: "malloc".to_string(),
1190            call_site: "test.rs:20".to_string(),
1191        };
1192
1193        let raw_pointer = UnsafeSource::RawPointer {
1194            operation: "dereference".to_string(),
1195            location: "test.rs:30".to_string(),
1196        };
1197
1198        let transmute = UnsafeSource::Transmute {
1199            from_type: "u32".to_string(),
1200            to_type: "f32".to_string(),
1201            location: "test.rs:40".to_string(),
1202        };
1203
1204        // Test that all variants can be created
1205        match unsafe_block {
1206            UnsafeSource::UnsafeBlock { location, .. } => assert_eq!(location, "test.rs:10"),
1207            _ => panic!("Wrong variant"),
1208        }
1209
1210        match ffi_function {
1211            UnsafeSource::FfiFunction { library, .. } => assert_eq!(library, "libc"),
1212            _ => panic!("Wrong variant"),
1213        }
1214
1215        match raw_pointer {
1216            UnsafeSource::RawPointer { operation, .. } => assert_eq!(operation, "dereference"),
1217            _ => panic!("Wrong variant"),
1218        }
1219
1220        match transmute {
1221            UnsafeSource::Transmute { from_type, .. } => assert_eq!(from_type, "u32"),
1222            _ => panic!("Wrong variant"),
1223        }
1224    }
1225
1226    #[test]
1227    fn test_dynamic_violation_creation() {
1228        let violation = DynamicViolation {
1229            violation_type: ViolationType::UseAfterFree,
1230            memory_address: 0x1000,
1231            memory_size: 256,
1232            detected_at: 1234567890,
1233            call_stack: vec![],
1234            severity: RiskLevel::Critical,
1235            context: "Test violation".to_string(),
1236        };
1237
1238        assert_eq!(violation.memory_address, 0x1000);
1239        assert_eq!(violation.memory_size, 256);
1240        matches!(violation.severity, RiskLevel::Critical);
1241    }
1242
1243    #[test]
1244    fn test_memory_context_creation() {
1245        let context = MemoryContext {
1246            total_allocated: 1024,
1247            active_allocations: 5,
1248            memory_pressure: MemoryPressureLevel::Medium,
1249            allocation_patterns: vec![],
1250        };
1251
1252        assert_eq!(context.total_allocated, 1024);
1253        assert_eq!(context.active_allocations, 5);
1254        matches!(context.memory_pressure, MemoryPressureLevel::Medium);
1255    }
1256
1257    #[test]
1258    fn test_allocation_pattern_creation() {
1259        let pattern = AllocationPattern {
1260            pattern_type: "frequent_small".to_string(),
1261            frequency: 100,
1262            average_size: 64,
1263            risk_level: RiskLevel::Low,
1264        };
1265
1266        assert_eq!(pattern.pattern_type, "frequent_small");
1267        assert_eq!(pattern.frequency, 100);
1268        assert_eq!(pattern.average_size, 64);
1269    }
1270
1271    #[test]
1272    fn test_memory_passport_creation() {
1273        let passport = MemoryPassport {
1274            passport_id: "test_passport".to_string(),
1275            allocation_ptr: 0x2000,
1276            size_bytes: 512,
1277            status_at_shutdown: PassportStatus::Unknown,
1278            lifecycle_events: vec![],
1279            risk_assessment: RiskAssessment {
1280                risk_level: RiskLevel::Low,
1281                risk_score: 20.0,
1282                risk_factors: vec![],
1283                confidence_score: 0.5,
1284                mitigation_suggestions: vec![],
1285                assessment_timestamp: 1234567890,
1286            },
1287            created_at: 1234567890,
1288            updated_at: 1234567890,
1289        };
1290
1291        assert_eq!(passport.passport_id, "test_passport");
1292        assert_eq!(passport.allocation_ptr, 0x2000);
1293        assert_eq!(passport.size_bytes, 512);
1294    }
1295
1296    #[test]
1297    fn test_passport_event_creation() {
1298        let event = PassportEvent {
1299            event_type: PassportEventType::AllocatedInRust,
1300            timestamp: 1234567890,
1301            context: "test_context".to_string(),
1302            call_stack: vec![],
1303            metadata: HashMap::new(),
1304        };
1305
1306        assert_eq!(event.timestamp, 1234567890);
1307        assert_eq!(event.context, "test_context");
1308        matches!(event.event_type, PassportEventType::AllocatedInRust);
1309    }
1310
1311    #[test]
1312    fn test_safety_analysis_config_default() {
1313        let config = SafetyAnalysisConfig::default();
1314
1315        assert!(config.detailed_risk_assessment);
1316        assert!(config.enable_passport_tracking);
1317        matches!(config.min_risk_level, RiskLevel::Low);
1318        assert_eq!(config.max_reports, 1000);
1319        assert!(config.enable_dynamic_violations);
1320    }
1321
1322    #[test]
1323    fn test_safety_analysis_config_custom() {
1324        let config = SafetyAnalysisConfig {
1325            detailed_risk_assessment: false,
1326            enable_passport_tracking: false,
1327            min_risk_level: RiskLevel::High,
1328            max_reports: 500,
1329            enable_dynamic_violations: false,
1330        };
1331
1332        assert!(!config.detailed_risk_assessment);
1333        assert!(!config.enable_passport_tracking);
1334        matches!(config.min_risk_level, RiskLevel::High);
1335        assert_eq!(config.max_reports, 500);
1336        assert!(!config.enable_dynamic_violations);
1337    }
1338
1339    #[test]
1340    fn test_safety_analysis_stats_default() {
1341        let stats = SafetyAnalysisStats::default();
1342
1343        assert_eq!(stats.total_reports, 0);
1344        assert!(stats.reports_by_risk_level.is_empty());
1345        assert_eq!(stats.total_passports, 0);
1346        assert!(stats.passports_by_status.is_empty());
1347        assert_eq!(stats.dynamic_violations, 0);
1348        assert_eq!(stats.analysis_start_time, 0);
1349    }
1350
1351    #[test]
1352    fn test_risk_assessment_engine_assess_unsafe_block() {
1353        let engine = RiskAssessmentEngine::new();
1354        let source = UnsafeSource::UnsafeBlock {
1355            location: "ptr::read(data)".to_string(),
1356            function: "test_function".to_string(),
1357            file_path: Some("test.rs".to_string()),
1358            line_number: Some(42),
1359        };
1360        let context = MemoryContext {
1361            total_allocated: 1024,
1362            active_allocations: 1,
1363            memory_pressure: MemoryPressureLevel::Low,
1364            allocation_patterns: vec![],
1365        };
1366
1367        let assessment = engine.assess_risk(&source, &context, &[]);
1368
1369        assert!(assessment.risk_score >= 0.0);
1370        assert!(assessment.confidence_score >= 0.0);
1371        assert!(assessment.assessment_timestamp > 0);
1372        assert!(!assessment.mitigation_suggestions.is_empty());
1373    }
1374
1375    #[test]
1376    fn test_risk_assessment_engine_assess_ffi_function() {
1377        let engine = RiskAssessmentEngine::new();
1378        let source = UnsafeSource::FfiFunction {
1379            library: "libc".to_string(),
1380            function: "malloc".to_string(),
1381            call_site: "test.rs:50".to_string(),
1382        };
1383        let context = MemoryContext {
1384            total_allocated: 2048,
1385            active_allocations: 2,
1386            memory_pressure: MemoryPressureLevel::Medium,
1387            allocation_patterns: vec![],
1388        };
1389
1390        let assessment = engine.assess_risk(&source, &context, &[]);
1391
1392        assert!(assessment.risk_score >= 0.0);
1393        assert!(assessment.confidence_score >= 0.0);
1394        assert!(!assessment.risk_factors.is_empty());
1395    }
1396
1397    #[test]
1398    fn test_risk_assessment_engine_assess_raw_pointer() {
1399        let engine = RiskAssessmentEngine::new();
1400        let source = UnsafeSource::RawPointer {
1401            operation: "dereference".to_string(),
1402            location: "test.rs:60".to_string(),
1403        };
1404        let context = MemoryContext {
1405            total_allocated: 512,
1406            active_allocations: 1,
1407            memory_pressure: MemoryPressureLevel::High,
1408            allocation_patterns: vec![],
1409        };
1410
1411        let assessment = engine.assess_risk(&source, &context, &[]);
1412
1413        assert!(assessment.risk_score >= 0.0);
1414        assert!(!assessment.risk_factors.is_empty());
1415        assert_eq!(
1416            assessment.risk_factors[0].factor_type,
1417            RiskFactorType::RawPointerDereference
1418        );
1419    }
1420
1421    #[test]
1422    fn test_risk_assessment_engine_assess_transmute() {
1423        let engine = RiskAssessmentEngine::new();
1424        let source = UnsafeSource::Transmute {
1425            from_type: "*const u8".to_string(),
1426            to_type: "*mut i32".to_string(),
1427            location: "test.rs:70".to_string(),
1428        };
1429        let context = MemoryContext {
1430            total_allocated: 256,
1431            active_allocations: 1,
1432            memory_pressure: MemoryPressureLevel::Critical,
1433            allocation_patterns: vec![],
1434        };
1435
1436        let assessment = engine.assess_risk(&source, &context, &[]);
1437
1438        assert!(assessment.risk_score >= 0.0);
1439        assert!(!assessment.risk_factors.is_empty());
1440        assert_eq!(
1441            assessment.risk_factors[0].factor_type,
1442            RiskFactorType::InvalidTransmute
1443        );
1444        // Pointer transmutes should have higher severity
1445        assert!(assessment.risk_factors[0].severity >= 8.0);
1446    }
1447
1448    #[test]
1449    fn test_memory_pressure_risk_adjustment() {
1450        let engine = RiskAssessmentEngine::new();
1451        let source = UnsafeSource::RawPointer {
1452            operation: "test".to_string(),
1453            location: "test.rs".to_string(),
1454        };
1455
1456        // Test different memory pressure levels
1457        let contexts = vec![
1458            (MemoryPressureLevel::Low, 0.8),
1459            (MemoryPressureLevel::Medium, 1.0),
1460            (MemoryPressureLevel::High, 1.2),
1461            (MemoryPressureLevel::Critical, 1.5),
1462        ];
1463
1464        for (pressure, _) in contexts {
1465            let context = MemoryContext {
1466                total_allocated: 1024,
1467                active_allocations: 1,
1468                memory_pressure: pressure,
1469                allocation_patterns: vec![],
1470            };
1471
1472            let assessment = engine.assess_risk(&source, &context, &[]);
1473            // Risk score should be adjusted by pressure multiplier
1474            assert!(assessment.risk_score > 0.0);
1475        }
1476    }
1477
1478    #[test]
1479    fn test_safety_analyzer_generate_unsafe_report() {
1480        let analyzer = SafetyAnalyzer::default();
1481        let source = UnsafeSource::UnsafeBlock {
1482            location: "test unsafe block".to_string(),
1483            function: "test_function".to_string(),
1484            file_path: Some("test.rs".to_string()),
1485            line_number: Some(10),
1486        };
1487        let allocations = vec![AllocationInfo::new(0x1000, 256)];
1488        let violations = vec![];
1489
1490        let result = analyzer.generate_unsafe_report(source, &allocations, &violations);
1491
1492        assert!(result.is_ok());
1493        let report_id = result.unwrap();
1494        assert!(!report_id.is_empty());
1495        assert!(report_id.starts_with("UNSAFE-UB-"));
1496    }
1497
1498    #[test]
1499    fn test_safety_analyzer_create_memory_passport() {
1500        let analyzer = SafetyAnalyzer::default();
1501        let allocation_ptr = 0x2000;
1502        let size_bytes = 512;
1503
1504        let result = analyzer.create_memory_passport(
1505            allocation_ptr,
1506            size_bytes,
1507            PassportEventType::AllocatedInRust,
1508        );
1509
1510        assert!(result.is_ok());
1511        let passport_id = result.unwrap();
1512        assert!(!passport_id.is_empty());
1513        assert!(passport_id.starts_with("passport_"));
1514    }
1515
1516    #[test]
1517    fn test_safety_analyzer_record_passport_event() {
1518        let analyzer = SafetyAnalyzer::default();
1519        let allocation_ptr = 0x3000;
1520
1521        // First create a passport
1522        let _ = analyzer.create_memory_passport(
1523            allocation_ptr,
1524            256,
1525            PassportEventType::AllocatedInRust,
1526        );
1527
1528        // Then record an event
1529        let result = analyzer.record_passport_event(
1530            allocation_ptr,
1531            PassportEventType::HandoverToFfi,
1532            "test context".to_string(),
1533        );
1534
1535        assert!(result.is_ok());
1536    }
1537
1538    #[test]
1539    fn test_safety_analyzer_finalize_passports() {
1540        let analyzer = SafetyAnalyzer::default();
1541
1542        // Create some passports
1543        let _ = analyzer.create_memory_passport(0x4000, 256, PassportEventType::AllocatedInRust);
1544        let _ = analyzer.create_memory_passport(0x5000, 512, PassportEventType::AllocatedInRust);
1545
1546        // Record handover event for one passport (should be detected as leak)
1547        let _ = analyzer.record_passport_event(
1548            0x4000,
1549            PassportEventType::HandoverToFfi,
1550            "handover".to_string(),
1551        );
1552
1553        let leaked_passports = analyzer.finalize_passports_at_shutdown();
1554
1555        // Should detect at least one leak
1556        assert!(!leaked_passports.is_empty());
1557    }
1558
1559    #[test]
1560    fn test_safety_analyzer_get_unsafe_reports() {
1561        let analyzer = SafetyAnalyzer::default();
1562        let source = UnsafeSource::FfiFunction {
1563            library: "test_lib".to_string(),
1564            function: "test_func".to_string(),
1565            call_site: "test.rs:100".to_string(),
1566        };
1567
1568        let _ = analyzer.generate_unsafe_report(source, &[], &[]);
1569        let reports = analyzer.get_unsafe_reports();
1570
1571        assert!(!reports.is_empty());
1572    }
1573
1574    #[test]
1575    fn test_safety_analyzer_get_memory_passports() {
1576        let analyzer = SafetyAnalyzer::default();
1577
1578        let _ = analyzer.create_memory_passport(0x6000, 128, PassportEventType::AllocatedInRust);
1579        let passports = analyzer.get_memory_passports();
1580
1581        assert!(!passports.is_empty());
1582        assert!(passports.contains_key(&0x6000));
1583    }
1584
1585    #[test]
1586    fn test_safety_analyzer_get_stats() {
1587        let analyzer = SafetyAnalyzer::default();
1588
1589        let stats = analyzer.get_stats();
1590
1591        assert_eq!(stats.total_reports, 0);
1592        assert_eq!(stats.total_passports, 0);
1593        assert!(stats.analysis_start_time > 0);
1594    }
1595
1596    #[test]
1597    fn test_safety_analyzer_with_disabled_features() {
1598        let config = SafetyAnalysisConfig {
1599            detailed_risk_assessment: false,
1600            enable_passport_tracking: false,
1601            min_risk_level: RiskLevel::High,
1602            max_reports: 10,
1603            enable_dynamic_violations: false,
1604        };
1605        let analyzer = SafetyAnalyzer::new(config);
1606
1607        // Test passport creation with disabled tracking
1608        let result =
1609            analyzer.create_memory_passport(0x7000, 256, PassportEventType::AllocatedInRust);
1610        assert!(result.is_ok());
1611        let passport_id = result.unwrap();
1612        assert!(passport_id.is_empty()); // Should return empty string when disabled
1613
1614        // Test event recording with disabled tracking
1615        let result = analyzer.record_passport_event(
1616            0x7000,
1617            PassportEventType::HandoverToFfi,
1618            "test".to_string(),
1619        );
1620        assert!(result.is_ok());
1621    }
1622
1623    #[test]
1624    fn test_safety_analyzer_report_filtering_by_risk_level() {
1625        let config = SafetyAnalysisConfig {
1626            min_risk_level: RiskLevel::High,
1627            ..Default::default()
1628        };
1629        let analyzer = SafetyAnalyzer::new(config);
1630
1631        // This should generate a low-risk report that gets filtered out
1632        let source = UnsafeSource::UnsafeBlock {
1633            location: "simple operation".to_string(),
1634            function: "test".to_string(),
1635            file_path: None,
1636            line_number: None,
1637        };
1638
1639        let result = analyzer.generate_unsafe_report(source, &[], &[]);
1640        assert!(result.is_ok());
1641
1642        // Reports should be filtered based on risk level
1643        let reports = analyzer.get_unsafe_reports();
1644        // The exact number depends on the risk assessment, but the mechanism should work
1645        assert!(reports.len() <= 1);
1646    }
1647
1648    #[test]
1649    fn test_safety_analyzer_max_reports_limit() {
1650        let config = SafetyAnalysisConfig {
1651            max_reports: 2,
1652            ..Default::default()
1653        };
1654        let analyzer = SafetyAnalyzer::new(config);
1655
1656        // Generate more reports than the limit
1657        for i in 0..5 {
1658            let source = UnsafeSource::RawPointer {
1659                operation: format!("operation_{i}"),
1660                location: format!("test.rs:{}", i * 10),
1661            };
1662            let _ = analyzer.generate_unsafe_report(source, &[], &[]);
1663        }
1664
1665        let reports = analyzer.get_unsafe_reports();
1666        assert!(reports.len() <= 2); // Should not exceed max_reports
1667    }
1668
1669    #[test]
1670    fn test_passport_status_determination() {
1671        let analyzer = SafetyAnalyzer::default();
1672
1673        // Test different event sequences
1674        let events_sequences = [
1675            // Normal Rust allocation and deallocation
1676            vec![PassportEventType::AllocatedInRust],
1677            // Handover to FFI without return
1678            vec![
1679                PassportEventType::AllocatedInRust,
1680                PassportEventType::HandoverToFfi,
1681            ],
1682            // Handover and reclaim
1683            vec![
1684                PassportEventType::AllocatedInRust,
1685                PassportEventType::HandoverToFfi,
1686                PassportEventType::ReclaimedByRust,
1687            ],
1688            // Foreign free
1689            vec![
1690                PassportEventType::AllocatedInRust,
1691                PassportEventType::HandoverToFfi,
1692                PassportEventType::FreedByForeign,
1693            ],
1694        ];
1695
1696        for (i, event_types) in events_sequences.iter().enumerate() {
1697            let ptr = 0x8000 + i * 0x1000;
1698            let _ = analyzer.create_memory_passport(ptr, 256, PassportEventType::AllocatedInRust);
1699
1700            // Record additional events
1701            for event_type in event_types.iter().skip(1) {
1702                let _ = analyzer.record_passport_event(ptr, event_type.clone(), "test".to_string());
1703            }
1704        }
1705
1706        let leaked_passports = analyzer.finalize_passports_at_shutdown();
1707        // Should detect leaks for handover-only scenarios
1708        assert!(!leaked_passports.is_empty());
1709    }
1710
1711    #[test]
1712    fn test_violation_type_conversion() {
1713        let analyzer = SafetyAnalyzer::default();
1714        let violations = vec![
1715            SafetyViolation::DoubleFree {
1716                first_free_stack: CallStackRef::new(0, Some(0)),
1717                second_free_stack: CallStackRef::new(1, Some(0)),
1718                timestamp: 1234567890,
1719            },
1720            SafetyViolation::InvalidFree {
1721                attempted_pointer: 0x2000,
1722                stack: CallStackRef::new(2, Some(0)),
1723                timestamp: 1234567891,
1724            },
1725        ];
1726
1727        let source = UnsafeSource::UnsafeBlock {
1728            location: "test".to_string(),
1729            function: "test".to_string(),
1730            file_path: None,
1731            line_number: None,
1732        };
1733
1734        let result = analyzer.generate_unsafe_report(source, &[], &violations);
1735        assert!(result.is_ok());
1736
1737        let reports = analyzer.get_unsafe_reports();
1738        assert!(!reports.is_empty());
1739
1740        let report = reports.values().next().unwrap();
1741        assert_eq!(report.dynamic_violations.len(), 2);
1742    }
1743
1744    #[test]
1745    fn test_comprehensive_unsafe_report_structure() {
1746        let analyzer = SafetyAnalyzer::default();
1747        let source = UnsafeSource::Transmute {
1748            from_type: "u64".to_string(),
1749            to_type: "f64".to_string(),
1750            location: "test.rs:200".to_string(),
1751        };
1752        let allocations = vec![
1753            AllocationInfo::new(0x1000, 512),
1754            AllocationInfo::new(0x2000, 1024),
1755        ];
1756
1757        let result = analyzer.generate_unsafe_report(source.clone(), &allocations, &[]);
1758        assert!(result.is_ok());
1759
1760        let reports = analyzer.get_unsafe_reports();
1761        let report = reports.values().next().unwrap();
1762
1763        // Verify report structure
1764        assert!(!report.report_id.is_empty());
1765        assert!(matches!(report.source, UnsafeSource::Transmute { .. }));
1766        assert!(report.risk_assessment.assessment_timestamp > 0);
1767        assert!(report.generated_at > 0);
1768        assert_eq!(report.memory_context.active_allocations, 2);
1769        assert_eq!(report.memory_context.total_allocated, 1536);
1770    }
1771}