1use crate::analysis::safety::types::*;
2use crate::analysis::unsafe_ffi_tracker::StackFrame;
3use std::collections::HashMap;
4use std::collections::HashSet;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7pub struct RiskAssessmentEngine {
8 _risk_weights: HashMap<RiskFactorType, f64>,
9 _historical_data: HashMap<String, Vec<f64>>,
10}
11
12impl RiskAssessmentEngine {
13 pub fn new() -> Self {
14 let mut risk_weights = HashMap::new();
15 risk_weights.insert(RiskFactorType::RawPointerDereference, 8.5);
16 risk_weights.insert(RiskFactorType::UnsafeDataRace, 9.0);
17 risk_weights.insert(RiskFactorType::InvalidTransmute, 7.5);
18 risk_weights.insert(RiskFactorType::FfiCall, 6.0);
19 risk_weights.insert(RiskFactorType::ManualMemoryManagement, 7.0);
20 risk_weights.insert(RiskFactorType::CrossBoundaryTransfer, 6.5);
21 risk_weights.insert(RiskFactorType::UseAfterFree, 9.5);
22 risk_weights.insert(RiskFactorType::BufferOverflow, 9.0);
23 risk_weights.insert(RiskFactorType::LifetimeViolation, 8.0);
24
25 Self {
26 _risk_weights: risk_weights,
27 _historical_data: HashMap::new(),
28 }
29 }
30
31 pub fn assess_risk(
32 &self,
33 source: &UnsafeSource,
34 context: &MemoryContext,
35 call_stack: &[StackFrame],
36 ) -> RiskAssessment {
37 let mut risk_factors = Vec::new();
38 let mut total_risk_score = 0.0;
39 let mut total_confidence = 0.0;
40
41 match source {
42 UnsafeSource::UnsafeBlock { location, .. } => {
43 risk_factors.extend(self.analyze_unsafe_block(location, call_stack));
44 }
45 UnsafeSource::FfiFunction {
46 library, function, ..
47 } => {
48 risk_factors.extend(self.analyze_ffi_function(library, function, call_stack));
49 }
50 UnsafeSource::RawPointer { operation, .. } => {
51 risk_factors.extend(self.analyze_raw_pointer(operation, call_stack));
52 }
53 UnsafeSource::Transmute {
54 from_type, to_type, ..
55 } => {
56 risk_factors.extend(self.analyze_transmute(from_type, to_type, call_stack));
57 }
58 }
59
60 for factor in &risk_factors {
61 total_risk_score += factor.severity * factor.confidence;
62 total_confidence += factor.confidence;
63 }
64
65 let risk_count = risk_factors.len() as f64;
66 let average_confidence = if risk_count > 0.0 {
67 total_confidence / risk_count
68 } else {
69 0.0
70 };
71
72 let pressure_multiplier = match context.memory_pressure {
73 MemoryPressureLevel::Critical => 1.5,
74 MemoryPressureLevel::High => 1.2,
75 MemoryPressureLevel::Medium => 1.0,
76 MemoryPressureLevel::Low => 0.8,
77 };
78
79 total_risk_score *= pressure_multiplier;
80
81 let risk_level = if risk_factors.is_empty() {
82 crate::analysis::unsafe_ffi_tracker::RiskLevel::Low
83 } else if total_risk_score >= 80.0 {
84 crate::analysis::unsafe_ffi_tracker::RiskLevel::Critical
85 } else if total_risk_score >= 60.0 {
86 crate::analysis::unsafe_ffi_tracker::RiskLevel::High
87 } else if total_risk_score >= 40.0 {
88 crate::analysis::unsafe_ffi_tracker::RiskLevel::Medium
89 } else {
90 crate::analysis::unsafe_ffi_tracker::RiskLevel::Low
91 };
92
93 let mitigation_suggestions =
94 self.generate_mitigation_suggestions(&risk_factors, &risk_level);
95
96 RiskAssessment {
97 risk_level,
98 risk_score: total_risk_score.min(100.0),
99 risk_factors,
100 confidence_score: average_confidence,
101 mitigation_suggestions,
102 assessment_timestamp: SystemTime::now()
103 .duration_since(UNIX_EPOCH)
104 .unwrap_or_default()
105 .as_secs(),
106 }
107 }
108
109 fn analyze_unsafe_block(&self, location: &str, call_stack: &[StackFrame]) -> Vec<RiskFactor> {
110 let mut factors = Vec::new();
111
112 if location.contains("*") || location.contains("ptr::") {
113 factors.push(RiskFactor {
114 factor_type: RiskFactorType::RawPointerDereference,
115 severity: 7.5,
116 confidence: 0.8,
117 description: "Raw pointer dereference in unsafe block".to_string(),
118 source_location: Some(location.to_string()),
119 call_stack: call_stack.to_vec(),
120 mitigation: "Add bounds checking and null pointer validation".to_string(),
121 });
122 }
123
124 if location.contains("alloc") || location.contains("dealloc") || location.contains("free") {
125 factors.push(RiskFactor {
126 factor_type: RiskFactorType::ManualMemoryManagement,
127 severity: 6.5,
128 confidence: 0.9,
129 description: "Manual memory management in unsafe block".to_string(),
130 source_location: Some(location.to_string()),
131 call_stack: call_stack.to_vec(),
132 mitigation: "Use RAII patterns and smart pointers where possible".to_string(),
133 });
134 }
135
136 factors
137 }
138
139 fn analyze_ffi_function(
140 &self,
141 library: &str,
142 function: &str,
143 call_stack: &[StackFrame],
144 ) -> Vec<RiskFactor> {
145 let mut factors = Vec::new();
146
147 factors.push(RiskFactor {
148 factor_type: RiskFactorType::FfiCall,
149 severity: 5.5,
150 confidence: 0.7,
151 description: format!("FFI call to {library}::{function}"),
152 source_location: Some(format!("{library}::{function}")),
153 call_stack: call_stack.to_vec(),
154 mitigation: "Validate all parameters and return values".to_string(),
155 });
156
157 let risky_functions = ["malloc", "free", "strcpy", "strcat", "sprintf", "gets"];
158 if risky_functions.iter().any(|&f| function.contains(f)) {
159 factors.push(RiskFactor {
160 factor_type: RiskFactorType::BufferOverflow,
161 severity: 8.0,
162 confidence: 0.9,
163 description: format!("Call to potentially unsafe function: {function}"),
164 source_location: Some(format!("{library}::{function}")),
165 call_stack: call_stack.to_vec(),
166 mitigation: "Use safer alternatives or add explicit bounds checking".to_string(),
167 });
168 }
169
170 factors
171 }
172
173 fn analyze_raw_pointer(&self, operation: &str, call_stack: &[StackFrame]) -> Vec<RiskFactor> {
174 let mut factors = Vec::new();
175
176 factors.push(RiskFactor {
177 factor_type: RiskFactorType::RawPointerDereference,
178 severity: 8.0,
179 confidence: 0.85,
180 description: format!("Raw pointer operation: {operation}"),
181 source_location: Some(operation.to_string()),
182 call_stack: call_stack.to_vec(),
183 mitigation: "Add null checks and bounds validation".to_string(),
184 });
185
186 factors
187 }
188
189 fn analyze_transmute(
190 &self,
191 from_type: &str,
192 to_type: &str,
193 call_stack: &[StackFrame],
194 ) -> Vec<RiskFactor> {
195 let mut factors = Vec::new();
196
197 let severity = if from_type.contains("*") || to_type.contains("*") {
198 9.0
199 } else {
200 7.0
201 };
202
203 factors.push(RiskFactor {
204 factor_type: RiskFactorType::InvalidTransmute,
205 severity,
206 confidence: 0.8,
207 description: format!("Transmute from {from_type} to {to_type}"),
208 source_location: Some(format!("{from_type} -> {to_type}")),
209 call_stack: call_stack.to_vec(),
210 mitigation: "Verify size and alignment compatibility".to_string(),
211 });
212
213 factors
214 }
215
216 fn generate_mitigation_suggestions(
217 &self,
218 risk_factors: &[RiskFactor],
219 risk_level: &crate::analysis::unsafe_ffi_tracker::RiskLevel,
220 ) -> Vec<String> {
221 let mut suggestions = Vec::new();
222
223 match risk_level {
224 crate::analysis::unsafe_ffi_tracker::RiskLevel::Critical => {
225 suggestions.push(
226 "URGENT: Critical safety issues detected - immediate review required"
227 .to_string(),
228 );
229 suggestions.push(
230 "Consider refactoring to eliminate unsafe code where possible".to_string(),
231 );
232 }
233 crate::analysis::unsafe_ffi_tracker::RiskLevel::High => {
234 suggestions.push(
235 "High-risk operations detected - thorough testing recommended".to_string(),
236 );
237 suggestions.push("Add comprehensive error handling and validation".to_string());
238 }
239 crate::analysis::unsafe_ffi_tracker::RiskLevel::Medium => {
240 suggestions
241 .push("Moderate risks detected - review and add safety checks".to_string());
242 }
243 crate::analysis::unsafe_ffi_tracker::RiskLevel::Low => {
244 suggestions.push("Low-level risks detected - monitor for issues".to_string());
245 }
246 }
247
248 let mut factor_types: HashSet<RiskFactorType> = HashSet::new();
249 for factor in risk_factors {
250 factor_types.insert(factor.factor_type.clone());
251 }
252
253 for factor_type in factor_types {
254 match factor_type {
255 RiskFactorType::RawPointerDereference => {
256 suggestions.push("Add null pointer checks before dereferencing".to_string());
257 suggestions.push("Validate pointer bounds and alignment".to_string());
258 }
259 RiskFactorType::UnsafeDataRace => {
260 suggestions.push("Use proper synchronization primitives".to_string());
261 suggestions.push("Consider using atomic operations".to_string());
262 }
263 RiskFactorType::FfiCall => {
264 suggestions.push("Validate all FFI parameters and return values".to_string());
265 suggestions.push("Handle FFI errors gracefully".to_string());
266 }
267 RiskFactorType::ManualMemoryManagement => {
268 suggestions.push("Use RAII patterns to ensure cleanup".to_string());
269 suggestions.push("Consider using smart pointers".to_string());
270 }
271 _ => {}
272 }
273 }
274
275 suggestions
276 }
277}
278
279impl Default for RiskAssessmentEngine {
280 fn default() -> Self {
281 Self::new()
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::analysis::unsafe_ffi_tracker::RiskLevel;
289
290 fn default_memory_context() -> MemoryContext {
291 MemoryContext {
292 total_allocated: 0,
293 active_allocations: 0,
294 memory_pressure: MemoryPressureLevel::Low,
295 allocation_patterns: Vec::new(),
296 }
297 }
298
299 #[test]
302 fn test_engine_creation() {
303 let engine = RiskAssessmentEngine::new();
304 let assessment = engine.assess_risk(
305 &UnsafeSource::UnsafeBlock {
306 location: "test.rs".to_string(),
307 function: "test".to_string(),
308 file_path: None,
309 line_number: None,
310 },
311 &default_memory_context(),
312 &[],
313 );
314 assert!(
315 assessment.risk_score >= 0.0,
316 "Risk score should be non-negative"
317 );
318 }
319
320 #[test]
323 fn test_engine_default() {
324 let engine = RiskAssessmentEngine::default();
325 let assessment = engine.assess_risk(
326 &UnsafeSource::UnsafeBlock {
327 location: "test.rs".to_string(),
328 function: "test".to_string(),
329 file_path: None,
330 line_number: None,
331 },
332 &default_memory_context(),
333 &[],
334 );
335 assert!(
336 assessment.confidence_score >= 0.0,
337 "Confidence score should be non-negative"
338 );
339 }
340
341 #[test]
344 fn test_analyze_unsafe_block_pointer() {
345 let engine = RiskAssessmentEngine::new();
346
347 let assessment = engine.assess_risk(
348 &UnsafeSource::UnsafeBlock {
349 location: "*ptr::read".to_string(),
350 function: "test".to_string(),
351 file_path: None,
352 line_number: None,
353 },
354 &default_memory_context(),
355 &[],
356 );
357
358 assert!(
359 !assessment.risk_factors.is_empty(),
360 "Should detect pointer risk in unsafe block"
361 );
362 assert!(
363 assessment
364 .risk_factors
365 .iter()
366 .any(|f| matches!(f.factor_type, RiskFactorType::RawPointerDereference)),
367 "Should have RawPointerDereference factor"
368 );
369 }
370
371 #[test]
374 fn test_analyze_unsafe_block_memory() {
375 let engine = RiskAssessmentEngine::new();
376
377 let assessment = engine.assess_risk(
378 &UnsafeSource::UnsafeBlock {
379 location: "alloc::alloc".to_string(),
380 function: "test".to_string(),
381 file_path: None,
382 line_number: None,
383 },
384 &default_memory_context(),
385 &[],
386 );
387
388 assert!(
389 assessment
390 .risk_factors
391 .iter()
392 .any(|f| matches!(f.factor_type, RiskFactorType::ManualMemoryManagement)),
393 "Should detect manual memory management"
394 );
395 }
396
397 #[test]
400 fn test_analyze_unsafe_block_dealloc() {
401 let engine = RiskAssessmentEngine::new();
402
403 let assessment = engine.assess_risk(
404 &UnsafeSource::UnsafeBlock {
405 location: "dealloc".to_string(),
406 function: "test".to_string(),
407 file_path: None,
408 line_number: None,
409 },
410 &default_memory_context(),
411 &[],
412 );
413
414 assert!(
415 assessment
416 .risk_factors
417 .iter()
418 .any(|f| matches!(f.factor_type, RiskFactorType::ManualMemoryManagement)),
419 "Should detect deallocation as memory management"
420 );
421 }
422
423 #[test]
426 fn test_analyze_unsafe_block_free() {
427 let engine = RiskAssessmentEngine::new();
428
429 let assessment = engine.assess_risk(
430 &UnsafeSource::UnsafeBlock {
431 location: "free_memory".to_string(),
432 function: "test".to_string(),
433 file_path: None,
434 line_number: None,
435 },
436 &default_memory_context(),
437 &[],
438 );
439
440 assert!(
441 assessment
442 .risk_factors
443 .iter()
444 .any(|f| matches!(f.factor_type, RiskFactorType::ManualMemoryManagement)),
445 "Should detect free as memory management"
446 );
447 }
448
449 #[test]
452 fn test_analyze_ffi_function_normal() {
453 let engine = RiskAssessmentEngine::new();
454
455 let assessment = engine.assess_risk(
456 &UnsafeSource::FfiFunction {
457 library: "libc".to_string(),
458 function: "printf".to_string(),
459 call_site: "test.rs".to_string(),
460 },
461 &default_memory_context(),
462 &[],
463 );
464
465 assert!(
466 !assessment.risk_factors.is_empty(),
467 "Should detect FFI call risk"
468 );
469 assert!(
470 assessment
471 .risk_factors
472 .iter()
473 .any(|f| matches!(f.factor_type, RiskFactorType::FfiCall)),
474 "Should have FfiCall factor"
475 );
476 }
477
478 #[test]
481 fn test_analyze_ffi_function_malloc() {
482 let engine = RiskAssessmentEngine::new();
483
484 let assessment = engine.assess_risk(
485 &UnsafeSource::FfiFunction {
486 library: "libc".to_string(),
487 function: "malloc".to_string(),
488 call_site: "test.rs".to_string(),
489 },
490 &default_memory_context(),
491 &[],
492 );
493
494 assert!(
495 assessment
496 .risk_factors
497 .iter()
498 .any(|f| matches!(f.factor_type, RiskFactorType::BufferOverflow)),
499 "Should detect BufferOverflow risk for malloc"
500 );
501 }
502
503 #[test]
506 fn test_analyze_ffi_function_strcpy() {
507 let engine = RiskAssessmentEngine::new();
508
509 let assessment = engine.assess_risk(
510 &UnsafeSource::FfiFunction {
511 library: "libc".to_string(),
512 function: "strcpy".to_string(),
513 call_site: "test.rs".to_string(),
514 },
515 &default_memory_context(),
516 &[],
517 );
518
519 assert!(
520 assessment
521 .risk_factors
522 .iter()
523 .any(|f| matches!(f.factor_type, RiskFactorType::BufferOverflow)),
524 "Should detect BufferOverflow risk for strcpy"
525 );
526 }
527
528 #[test]
531 fn test_analyze_ffi_function_sprintf() {
532 let engine = RiskAssessmentEngine::new();
533
534 let assessment = engine.assess_risk(
535 &UnsafeSource::FfiFunction {
536 library: "libc".to_string(),
537 function: "sprintf".to_string(),
538 call_site: "test.rs".to_string(),
539 },
540 &default_memory_context(),
541 &[],
542 );
543
544 assert!(
545 assessment
546 .risk_factors
547 .iter()
548 .any(|f| matches!(f.factor_type, RiskFactorType::BufferOverflow)),
549 "Should detect BufferOverflow risk for sprintf"
550 );
551 }
552
553 #[test]
556 fn test_analyze_ffi_function_gets() {
557 let engine = RiskAssessmentEngine::new();
558
559 let assessment = engine.assess_risk(
560 &UnsafeSource::FfiFunction {
561 library: "libc".to_string(),
562 function: "gets".to_string(),
563 call_site: "test.rs".to_string(),
564 },
565 &default_memory_context(),
566 &[],
567 );
568
569 assert!(
570 assessment
571 .risk_factors
572 .iter()
573 .any(|f| matches!(f.factor_type, RiskFactorType::BufferOverflow)),
574 "Should detect BufferOverflow risk for gets"
575 );
576 }
577
578 #[test]
581 fn test_analyze_raw_pointer() {
582 let engine = RiskAssessmentEngine::new();
583
584 let assessment = engine.assess_risk(
585 &UnsafeSource::RawPointer {
586 operation: "dereference".to_string(),
587 location: "test.rs".to_string(),
588 },
589 &default_memory_context(),
590 &[],
591 );
592
593 assert_eq!(
594 assessment.risk_factors.len(),
595 1,
596 "Should have exactly one risk factor"
597 );
598 assert!(
599 matches!(
600 assessment.risk_factors[0].factor_type,
601 RiskFactorType::RawPointerDereference
602 ),
603 "Should have RawPointerDereference factor"
604 );
605 assert!(
606 assessment.risk_factors[0].severity > 0.0,
607 "Severity should be positive"
608 );
609 }
610
611 #[test]
614 fn test_analyze_transmute_pointer() {
615 let engine = RiskAssessmentEngine::new();
616
617 let assessment = engine.assess_risk(
618 &UnsafeSource::Transmute {
619 from_type: "*const u8".to_string(),
620 to_type: "usize".to_string(),
621 location: "test.rs".to_string(),
622 },
623 &default_memory_context(),
624 &[],
625 );
626
627 assert_eq!(
628 assessment.risk_factors.len(),
629 1,
630 "Should have exactly one risk factor"
631 );
632 assert!(
633 matches!(
634 assessment.risk_factors[0].factor_type,
635 RiskFactorType::InvalidTransmute
636 ),
637 "Should have InvalidTransmute factor"
638 );
639 assert_eq!(
640 assessment.risk_factors[0].severity, 9.0,
641 "Pointer transmute should have severity 9.0"
642 );
643 }
644
645 #[test]
648 fn test_analyze_transmute_non_pointer() {
649 let engine = RiskAssessmentEngine::new();
650
651 let assessment = engine.assess_risk(
652 &UnsafeSource::Transmute {
653 from_type: "u32".to_string(),
654 to_type: "i32".to_string(),
655 location: "test.rs".to_string(),
656 },
657 &default_memory_context(),
658 &[],
659 );
660
661 assert_eq!(
662 assessment.risk_factors[0].severity, 7.0,
663 "Non-pointer transmute should have severity 7.0"
664 );
665 }
666
667 #[test]
670 fn test_memory_pressure_critical() {
671 let engine = RiskAssessmentEngine::new();
672
673 let context = MemoryContext {
674 total_allocated: 2 * 1024 * 1024 * 1024,
675 active_allocations: 100,
676 memory_pressure: MemoryPressureLevel::Critical,
677 allocation_patterns: Vec::new(),
678 };
679
680 let assessment = engine.assess_risk(
681 &UnsafeSource::RawPointer {
682 operation: "test".to_string(),
683 location: "test.rs".to_string(),
684 },
685 &context,
686 &[],
687 );
688
689 assert!(
690 assessment.risk_score > 0.0,
691 "Critical pressure should increase risk score"
692 );
693 }
694
695 #[test]
698 fn test_memory_pressure_high() {
699 let engine = RiskAssessmentEngine::new();
700
701 let context = MemoryContext {
702 total_allocated: 600 * 1024 * 1024,
703 active_allocations: 50,
704 memory_pressure: MemoryPressureLevel::High,
705 allocation_patterns: Vec::new(),
706 };
707
708 let assessment = engine.assess_risk(
709 &UnsafeSource::RawPointer {
710 operation: "test".to_string(),
711 location: "test.rs".to_string(),
712 },
713 &context,
714 &[],
715 );
716
717 assert!(
718 assessment.risk_score > 0.0,
719 "High pressure should affect risk score"
720 );
721 }
722
723 #[test]
726 fn test_memory_pressure_medium() {
727 let engine = RiskAssessmentEngine::new();
728
729 let context = MemoryContext {
730 total_allocated: 300 * 1024 * 1024,
731 active_allocations: 30,
732 memory_pressure: MemoryPressureLevel::Medium,
733 allocation_patterns: Vec::new(),
734 };
735
736 let assessment = engine.assess_risk(
737 &UnsafeSource::RawPointer {
738 operation: "test".to_string(),
739 location: "test.rs".to_string(),
740 },
741 &context,
742 &[],
743 );
744
745 assert!(
746 assessment.risk_score > 0.0,
747 "Medium pressure should not reduce risk score"
748 );
749 }
750
751 #[test]
754 fn test_memory_pressure_low() {
755 let engine = RiskAssessmentEngine::new();
756
757 let context = MemoryContext {
758 total_allocated: 100 * 1024 * 1024,
759 active_allocations: 10,
760 memory_pressure: MemoryPressureLevel::Low,
761 allocation_patterns: Vec::new(),
762 };
763
764 let assessment = engine.assess_risk(
765 &UnsafeSource::RawPointer {
766 operation: "test".to_string(),
767 location: "test.rs".to_string(),
768 },
769 &context,
770 &[],
771 );
772
773 assert!(
774 assessment.risk_score >= 0.0,
775 "Low pressure should produce valid risk score"
776 );
777 }
778
779 #[test]
782 fn test_risk_level_critical() {
783 let engine = RiskAssessmentEngine::new();
784
785 let assessment = engine.assess_risk(
786 &UnsafeSource::FfiFunction {
787 library: "libc".to_string(),
788 function: "malloc".to_string(),
789 call_site: "test.rs".to_string(),
790 },
791 &MemoryContext {
792 total_allocated: 2 * 1024 * 1024 * 1024,
793 active_allocations: 100,
794 memory_pressure: MemoryPressureLevel::Critical,
795 allocation_patterns: Vec::new(),
796 },
797 &[],
798 );
799
800 assert!(
801 assessment.risk_score >= 0.0,
802 "Risk score should be non-negative"
803 );
804 }
805
806 #[test]
809 fn test_risk_level_empty_factors() {
810 let engine = RiskAssessmentEngine::new();
811
812 let assessment = engine.assess_risk(
813 &UnsafeSource::UnsafeBlock {
814 location: "safe_location".to_string(),
815 function: "safe_function".to_string(),
816 file_path: None,
817 line_number: None,
818 },
819 &default_memory_context(),
820 &[],
821 );
822
823 assert!(
824 matches!(assessment.risk_level, RiskLevel::Low),
825 "No risk factors should result in Low risk level"
826 );
827 }
828
829 #[test]
832 fn test_mitigation_critical() {
833 let engine = RiskAssessmentEngine::new();
834
835 let assessment = engine.assess_risk(
836 &UnsafeSource::FfiFunction {
837 library: "libc".to_string(),
838 function: "malloc".to_string(),
839 call_site: "test.rs".to_string(),
840 },
841 &MemoryContext {
842 total_allocated: 2 * 1024 * 1024 * 1024,
843 active_allocations: 100,
844 memory_pressure: MemoryPressureLevel::Critical,
845 allocation_patterns: Vec::new(),
846 },
847 &[],
848 );
849
850 if matches!(assessment.risk_level, RiskLevel::Critical) {
851 assert!(
852 assessment
853 .mitigation_suggestions
854 .iter()
855 .any(|s| s.contains("URGENT") || s.contains("Critical")),
856 "Critical risk should have urgent suggestions"
857 );
858 }
859 }
860
861 #[test]
864 fn test_mitigation_high() {
865 let engine = RiskAssessmentEngine::new();
866
867 let assessment = engine.assess_risk(
868 &UnsafeSource::RawPointer {
869 operation: "dereference".to_string(),
870 location: "test.rs".to_string(),
871 },
872 &MemoryContext {
873 total_allocated: 0,
874 active_allocations: 0,
875 memory_pressure: MemoryPressureLevel::High,
876 allocation_patterns: Vec::new(),
877 },
878 &[],
879 );
880
881 if matches!(assessment.risk_level, RiskLevel::High) {
882 assert!(
883 assessment
884 .mitigation_suggestions
885 .iter()
886 .any(|s| s.contains("High-risk") || s.contains("testing")),
887 "High risk should have testing suggestions"
888 );
889 }
890 }
891
892 #[test]
895 fn test_mitigation_medium() {
896 let engine = RiskAssessmentEngine::new();
897
898 let assessment = engine.assess_risk(
899 &UnsafeSource::UnsafeBlock {
900 location: "test.rs".to_string(),
901 function: "test".to_string(),
902 file_path: None,
903 line_number: None,
904 },
905 &MemoryContext {
906 total_allocated: 0,
907 active_allocations: 0,
908 memory_pressure: MemoryPressureLevel::Medium,
909 allocation_patterns: Vec::new(),
910 },
911 &[],
912 );
913
914 if matches!(assessment.risk_level, RiskLevel::Medium) {
915 assert!(
916 assessment
917 .mitigation_suggestions
918 .iter()
919 .any(|s| s.contains("Moderate") || s.contains("review")),
920 "Medium risk should have review suggestions"
921 );
922 }
923 }
924
925 #[test]
928 fn test_mitigation_low() {
929 let engine = RiskAssessmentEngine::new();
930
931 let assessment = engine.assess_risk(
932 &UnsafeSource::UnsafeBlock {
933 location: "safe".to_string(),
934 function: "safe".to_string(),
935 file_path: None,
936 line_number: None,
937 },
938 &default_memory_context(),
939 &[],
940 );
941
942 assert!(
943 assessment
944 .mitigation_suggestions
945 .iter()
946 .any(|s| s.contains("Low") || s.contains("monitor")),
947 "Low risk should have monitor suggestions"
948 );
949 }
950
951 #[test]
954 fn test_mitigation_raw_pointer() {
955 let engine = RiskAssessmentEngine::new();
956
957 let assessment = engine.assess_risk(
958 &UnsafeSource::RawPointer {
959 operation: "dereference".to_string(),
960 location: "test.rs".to_string(),
961 },
962 &default_memory_context(),
963 &[],
964 );
965
966 assert!(
967 assessment
968 .mitigation_suggestions
969 .iter()
970 .any(|s| s.contains("null") || s.contains("pointer")),
971 "Should have null pointer check suggestion"
972 );
973 }
974
975 #[test]
978 fn test_mitigation_ffi_call() {
979 let engine = RiskAssessmentEngine::new();
980
981 let assessment = engine.assess_risk(
982 &UnsafeSource::FfiFunction {
983 library: "libc".to_string(),
984 function: "printf".to_string(),
985 call_site: "test.rs".to_string(),
986 },
987 &default_memory_context(),
988 &[],
989 );
990
991 assert!(
992 assessment
993 .mitigation_suggestions
994 .iter()
995 .any(|s| s.contains("FFI") || s.contains("validate")),
996 "Should have FFI validation suggestion"
997 );
998 }
999
1000 #[test]
1003 fn test_mitigation_memory_management() {
1004 let engine = RiskAssessmentEngine::new();
1005
1006 let assessment = engine.assess_risk(
1007 &UnsafeSource::UnsafeBlock {
1008 location: "alloc".to_string(),
1009 function: "test".to_string(),
1010 file_path: None,
1011 line_number: None,
1012 },
1013 &default_memory_context(),
1014 &[],
1015 );
1016
1017 assert!(
1018 assessment
1019 .mitigation_suggestions
1020 .iter()
1021 .any(|s| s.contains("RAII") || s.contains("smart pointer")),
1022 "Should have RAII suggestion"
1023 );
1024 }
1025
1026 #[test]
1029 fn test_confidence_calculation() {
1030 let engine = RiskAssessmentEngine::new();
1031
1032 let assessment = engine.assess_risk(
1033 &UnsafeSource::RawPointer {
1034 operation: "test".to_string(),
1035 location: "test.rs".to_string(),
1036 },
1037 &default_memory_context(),
1038 &[],
1039 );
1040
1041 assert!(
1042 assessment.confidence_score >= 0.0 && assessment.confidence_score <= 1.0,
1043 "Confidence should be between 0 and 1"
1044 );
1045 }
1046
1047 #[test]
1050 fn test_risk_score_cap() {
1051 let engine = RiskAssessmentEngine::new();
1052
1053 let assessment = engine.assess_risk(
1054 &UnsafeSource::FfiFunction {
1055 library: "libc".to_string(),
1056 function: "malloc".to_string(),
1057 call_site: "test.rs".to_string(),
1058 },
1059 &MemoryContext {
1060 total_allocated: usize::MAX,
1061 active_allocations: usize::MAX,
1062 memory_pressure: MemoryPressureLevel::Critical,
1063 allocation_patterns: Vec::new(),
1064 },
1065 &[],
1066 );
1067
1068 assert!(
1069 assessment.risk_score <= 100.0,
1070 "Risk score should be capped at 100"
1071 );
1072 }
1073
1074 #[test]
1077 fn test_assessment_timestamp() {
1078 let engine = RiskAssessmentEngine::new();
1079
1080 let now = SystemTime::now()
1081 .duration_since(UNIX_EPOCH)
1082 .unwrap()
1083 .as_secs();
1084
1085 let assessment = engine.assess_risk(
1086 &UnsafeSource::UnsafeBlock {
1087 location: "test".to_string(),
1088 function: "test".to_string(),
1089 file_path: None,
1090 line_number: None,
1091 },
1092 &default_memory_context(),
1093 &[],
1094 );
1095
1096 let diff = assessment.assessment_timestamp.abs_diff(now);
1097 assert!(diff < 5, "Timestamp should be within 5 seconds of now");
1098 }
1099
1100 #[test]
1103 fn test_call_stack_preservation() {
1104 let engine = RiskAssessmentEngine::new();
1105
1106 let call_stack = vec![StackFrame {
1107 function_name: "test_fn".to_string(),
1108 file_name: Some("test.rs".to_string()),
1109 line_number: Some(10),
1110 is_unsafe: true,
1111 }];
1112
1113 let assessment = engine.assess_risk(
1114 &UnsafeSource::RawPointer {
1115 operation: "test".to_string(),
1116 location: "test.rs".to_string(),
1117 },
1118 &default_memory_context(),
1119 &call_stack,
1120 );
1121
1122 for factor in &assessment.risk_factors {
1123 assert_eq!(
1124 factor.call_stack.len(),
1125 1,
1126 "Call stack should be preserved in risk factor"
1127 );
1128 }
1129 }
1130
1131 #[test]
1134 fn test_multiple_risk_factors() {
1135 let engine = RiskAssessmentEngine::new();
1136
1137 let assessment = engine.assess_risk(
1138 &UnsafeSource::UnsafeBlock {
1139 location: "*ptr alloc".to_string(),
1140 function: "test".to_string(),
1141 file_path: None,
1142 line_number: None,
1143 },
1144 &default_memory_context(),
1145 &[],
1146 );
1147
1148 assert!(
1149 assessment.risk_factors.len() >= 2,
1150 "Should have multiple risk factors for combined risks"
1151 );
1152 }
1153
1154 #[test]
1157 fn test_risk_factor_descriptions() {
1158 let engine = RiskAssessmentEngine::new();
1159
1160 let assessment = engine.assess_risk(
1161 &UnsafeSource::FfiFunction {
1162 library: "libc".to_string(),
1163 function: "malloc".to_string(),
1164 call_site: "test.rs".to_string(),
1165 },
1166 &default_memory_context(),
1167 &[],
1168 );
1169
1170 for factor in &assessment.risk_factors {
1171 assert!(
1172 !factor.description.is_empty(),
1173 "Risk factor should have description"
1174 );
1175 assert!(
1176 !factor.mitigation.is_empty(),
1177 "Risk factor should have mitigation"
1178 );
1179 }
1180 }
1181
1182 #[test]
1185 fn test_transmute_pointer_to_type() {
1186 let engine = RiskAssessmentEngine::new();
1187
1188 let assessment = engine.assess_risk(
1189 &UnsafeSource::Transmute {
1190 from_type: "usize".to_string(),
1191 to_type: "*mut u8".to_string(),
1192 location: "test.rs".to_string(),
1193 },
1194 &default_memory_context(),
1195 &[],
1196 );
1197
1198 assert_eq!(
1199 assessment.risk_factors[0].severity, 9.0,
1200 "Transmute to pointer should have high severity"
1201 );
1202 }
1203
1204 #[test]
1207 fn test_risk_factor_type_variants() {
1208 let types = vec![
1209 RiskFactorType::RawPointerDereference,
1210 RiskFactorType::UnsafeDataRace,
1211 RiskFactorType::InvalidTransmute,
1212 RiskFactorType::FfiCall,
1213 RiskFactorType::ManualMemoryManagement,
1214 RiskFactorType::CrossBoundaryTransfer,
1215 RiskFactorType::UseAfterFree,
1216 RiskFactorType::BufferOverflow,
1217 RiskFactorType::LifetimeViolation,
1218 ];
1219
1220 for factor_type in types {
1221 let debug_str = format!("{:?}", factor_type);
1222 assert!(
1223 !debug_str.is_empty(),
1224 "RiskFactorType should have debug representation"
1225 );
1226 }
1227 }
1228}