1use crate::core::types::AllocationInfo;
7use crate::export::binary::error::BinaryExportError;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FfiSafetyAnalysis {
14 pub summary: FfiSafetySummary,
16 pub unsafe_operations: Vec<UnsafeOperation>,
18 pub ffi_hotspots: Vec<FfiHotspot>,
20 pub risk_assessment: RiskAssessment,
22 pub call_graph: FfiCallGraph,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct FfiSafetySummary {
29 pub total_allocations: usize,
31 pub unsafe_operations_count: usize,
33 pub ffi_allocations_count: usize,
35 pub safety_score: u32,
37 pub risk_level: RiskLevel,
39 pub ffi_memory_usage: usize,
41 pub ffi_memory_percentage: f64,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct UnsafeOperation {
48 pub id: String,
50 pub operation_type: UnsafeOperationType,
52 pub memory_address: usize,
54 pub memory_size: usize,
56 pub location: String,
58 pub stack_trace: Vec<String>,
60 pub risk_level: RiskLevel,
62 pub description: String,
64 pub timestamp: u64,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct FfiHotspot {
71 pub name: String,
73 pub call_count: usize,
75 pub total_memory: usize,
77 pub average_size: f64,
79 pub risk_score: u32,
81 pub operation_types: Vec<String>,
83 pub unsafe_operations: Vec<String>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct RiskAssessment {
90 pub memory_safety: CategoryRisk,
92 pub type_safety: CategoryRisk,
94 pub concurrency_safety: CategoryRisk,
96 pub data_integrity: CategoryRisk,
98 pub risk_distribution: HashMap<RiskLevel, usize>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct CategoryRisk {
105 pub level: RiskLevel,
107 pub issue_count: usize,
109 pub description: String,
111 pub recommendations: Vec<String>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct FfiCallGraph {
118 pub nodes: Vec<CallGraphNode>,
120 pub edges: Vec<CallGraphEdge>,
122 pub statistics: GraphStatistics,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct CallGraphNode {
129 pub id: String,
131 pub name: String,
133 pub node_type: NodeType,
135 pub allocation_count: usize,
137 pub memory_usage: usize,
139 pub risk_level: RiskLevel,
141 pub position: (f64, f64),
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct CallGraphEdge {
148 pub source: String,
150 pub target: String,
152 pub call_count: usize,
154 pub memory_transferred: usize,
156 pub edge_type: EdgeType,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct GraphStatistics {
163 pub node_count: usize,
165 pub edge_count: usize,
167 pub ffi_boundaries: usize,
169 pub max_depth: usize,
171 pub connected_components: usize,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177pub enum UnsafeOperationType {
178 RawPointerDeref,
180 UninitializedMemory,
182 BufferOverflow,
184 UseAfterFree,
186 DoubleFree,
188 FfiBoundary,
190 UnsafeCast,
192 UnsafeConcurrency,
194 MemoryLayoutAssumption,
196 Other(String),
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
202pub enum RiskLevel {
203 Low,
205 Medium,
207 High,
209 Critical,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
215pub enum NodeType {
216 RustFunction,
218 FfiFunction,
220 ExternalLibrary,
222 SystemCall,
224 Unknown,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub enum EdgeType {
231 SafeCall,
233 UnsafeCall,
235 FfiBoundary,
237 MemoryTransfer,
239}
240
241pub struct FfiSafetyAnalyzer {
243 unsafe_operations: Vec<UnsafeOperation>,
245 hotspots: HashMap<String, FfiHotspotTracker>,
247 call_graph_builder: CallGraphBuilder,
249 risk_tracker: RiskTracker,
251}
252
253#[derive(Debug, Clone)]
255struct FfiHotspotTracker {
256 call_count: usize,
257 total_memory: usize,
258 sizes: Vec<usize>,
259 operation_types: Vec<String>,
260 unsafe_operations: Vec<String>,
261 risk_factors: Vec<String>,
262}
263
264#[derive(Debug, Clone)]
266struct CallGraphBuilder {
267 nodes: HashMap<String, CallGraphNode>,
268 edges: HashMap<(String, String), CallGraphEdge>,
269 node_counter: usize,
270}
271
272#[derive(Debug, Clone)]
274struct RiskTracker {
275 memory_safety_issues: usize,
276 type_safety_issues: usize,
277 concurrency_issues: usize,
278 data_integrity_issues: usize,
279}
280
281impl FfiSafetyAnalyzer {
282 pub fn new() -> Self {
284 Self {
285 unsafe_operations: Vec::new(),
286 hotspots: HashMap::new(),
287 call_graph_builder: CallGraphBuilder::new(),
288 risk_tracker: RiskTracker::new(),
289 }
290 }
291
292 pub fn analyze_allocations(
294 allocations: &[AllocationInfo],
295 ) -> Result<FfiSafetyAnalysis, BinaryExportError> {
296 let mut analyzer = Self::new();
297
298 for allocation in allocations {
300 analyzer.process_allocation(allocation)?;
301 }
302
303 analyzer.generate_analysis(allocations.len())
305 }
306
307 fn process_allocation(&mut self, allocation: &AllocationInfo) -> Result<(), BinaryExportError> {
309 if let Some(ref stack_trace) = allocation.stack_trace {
311 self.analyze_stack_trace(allocation, stack_trace)?;
312 }
313
314 if let Some(ref type_name) = allocation.type_name {
316 self.analyze_type_safety(allocation, type_name)?;
317 }
318
319 if let Some(ref scope_name) = allocation.scope_name {
321 self.analyze_scope_safety(allocation, scope_name)?;
322 }
323
324 self.analyze_memory_safety(allocation)?;
326
327 Ok(())
328 }
329
330 fn analyze_stack_trace(
332 &mut self,
333 allocation: &AllocationInfo,
334 stack_trace: &[String],
335 ) -> Result<(), BinaryExportError> {
336 for (index, frame) in stack_trace.iter().enumerate() {
337 if self.is_ffi_boundary(frame) {
339 self.record_ffi_operation(allocation, frame, index)?;
340 }
341
342 if self.is_unsafe_operation(frame) {
344 self.record_unsafe_operation(allocation, frame, stack_trace)?;
345 }
346
347 self.call_graph_builder.add_call_frame(frame, allocation)?;
349 }
350
351 Ok(())
352 }
353
354 fn analyze_type_safety(
356 &mut self,
357 allocation: &AllocationInfo,
358 type_name: &str,
359 ) -> Result<(), BinaryExportError> {
360 if self.is_raw_pointer_type(type_name) {
362 let operation = UnsafeOperation {
363 id: format!("raw_ptr_{}", allocation.ptr),
364 operation_type: UnsafeOperationType::RawPointerDeref,
365 memory_address: allocation.ptr,
366 memory_size: allocation.size,
367 location: type_name.to_string(),
368 stack_trace: allocation.stack_trace.clone().unwrap_or_default(),
369 risk_level: RiskLevel::High,
370 description: format!("Raw pointer type detected: {type_name}"),
371 timestamp: allocation.timestamp_alloc,
372 };
373 self.unsafe_operations.push(operation);
374 self.risk_tracker.type_safety_issues += 1;
375 }
376
377 if self.is_ffi_type(type_name) {
379 self.record_ffi_type_usage(allocation, type_name)?;
380 }
381
382 Ok(())
383 }
384
385 fn analyze_scope_safety(
387 &mut self,
388 allocation: &AllocationInfo,
389 scope_name: &str,
390 ) -> Result<(), BinaryExportError> {
391 if scope_name.contains("unsafe") {
392 let operation = UnsafeOperation {
393 id: format!("unsafe_scope_{}", allocation.ptr),
394 operation_type: UnsafeOperationType::FfiBoundary,
395 memory_address: allocation.ptr,
396 memory_size: allocation.size,
397 location: scope_name.to_string(),
398 stack_trace: allocation.stack_trace.clone().unwrap_or_default(),
399 risk_level: RiskLevel::Medium,
400 description: format!("Allocation in unsafe scope: {scope_name}"),
401 timestamp: allocation.timestamp_alloc,
402 };
403 self.unsafe_operations.push(operation);
404 self.risk_tracker.memory_safety_issues += 1;
405 }
406
407 Ok(())
408 }
409
410 fn analyze_memory_safety(
412 &mut self,
413 allocation: &AllocationInfo,
414 ) -> Result<(), BinaryExportError> {
415 if allocation.is_leaked {
417 let operation = UnsafeOperation {
418 id: format!("leak_{}", allocation.ptr),
419 operation_type: UnsafeOperationType::UseAfterFree,
420 memory_address: allocation.ptr,
421 memory_size: allocation.size,
422 location: allocation
423 .scope_name
424 .clone()
425 .unwrap_or_else(|| "unknown".to_string()),
426 stack_trace: allocation.stack_trace.clone().unwrap_or_default(),
427 risk_level: RiskLevel::High,
428 description: "Potential memory leak detected".to_string(),
429 timestamp: allocation.timestamp_alloc,
430 };
431 self.unsafe_operations.push(operation);
432 self.risk_tracker.memory_safety_issues += 1;
433 }
434
435 if allocation.size > 1024 * 1024 {
437 let operation = UnsafeOperation {
439 id: format!("large_alloc_{}", allocation.ptr),
440 operation_type: UnsafeOperationType::BufferOverflow,
441 memory_address: allocation.ptr,
442 memory_size: allocation.size,
443 location: allocation
444 .scope_name
445 .clone()
446 .unwrap_or_else(|| "unknown".to_string()),
447 stack_trace: allocation.stack_trace.clone().unwrap_or_default(),
448 risk_level: RiskLevel::Medium,
449 description: format!(
450 "Large allocation detected: {size} bytes",
451 size = allocation.size
452 ),
453 timestamp: allocation.timestamp_alloc,
454 };
455 self.unsafe_operations.push(operation);
456 self.risk_tracker.data_integrity_issues += 1;
457 }
458
459 Ok(())
460 }
461
462 fn is_ffi_boundary(&self, frame: &str) -> bool {
464 frame.contains("::ffi::")
465 || frame.contains("extern \"C\"")
466 || frame.contains("libc::")
467 || frame.contains("_c_")
468 || frame.contains("sys::")
469 || frame.ends_with("_sys")
470 }
471
472 fn is_unsafe_operation(&self, frame: &str) -> bool {
474 frame.contains("unsafe")
475 || frame.contains("transmute")
476 || frame.contains("from_raw")
477 || frame.contains("as_ptr")
478 || frame.contains("as_mut_ptr")
479 }
480
481 fn is_raw_pointer_type(&self, type_name: &str) -> bool {
483 type_name.starts_with("*const")
484 || type_name.starts_with("*mut")
485 || type_name.contains("NonNull")
486 || type_name.contains("RawPtr")
487 }
488
489 fn is_ffi_type(&self, type_name: &str) -> bool {
491 type_name.starts_with("c_")
492 || type_name.contains("CString")
493 || type_name.contains("CStr")
494 || type_name.contains("OsString")
495 || type_name.contains("PathBuf")
496 || type_name.contains("libc::")
497 }
498
499 fn record_ffi_operation(
501 &mut self,
502 allocation: &AllocationInfo,
503 frame: &str,
504 _index: usize,
505 ) -> Result<(), BinaryExportError> {
506 let location = self.extract_function_name(frame);
507
508 let tracker = self
509 .hotspots
510 .entry(location.clone())
511 .or_insert(FfiHotspotTracker {
512 call_count: 0,
513 total_memory: 0,
514 sizes: Vec::new(),
515 operation_types: Vec::new(),
516 unsafe_operations: Vec::new(),
517 risk_factors: Vec::new(),
518 });
519
520 tracker.call_count += 1;
521 tracker.total_memory += allocation.size;
522 tracker.sizes.push(allocation.size);
523 tracker.operation_types.push("ffi_call".to_string());
524
525 Ok(())
526 }
527
528 fn record_unsafe_operation(
530 &mut self,
531 allocation: &AllocationInfo,
532 frame: &str,
533 stack_trace: &[String],
534 ) -> Result<(), BinaryExportError> {
535 let operation = UnsafeOperation {
536 id: format!("unsafe_{}", allocation.ptr),
537 operation_type: self.classify_unsafe_operation(frame),
538 memory_address: allocation.ptr,
539 memory_size: allocation.size,
540 location: self.extract_function_name(frame),
541 stack_trace: stack_trace.to_vec(),
542 risk_level: self.assess_risk_level(frame),
543 description: format!("Unsafe operation detected in: {frame}"),
544 timestamp: allocation.timestamp_alloc,
545 };
546
547 self.unsafe_operations.push(operation);
548 self.risk_tracker.memory_safety_issues += 1;
549
550 Ok(())
551 }
552
553 fn record_ffi_type_usage(
555 &mut self,
556 allocation: &AllocationInfo,
557 type_name: &str,
558 ) -> Result<(), BinaryExportError> {
559 let location = format!("ffi_type_{type_name}");
560
561 let tracker = self.hotspots.entry(location).or_insert(FfiHotspotTracker {
562 call_count: 0,
563 total_memory: 0,
564 sizes: Vec::new(),
565 operation_types: Vec::new(),
566 unsafe_operations: Vec::new(),
567 risk_factors: Vec::new(),
568 });
569
570 tracker.call_count += 1;
571 tracker.total_memory += allocation.size;
572 tracker.sizes.push(allocation.size);
573 tracker.operation_types.push("ffi_type".to_string());
574
575 Ok(())
576 }
577
578 fn extract_function_name(&self, frame: &str) -> String {
580 if let Some(start) = frame.find("::") {
582 if let Some(end) = frame[start + 2..].find("::") {
583 frame[start + 2..start + 2 + end].to_string()
584 } else {
585 frame[start + 2..]
586 .split_whitespace()
587 .next()
588 .unwrap_or("unknown")
589 .to_string()
590 }
591 } else {
592 frame
593 .split_whitespace()
594 .next()
595 .unwrap_or("unknown")
596 .to_string()
597 }
598 }
599
600 fn classify_unsafe_operation(&self, frame: &str) -> UnsafeOperationType {
602 if frame.contains("transmute") {
603 UnsafeOperationType::UnsafeCast
604 } else if frame.contains("from_raw") {
605 UnsafeOperationType::RawPointerDeref
606 } else if frame.contains("ffi") {
607 UnsafeOperationType::FfiBoundary
608 } else {
609 UnsafeOperationType::Other("unknown_unsafe".to_string())
610 }
611 }
612
613 fn assess_risk_level(&self, frame: &str) -> RiskLevel {
615 if frame.contains("transmute") || frame.contains("from_raw") {
616 RiskLevel::High
617 } else if frame.contains("unsafe") {
618 RiskLevel::Medium
619 } else {
620 RiskLevel::Low
621 }
622 }
623
624 fn generate_analysis(
626 &mut self,
627 total_allocations: usize,
628 ) -> Result<FfiSafetyAnalysis, BinaryExportError> {
629 let ffi_allocations_count = self.hotspots.values().map(|h| h.call_count).sum();
631 let ffi_memory_usage = self.hotspots.values().map(|h| h.total_memory).sum();
632 let ffi_memory_percentage = if total_allocations > 0 {
633 (ffi_allocations_count as f64 / total_allocations as f64) * 100.0
634 } else {
635 0.0
636 };
637
638 let safety_score = self.calculate_safety_score();
639 let risk_level = self.determine_overall_risk_level();
640
641 let summary = FfiSafetySummary {
642 total_allocations,
643 unsafe_operations_count: self.unsafe_operations.len(),
644 ffi_allocations_count,
645 safety_score,
646 risk_level,
647 ffi_memory_usage,
648 ffi_memory_percentage,
649 };
650
651 let ffi_hotspots = self.generate_hotspots()?;
653
654 let risk_assessment = self.generate_risk_assessment()?;
656
657 let call_graph = self.call_graph_builder.build_graph()?;
659
660 Ok(FfiSafetyAnalysis {
661 summary,
662 unsafe_operations: self.unsafe_operations.clone(),
663 ffi_hotspots,
664 risk_assessment,
665 call_graph,
666 })
667 }
668
669 fn calculate_safety_score(&self) -> u32 {
671 let base_score = 100u32;
672 let unsafe_penalty = (self.unsafe_operations.len() as u32).min(50);
673 let risk_penalty = match self.determine_overall_risk_level() {
674 RiskLevel::Low => 0,
675 RiskLevel::Medium => 10,
676 RiskLevel::High => 25,
677 RiskLevel::Critical => 50,
678 };
679
680 base_score.saturating_sub(unsafe_penalty + risk_penalty)
681 }
682
683 fn determine_overall_risk_level(&self) -> RiskLevel {
685 let critical_count = self
686 .unsafe_operations
687 .iter()
688 .filter(|op| op.risk_level == RiskLevel::Critical)
689 .count();
690 let high_count = self
691 .unsafe_operations
692 .iter()
693 .filter(|op| op.risk_level == RiskLevel::High)
694 .count();
695
696 if critical_count > 0 {
697 RiskLevel::Critical
698 } else if high_count > 3 {
699 RiskLevel::High
700 } else if high_count > 0 || self.unsafe_operations.len() > 5 {
701 RiskLevel::Medium
702 } else {
703 RiskLevel::Low
704 }
705 }
706
707 fn generate_hotspots(&self) -> Result<Vec<FfiHotspot>, BinaryExportError> {
709 let mut hotspots = Vec::new();
710
711 for (name, tracker) in &self.hotspots {
712 let average_size = if tracker.call_count > 0 {
713 tracker.total_memory as f64 / tracker.call_count as f64
714 } else {
715 0.0
716 };
717
718 let risk_score = self.calculate_hotspot_risk_score(tracker);
719
720 let hotspot = FfiHotspot {
721 name: name.clone(),
722 call_count: tracker.call_count,
723 total_memory: tracker.total_memory,
724 average_size,
725 risk_score,
726 operation_types: tracker.operation_types.clone(),
727 unsafe_operations: tracker.unsafe_operations.clone(),
728 };
729
730 hotspots.push(hotspot);
731 }
732
733 hotspots.sort_by(|a, b| b.risk_score.cmp(&a.risk_score));
735
736 Ok(hotspots)
737 }
738
739 fn calculate_hotspot_risk_score(&self, tracker: &FfiHotspotTracker) -> u32 {
741 let mut score = 0u32;
742
743 score += (tracker.call_count as u32).min(50);
745
746 if tracker.total_memory > 1024 * 1024 {
748 score += 20;
749 }
750
751 score += (tracker.risk_factors.len() as u32 * 10).min(30);
753
754 score.min(100)
755 }
756
757 fn generate_risk_assessment(&self) -> Result<RiskAssessment, BinaryExportError> {
759 let memory_safety = CategoryRisk {
760 level: if self.risk_tracker.memory_safety_issues > 5 {
761 RiskLevel::High
762 } else {
763 RiskLevel::Medium
764 },
765 issue_count: self.risk_tracker.memory_safety_issues,
766 description: "Memory safety issues detected in FFI operations".to_string(),
767 recommendations: vec![
768 "Review unsafe memory operations".to_string(),
769 "Add bounds checking".to_string(),
770 "Use safe wrapper types".to_string(),
771 ],
772 };
773
774 let type_safety = CategoryRisk {
775 level: if self.risk_tracker.type_safety_issues > 3 {
776 RiskLevel::High
777 } else {
778 RiskLevel::Low
779 },
780 issue_count: self.risk_tracker.type_safety_issues,
781 description: "Type safety concerns in FFI boundaries".to_string(),
782 recommendations: vec![
783 "Validate type conversions".to_string(),
784 "Use newtype wrappers".to_string(),
785 ],
786 };
787
788 let concurrency_safety = CategoryRisk {
789 level: RiskLevel::Low,
790 issue_count: self.risk_tracker.concurrency_issues,
791 description: "Concurrency safety analysis".to_string(),
792 recommendations: vec!["Review thread safety".to_string()],
793 };
794
795 let data_integrity = CategoryRisk {
796 level: if self.risk_tracker.data_integrity_issues > 2 {
797 RiskLevel::Medium
798 } else {
799 RiskLevel::Low
800 },
801 issue_count: self.risk_tracker.data_integrity_issues,
802 description: "Data integrity concerns".to_string(),
803 recommendations: vec!["Validate data consistency".to_string()],
804 };
805
806 let mut risk_distribution = HashMap::new();
808 for operation in &self.unsafe_operations {
809 *risk_distribution
810 .entry(operation.risk_level.clone())
811 .or_insert(0) += 1;
812 }
813
814 Ok(RiskAssessment {
815 memory_safety,
816 type_safety,
817 concurrency_safety,
818 data_integrity,
819 risk_distribution,
820 })
821 }
822}
823
824impl CallGraphBuilder {
825 fn new() -> Self {
826 Self {
827 nodes: HashMap::new(),
828 edges: HashMap::new(),
829 node_counter: 0,
830 }
831 }
832
833 fn add_call_frame(
834 &mut self,
835 frame: &str,
836 allocation: &AllocationInfo,
837 ) -> Result<(), BinaryExportError> {
838 let node_id = format!("node_{}", self.node_counter);
839 self.node_counter += 1;
840
841 let node = CallGraphNode {
842 id: node_id.clone(),
843 name: frame.to_string(),
844 node_type: self.classify_node_type(frame),
845 allocation_count: 1,
846 memory_usage: allocation.size,
847 risk_level: RiskLevel::Low,
848 position: (0.0, 0.0), };
850
851 self.nodes.insert(node_id, node);
852 Ok(())
853 }
854
855 fn classify_node_type(&self, frame: &str) -> NodeType {
856 if frame.contains("::ffi::") || frame.contains("extern \"C\"") {
857 NodeType::FfiFunction
858 } else if frame.contains("libc::") || frame.contains("sys::") {
859 NodeType::ExternalLibrary
860 } else {
861 NodeType::RustFunction
862 }
863 }
864
865 fn build_graph(&self) -> Result<FfiCallGraph, BinaryExportError> {
866 let nodes: Vec<CallGraphNode> = self.nodes.values().cloned().collect();
867 let edges: Vec<CallGraphEdge> = self.edges.values().cloned().collect();
868
869 let statistics = GraphStatistics {
870 node_count: nodes.len(),
871 edge_count: edges.len(),
872 ffi_boundaries: edges
873 .iter()
874 .filter(|e| e.edge_type == EdgeType::FfiBoundary)
875 .count(),
876 max_depth: 0, connected_components: 1, };
879
880 Ok(FfiCallGraph {
881 nodes,
882 edges,
883 statistics,
884 })
885 }
886}
887
888impl RiskTracker {
889 fn new() -> Self {
890 Self {
891 memory_safety_issues: 0,
892 type_safety_issues: 0,
893 concurrency_issues: 0,
894 data_integrity_issues: 0,
895 }
896 }
897}
898
899impl Default for FfiSafetyAnalyzer {
900 fn default() -> Self {
901 Self::new()
902 }
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908
909 fn create_test_allocation_with_stack_trace(
910 ptr: usize,
911 size: usize,
912 stack_trace: Vec<String>,
913 ) -> AllocationInfo {
914 AllocationInfo {
915 ptr,
916 size,
917 var_name: Some("test_var".to_string()),
918 type_name: Some("TestType".to_string()),
919 scope_name: Some("test_scope".to_string()),
920 timestamp_alloc: 1000,
921 timestamp_dealloc: None,
922 thread_id: "main".to_string(),
923 borrow_count: 0,
924 stack_trace: Some(stack_trace),
925 is_leaked: false,
926 lifetime_ms: None,
927 borrow_info: None,
928 clone_info: None,
929 ownership_history_available: false,
930 smart_pointer_info: None,
931 memory_layout: None,
932 generic_info: None,
933 dynamic_type_info: None,
934 runtime_state: None,
935 stack_allocation: None,
936 temporary_object: None,
937 fragmentation_analysis: None,
938 generic_instantiation: None,
939 type_relationships: None,
940 type_usage: None,
941 function_call_tracking: None,
942 lifecycle_tracking: None,
943 access_tracking: None,
944 drop_chain_analysis: None,
945 }
946 }
947
948 #[test]
949 fn test_ffi_boundary_detection() {
950 let analyzer = FfiSafetyAnalyzer::new();
951
952 assert!(analyzer.is_ffi_boundary("module::ffi::function"));
953 assert!(analyzer.is_ffi_boundary("extern \"C\" fn test"));
954 assert!(analyzer.is_ffi_boundary("libc::malloc"));
955 assert!(!analyzer.is_ffi_boundary("std::vec::Vec::new"));
956 }
957
958 #[test]
959 fn test_unsafe_operation_detection() {
960 let analyzer = FfiSafetyAnalyzer::new();
961
962 assert!(analyzer.is_unsafe_operation("unsafe { transmute(ptr) }"));
963 assert!(analyzer.is_unsafe_operation("Vec::from_raw_parts"));
964 assert!(analyzer.is_unsafe_operation("slice.as_mut_ptr()"));
965 assert!(!analyzer.is_unsafe_operation("Vec::new()"));
966 }
967
968 #[test]
969 fn test_raw_pointer_type_detection() {
970 let analyzer = FfiSafetyAnalyzer::new();
971
972 assert!(analyzer.is_raw_pointer_type("*const u8"));
973 assert!(analyzer.is_raw_pointer_type("*mut i32"));
974 assert!(analyzer.is_raw_pointer_type("NonNull<T>"));
975 assert!(!analyzer.is_raw_pointer_type("Box<T>"));
976 assert!(!analyzer.is_raw_pointer_type("&mut T"));
977 }
978
979 #[test]
980 fn test_ffi_type_detection() {
981 let analyzer = FfiSafetyAnalyzer::new();
982
983 assert!(analyzer.is_ffi_type("c_int"));
984 assert!(analyzer.is_ffi_type("CString"));
985 assert!(analyzer.is_ffi_type("CStr"));
986 assert!(analyzer.is_ffi_type("libc::size_t"));
987 assert!(!analyzer.is_ffi_type("String"));
988 assert!(!analyzer.is_ffi_type("Vec<u8>"));
989 }
990
991 #[test]
992 fn test_ffi_safety_analysis() {
993 let allocations = vec![
994 create_test_allocation_with_stack_trace(
995 0x1000,
996 1024,
997 vec![
998 "main::test".to_string(),
999 "module::ffi::unsafe_function".to_string(),
1000 "libc::malloc".to_string(),
1001 ],
1002 ),
1003 create_test_allocation_with_stack_trace(
1004 0x2000,
1005 2048,
1006 vec![
1007 "safe_function".to_string(),
1008 "std::vec::Vec::new".to_string(),
1009 ],
1010 ),
1011 ];
1012
1013 let analysis =
1014 FfiSafetyAnalyzer::analyze_allocations(&allocations).expect("Failed to get test value");
1015
1016 assert_eq!(analysis.summary.total_allocations, 2);
1017 assert!(analysis.summary.unsafe_operations_count > 0);
1018 assert!(analysis.summary.ffi_allocations_count > 0);
1019 assert!(analysis.summary.safety_score <= 100);
1020 assert!(!analysis.ffi_hotspots.is_empty());
1021 }
1022
1023 #[test]
1024 fn test_risk_level_assessment() {
1025 let analyzer = FfiSafetyAnalyzer::new();
1026
1027 assert_eq!(analyzer.assess_risk_level("transmute"), RiskLevel::High);
1028 assert_eq!(analyzer.assess_risk_level("from_raw"), RiskLevel::High);
1029 assert_eq!(
1030 analyzer.assess_risk_level("unsafe block"),
1031 RiskLevel::Medium
1032 );
1033 assert_eq!(analyzer.assess_risk_level("safe_function"), RiskLevel::Low);
1034 }
1035
1036 #[test]
1037 fn test_function_name_extraction() {
1038 let analyzer = FfiSafetyAnalyzer::new();
1039
1040 assert_eq!(
1041 analyzer.extract_function_name("module::submodule::function"),
1042 "submodule"
1043 );
1044 assert_eq!(analyzer.extract_function_name("std::vec::Vec::new"), "vec");
1045 assert_eq!(
1046 analyzer.extract_function_name("simple_function"),
1047 "simple_function"
1048 );
1049 }
1050
1051 #[test]
1052 fn test_safety_score_calculation() {
1053 let mut analyzer = FfiSafetyAnalyzer::new();
1054
1055 analyzer.unsafe_operations.push(UnsafeOperation {
1057 id: "test1".to_string(),
1058 operation_type: UnsafeOperationType::RawPointerDeref,
1059 memory_address: 0x1000,
1060 memory_size: 1024,
1061 location: "test".to_string(),
1062 stack_trace: vec![],
1063 risk_level: RiskLevel::High,
1064 description: "Test".to_string(),
1065 timestamp: 1000,
1066 });
1067
1068 let score = analyzer.calculate_safety_score();
1069 assert!(score < 100);
1070 }
1071}