1use 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
18pub enum RiskFactorType {
19 RawPointerDereference,
21 UnsafeDataRace,
23 InvalidTransmute,
25 FfiCall,
27 ManualMemoryManagement,
29 CrossBoundaryTransfer,
31 UseAfterFree,
33 BufferOverflow,
35 LifetimeViolation,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RiskFactor {
42 pub factor_type: RiskFactorType,
44 pub severity: f64,
46 pub confidence: f64,
48 pub description: String,
50 pub source_location: Option<String>,
52 pub call_stack: Vec<StackFrame>,
54 pub mitigation: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct RiskAssessment {
61 pub risk_level: RiskLevel,
63 pub risk_score: f64,
65 pub risk_factors: Vec<RiskFactor>,
67 pub confidence_score: f64,
69 pub mitigation_suggestions: Vec<String>,
71 pub assessment_timestamp: u64,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct UnsafeReport {
78 pub report_id: String,
80 pub source: UnsafeSource,
82 pub risk_assessment: RiskAssessment,
84 pub dynamic_violations: Vec<DynamicViolation>,
86 pub related_passports: Vec<String>,
88 pub memory_context: MemoryContext,
90 pub generated_at: u64,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub enum UnsafeSource {
97 UnsafeBlock {
99 location: String,
101 function: String,
103 file_path: Option<String>,
105 line_number: Option<u32>,
107 },
108 FfiFunction {
110 library: String,
112 function: String,
114 call_site: String,
116 },
117 RawPointer {
119 operation: String,
121 location: String,
123 },
124 Transmute {
126 from_type: String,
128 to_type: String,
130 location: String,
132 },
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct DynamicViolation {
138 pub violation_type: ViolationType,
140 pub memory_address: usize,
142 pub memory_size: usize,
144 pub detected_at: u64,
146 pub call_stack: Vec<StackFrame>,
148 pub severity: RiskLevel,
150 pub context: String,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub enum ViolationType {
157 DoubleFree,
159 UseAfterFree,
161 BufferOverflow,
163 InvalidAccess,
165 DataRace,
167 FfiBoundaryViolation,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct MemoryContext {
174 pub total_allocated: usize,
176 pub active_allocations: usize,
178 pub memory_pressure: MemoryPressureLevel,
180 pub allocation_patterns: Vec<AllocationPattern>,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub enum MemoryPressureLevel {
187 Low,
188 Medium,
189 High,
190 Critical,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct AllocationPattern {
196 pub pattern_type: String,
198 pub frequency: u32,
200 pub average_size: usize,
202 pub risk_level: RiskLevel,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct MemoryPassport {
209 pub passport_id: String,
211 pub allocation_ptr: usize,
213 pub size_bytes: usize,
215 pub status_at_shutdown: PassportStatus,
217 pub lifecycle_events: Vec<PassportEvent>,
219 pub risk_assessment: RiskAssessment,
221 pub created_at: u64,
223 pub updated_at: u64,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub enum PassportStatus {
230 FreedByRust,
232 HandoverToFfi,
234 FreedByForeign,
236 ReclaimedByRust,
238 InForeignCustody,
240 Unknown,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PassportEvent {
247 pub event_type: PassportEventType,
249 pub timestamp: u64,
251 pub context: String,
253 pub call_stack: Vec<StackFrame>,
255 pub metadata: HashMap<String, String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub enum PassportEventType {
262 AllocatedInRust,
264 HandoverToFfi,
266 FreedByForeign,
268 ReclaimedByRust,
270 BoundaryAccess,
272 OwnershipTransfer,
274}
275
276pub struct SafetyAnalyzer {
278 unsafe_reports: Arc<Mutex<HashMap<String, UnsafeReport>>>,
280 memory_passports: Arc<Mutex<HashMap<usize, MemoryPassport>>>,
282 risk_engine: RiskAssessmentEngine,
284 config: SafetyAnalysisConfig,
286 stats: Arc<Mutex<SafetyAnalysisStats>>,
288}
289
290#[derive(Debug, Clone)]
292pub struct SafetyAnalysisConfig {
293 pub detailed_risk_assessment: bool,
295 pub enable_passport_tracking: bool,
297 pub min_risk_level: RiskLevel,
299 pub max_reports: usize,
301 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
319pub struct SafetyAnalysisStats {
320 pub total_reports: usize,
322 pub reports_by_risk_level: HashMap<String, usize>,
324 pub total_passports: usize,
326 pub passports_by_status: HashMap<String, usize>,
328 pub dynamic_violations: usize,
330 pub analysis_start_time: u64,
332}
333
334pub struct RiskAssessmentEngine {
336 _risk_weights: HashMap<RiskFactorType, f64>,
338 _historical_data: HashMap<String, Vec<f64>>,
340}
341
342impl RiskAssessmentEngine {
343 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 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 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 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 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 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 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 fn analyze_unsafe_block(&self, location: &str, call_stack: &[StackFrame]) -> Vec<RiskFactor> {
446 let mut factors = Vec::new();
447
448 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 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 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 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 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 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 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 } else {
543 7.0 };
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 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 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 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 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 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 let memory_context = self.create_memory_context(allocations);
673
674 let call_stack = self.capture_call_stack()?;
676
677 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 if !self.should_generate_report(&risk_assessment.risk_level) {
687 return Ok(report_id);
688 }
689
690 let dynamic_violations = self.convert_safety_violations(violations);
692
693 let related_passports = if self.config.enable_passport_tracking {
695 self.find_related_passports(&source, allocations)
696 } else {
697 Vec::new()
698 };
699
700 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 if let Ok(mut reports) = self.unsafe_reports.lock() {
716 if reports.len() >= self.config.max_reports {
718 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 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 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 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 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 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 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 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 let final_status = self.determine_final_passport_status(&passport.lifecycle_events);
862 passport.status_at_shutdown = final_status.clone();
863
864 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 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 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 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 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 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(), }
967 }
968
969 fn capture_call_stack(&self) -> TrackingResult<Vec<StackFrame>> {
970 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, 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 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 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 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 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 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 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 let _ = analyzer.create_memory_passport(
1523 allocation_ptr,
1524 256,
1525 PassportEventType::AllocatedInRust,
1526 );
1527
1528 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 let _ = analyzer.create_memory_passport(0x4000, 256, PassportEventType::AllocatedInRust);
1544 let _ = analyzer.create_memory_passport(0x5000, 512, PassportEventType::AllocatedInRust);
1545
1546 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 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 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()); 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 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 let reports = analyzer.get_unsafe_reports();
1644 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 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); }
1668
1669 #[test]
1670 fn test_passport_status_determination() {
1671 let analyzer = SafetyAnalyzer::default();
1672
1673 let events_sequences = [
1675 vec![PassportEventType::AllocatedInRust],
1677 vec![
1679 PassportEventType::AllocatedInRust,
1680 PassportEventType::HandoverToFfi,
1681 ],
1682 vec![
1684 PassportEventType::AllocatedInRust,
1685 PassportEventType::HandoverToFfi,
1686 PassportEventType::ReclaimedByRust,
1687 ],
1688 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 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 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 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}