Skip to main content

memscope_rs/analysis/safety/
engine.rs

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    /// Objective: Verify RiskAssessmentEngine creation with default weights
300    /// Invariants: Engine should initialize with predefined risk weights
301    #[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    /// Objective: Verify Default trait implementation
321    /// Invariants: Default should create same engine as new()
322    #[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    /// Objective: Verify analyze_unsafe_block with pointer dereference
342    /// Invariants: Should detect raw pointer dereference risk
343    #[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    /// Objective: Verify analyze_unsafe_block with memory management
372    /// Invariants: Should detect manual memory management risk
373    #[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    /// Objective: Verify analyze_unsafe_block with dealloc
398    /// Invariants: Should detect deallocation risk
399    #[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    /// Objective: Verify analyze_unsafe_block with free
424    /// Invariants: Should detect free operation risk
425    #[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    /// Objective: Verify analyze_ffi_function with normal function
450    /// Invariants: Should detect FFI call risk
451    #[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    /// Objective: Verify analyze_ffi_function with risky function (malloc)
479    /// Invariants: Should detect buffer overflow risk for malloc
480    #[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    /// Objective: Verify analyze_ffi_function with risky function (strcpy)
504    /// Invariants: Should detect buffer overflow risk for strcpy
505    #[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    /// Objective: Verify analyze_ffi_function with risky function (sprintf)
529    /// Invariants: Should detect buffer overflow risk for sprintf
530    #[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    /// Objective: Verify analyze_ffi_function with risky function (gets)
554    /// Invariants: Should detect buffer overflow risk for gets
555    #[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    /// Objective: Verify analyze_raw_pointer operation
579    /// Invariants: Should detect raw pointer dereference risk
580    #[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    /// Objective: Verify analyze_transmute with pointer types
612    /// Invariants: Should assign higher severity for pointer transmute
613    #[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    /// Objective: Verify analyze_transmute with non-pointer types
646    /// Invariants: Should assign lower severity for non-pointer transmute
647    #[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    /// Objective: Verify memory pressure multiplier - Critical
668    /// Invariants: Critical pressure should multiply risk by 1.5
669    #[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    /// Objective: Verify memory pressure multiplier - High
696    /// Invariants: High pressure should multiply risk by 1.2
697    #[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    /// Objective: Verify memory pressure multiplier - Medium
724    /// Invariants: Medium pressure should multiply risk by 1.0
725    #[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    /// Objective: Verify memory pressure multiplier - Low
752    /// Invariants: Low pressure should multiply risk by 0.8
753    #[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    /// Objective: Verify risk level calculation with critical memory pressure
780    /// Invariants: Risk assessment should complete successfully with critical memory
781    #[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    /// Objective: Verify risk level calculation for empty factors
807    /// Invariants: Empty risk factors should result in Low risk
808    #[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    /// Objective: Verify mitigation suggestions for Critical risk
830    /// Invariants: Critical risk should have urgent suggestions
831    #[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    /// Objective: Verify mitigation suggestions for High risk
862    /// Invariants: High risk should have thorough testing suggestion
863    #[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    /// Objective: Verify mitigation suggestions for Medium risk
893    /// Invariants: Medium risk should have review suggestion
894    #[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    /// Objective: Verify mitigation suggestions for Low risk
926    /// Invariants: Low risk should have monitor suggestion
927    #[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    /// Objective: Verify mitigation suggestions for RawPointerDereference
952    /// Invariants: Should include null pointer check suggestion
953    #[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    /// Objective: Verify mitigation suggestions for FfiCall
976    /// Invariants: Should include FFI validation suggestion
977    #[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    /// Objective: Verify mitigation suggestions for ManualMemoryManagement
1001    /// Invariants: Should include RAII suggestion
1002    #[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    /// Objective: Verify confidence score calculation
1027    /// Invariants: Confidence should be average of all factor confidences
1028    #[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    /// Objective: Verify risk score is capped at 100
1048    /// Invariants: Risk score should never exceed 100.0
1049    #[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    /// Objective: Verify assessment timestamp is recent
1075    /// Invariants: Timestamp should be close to current time
1076    #[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    /// Objective: Verify call stack is preserved in risk factors
1101    /// Invariants: Call stack should be included in each risk factor
1102    #[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    /// Objective: Verify multiple risk factors from single source
1132    /// Invariants: Unsafe block with pointer and alloc should have multiple factors
1133    #[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    /// Objective: Verify risk factor descriptions are meaningful
1155    /// Invariants: Each factor should have a non-empty description
1156    #[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    /// Objective: Verify transmute with pointer in to_type
1183    /// Invariants: Pointer in to_type should also trigger high severity
1184    #[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    /// Objective: Verify RiskFactorType variants coverage
1205    /// Invariants: All risk factor types should be handled
1206    #[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}