memscope_rs/export/binary/
ffi_safety_analyzer.rs

1//! FFI safety analysis for binary to HTML conversion
2//!
3//! This module provides comprehensive analysis of Foreign Function Interface (FFI) safety
4//! based on allocation data, identifying unsafe operations, risk assessment, and hotspot detection.
5
6use crate::core::types::AllocationInfo;
7use crate::export::binary::error::BinaryExportError;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// FFI safety analysis results for dashboard display
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FfiSafetyAnalysis {
14    /// Summary statistics for FFI safety
15    pub summary: FfiSafetySummary,
16    /// Unsafe operations detected
17    pub unsafe_operations: Vec<UnsafeOperation>,
18    /// FFI call relationships and hotspots
19    pub ffi_hotspots: Vec<FfiHotspot>,
20    /// Risk assessment by category
21    pub risk_assessment: RiskAssessment,
22    /// FFI call graph data for visualization
23    pub call_graph: FfiCallGraph,
24}
25
26/// Summary statistics for FFI safety analysis
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct FfiSafetySummary {
29    /// Total number of allocations analyzed
30    pub total_allocations: usize,
31    /// Number of potentially unsafe operations
32    pub unsafe_operations_count: usize,
33    /// Number of FFI-related allocations
34    pub ffi_allocations_count: usize,
35    /// Overall safety score (0-100, higher is safer)
36    pub safety_score: u32,
37    /// Risk level assessment
38    pub risk_level: RiskLevel,
39    /// Memory used by FFI operations
40    pub ffi_memory_usage: usize,
41    /// Percentage of total memory used by FFI
42    pub ffi_memory_percentage: f64,
43}
44
45/// Information about an unsafe operation
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct UnsafeOperation {
48    /// Unique identifier for the operation
49    pub id: String,
50    /// Type of unsafe operation
51    pub operation_type: UnsafeOperationType,
52    /// Memory address involved
53    pub memory_address: usize,
54    /// Size of memory involved
55    pub memory_size: usize,
56    /// Function or scope where operation occurred
57    pub location: String,
58    /// Stack trace if available
59    pub stack_trace: Vec<String>,
60    /// Risk level of this operation
61    pub risk_level: RiskLevel,
62    /// Description of the potential issue
63    pub description: String,
64    /// Timestamp when detected
65    pub timestamp: u64,
66}
67
68/// FFI hotspot information
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct FfiHotspot {
71    /// Function or module name
72    pub name: String,
73    /// Number of FFI calls from this location
74    pub call_count: usize,
75    /// Total memory allocated through FFI calls
76    pub total_memory: usize,
77    /// Average allocation size
78    pub average_size: f64,
79    /// Risk score for this hotspot (0-100)
80    pub risk_score: u32,
81    /// Types of operations performed
82    pub operation_types: Vec<String>,
83    /// Related unsafe operations
84    pub unsafe_operations: Vec<String>,
85}
86
87/// Risk assessment by different categories
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct RiskAssessment {
90    /// Memory safety risks
91    pub memory_safety: CategoryRisk,
92    /// Type safety risks
93    pub type_safety: CategoryRisk,
94    /// Concurrency safety risks
95    pub concurrency_safety: CategoryRisk,
96    /// Data integrity risks
97    pub data_integrity: CategoryRisk,
98    /// Overall risk distribution
99    pub risk_distribution: HashMap<RiskLevel, usize>,
100}
101
102/// Risk information for a specific category
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct CategoryRisk {
105    /// Risk level for this category
106    pub level: RiskLevel,
107    /// Number of issues in this category
108    pub issue_count: usize,
109    /// Description of main risks
110    pub description: String,
111    /// Recommended actions
112    pub recommendations: Vec<String>,
113}
114
115/// FFI call graph for visualization
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct FfiCallGraph {
118    /// Nodes in the call graph
119    pub nodes: Vec<CallGraphNode>,
120    /// Edges representing call relationships
121    pub edges: Vec<CallGraphEdge>,
122    /// Graph statistics
123    pub statistics: GraphStatistics,
124}
125
126/// Node in the FFI call graph
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct CallGraphNode {
129    /// Unique node identifier
130    pub id: String,
131    /// Function or module name
132    pub name: String,
133    /// Node type (rust_function, ffi_function, external_library)
134    pub node_type: NodeType,
135    /// Number of allocations from this node
136    pub allocation_count: usize,
137    /// Total memory allocated
138    pub memory_usage: usize,
139    /// Risk level of this node
140    pub risk_level: RiskLevel,
141    /// Position for graph layout (x, y coordinates)
142    pub position: (f64, f64),
143}
144
145/// Edge in the FFI call graph
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct CallGraphEdge {
148    /// Source node ID
149    pub source: String,
150    /// Target node ID
151    pub target: String,
152    /// Number of calls between nodes
153    pub call_count: usize,
154    /// Total memory transferred
155    pub memory_transferred: usize,
156    /// Edge type (safe_call, unsafe_call, ffi_boundary)
157    pub edge_type: EdgeType,
158}
159
160/// Statistics for the call graph
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct GraphStatistics {
163    /// Total number of nodes
164    pub node_count: usize,
165    /// Total number of edges
166    pub edge_count: usize,
167    /// Number of FFI boundary crossings
168    pub ffi_boundaries: usize,
169    /// Maximum call depth
170    pub max_depth: usize,
171    /// Number of strongly connected components
172    pub connected_components: usize,
173}
174
175/// Type of unsafe operation
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177pub enum UnsafeOperationType {
178    /// Raw pointer dereference
179    RawPointerDeref,
180    /// Uninitialized memory access
181    UninitializedMemory,
182    /// Buffer overflow potential
183    BufferOverflow,
184    /// Use after free
185    UseAfterFree,
186    /// Double free
187    DoubleFree,
188    /// FFI boundary crossing
189    FfiBoundary,
190    /// Unsafe type casting
191    UnsafeCast,
192    /// Concurrent access without synchronization
193    UnsafeConcurrency,
194    /// Memory layout assumptions
195    MemoryLayoutAssumption,
196    /// Other unsafe operation
197    Other(String),
198}
199
200/// Risk level enumeration
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
202pub enum RiskLevel {
203    /// Low risk - generally safe operations
204    Low,
205    /// Medium risk - requires attention
206    Medium,
207    /// High risk - potentially dangerous
208    High,
209    /// Critical risk - immediate attention required
210    Critical,
211}
212
213/// Type of node in the call graph
214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
215pub enum NodeType {
216    /// Rust function
217    RustFunction,
218    /// FFI function
219    FfiFunction,
220    /// External library
221    ExternalLibrary,
222    /// System call
223    SystemCall,
224    /// Unknown type
225    Unknown,
226}
227
228/// Type of edge in the call graph
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub enum EdgeType {
231    /// Safe function call
232    SafeCall,
233    /// Unsafe function call
234    UnsafeCall,
235    /// FFI boundary crossing
236    FfiBoundary,
237    /// Memory transfer
238    MemoryTransfer,
239}
240
241/// FFI safety analyzer for processing allocation data
242pub struct FfiSafetyAnalyzer {
243    /// Detected unsafe operations
244    unsafe_operations: Vec<UnsafeOperation>,
245    /// FFI hotspots tracker
246    hotspots: HashMap<String, FfiHotspotTracker>,
247    /// Call graph builder
248    call_graph_builder: CallGraphBuilder,
249    /// Risk assessment tracker
250    risk_tracker: RiskTracker,
251}
252
253/// Internal tracker for FFI hotspots
254#[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/// Internal call graph builder
265#[derive(Debug, Clone)]
266struct CallGraphBuilder {
267    nodes: HashMap<String, CallGraphNode>,
268    edges: HashMap<(String, String), CallGraphEdge>,
269    node_counter: usize,
270}
271
272/// Internal risk tracker
273#[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    /// Create a new FFI safety analyzer
283    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    /// Analyze FFI safety from allocation data
293    pub fn analyze_allocations(
294        allocations: &[AllocationInfo],
295    ) -> Result<FfiSafetyAnalysis, BinaryExportError> {
296        let mut analyzer = Self::new();
297
298        // Process each allocation for FFI safety analysis
299        for allocation in allocations {
300            analyzer.process_allocation(allocation)?;
301        }
302
303        // Generate analysis results
304        analyzer.generate_analysis(allocations.len())
305    }
306
307    /// Process a single allocation for FFI safety analysis
308    fn process_allocation(&mut self, allocation: &AllocationInfo) -> Result<(), BinaryExportError> {
309        // Analyze stack trace for FFI patterns
310        if let Some(ref stack_trace) = allocation.stack_trace {
311            self.analyze_stack_trace(allocation, stack_trace)?;
312        }
313
314        // Analyze type name for FFI indicators
315        if let Some(ref type_name) = allocation.type_name {
316            self.analyze_type_safety(allocation, type_name)?;
317        }
318
319        // Analyze scope for unsafe operations
320        if let Some(ref scope_name) = allocation.scope_name {
321            self.analyze_scope_safety(allocation, scope_name)?;
322        }
323
324        // Check for memory safety issues
325        self.analyze_memory_safety(allocation)?;
326
327        Ok(())
328    }
329
330    /// Analyze stack trace for FFI patterns and unsafe operations
331    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            // Detect FFI boundary crossings
338            if self.is_ffi_boundary(frame) {
339                self.record_ffi_operation(allocation, frame, index)?;
340            }
341
342            // Detect unsafe operations
343            if self.is_unsafe_operation(frame) {
344                self.record_unsafe_operation(allocation, frame, stack_trace)?;
345            }
346
347            // Build call graph
348            self.call_graph_builder.add_call_frame(frame, allocation)?;
349        }
350
351        Ok(())
352    }
353
354    /// Analyze type name for safety implications
355    fn analyze_type_safety(
356        &mut self,
357        allocation: &AllocationInfo,
358        type_name: &str,
359    ) -> Result<(), BinaryExportError> {
360        // Check for raw pointer types
361        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        // Check for FFI-related types
378        if self.is_ffi_type(type_name) {
379            self.record_ffi_type_usage(allocation, type_name)?;
380        }
381
382        Ok(())
383    }
384
385    /// Analyze scope for unsafe operations
386    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    /// Analyze memory safety patterns
411    fn analyze_memory_safety(
412        &mut self,
413        allocation: &AllocationInfo,
414    ) -> Result<(), BinaryExportError> {
415        // Check for potential use-after-free
416        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        // Check for large allocations (potential buffer overflow risk)
436        if allocation.size > 1024 * 1024 {
437            // Allocations larger than 1MB
438            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    /// Check if a stack frame indicates FFI boundary
463    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    /// Check if a stack frame indicates unsafe operation
473    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    /// Check if a type is a raw pointer type
482    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    /// Check if a type is FFI-related
490    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    /// Record FFI operation
500    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    /// Record unsafe operation
529    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    /// Record FFI type usage
554    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    /// Extract function name from stack frame
579    fn extract_function_name(&self, frame: &str) -> String {
580        // Simple extraction - in real implementation, this would be more sophisticated
581        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    /// Classify the type of unsafe operation
601    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    /// Assess risk level based on operation
614    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    /// Generate the final analysis results
625    fn generate_analysis(
626        &mut self,
627        total_allocations: usize,
628    ) -> Result<FfiSafetyAnalysis, BinaryExportError> {
629        // Calculate summary statistics
630        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        // Generate hotspots
652        let ffi_hotspots = self.generate_hotspots()?;
653
654        // Generate risk assessment
655        let risk_assessment = self.generate_risk_assessment()?;
656
657        // Generate call graph
658        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    /// Calculate overall safety score (0-100)
670    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    /// Determine overall risk level
684    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    /// Generate hotspots from tracked data
708    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        // Sort by risk score (descending)
734        hotspots.sort_by(|a, b| b.risk_score.cmp(&a.risk_score));
735
736        Ok(hotspots)
737    }
738
739    /// Calculate risk score for a hotspot
740    fn calculate_hotspot_risk_score(&self, tracker: &FfiHotspotTracker) -> u32 {
741        let mut score = 0u32;
742
743        // Base score from call count
744        score += (tracker.call_count as u32).min(50);
745
746        // Memory usage factor
747        if tracker.total_memory > 1024 * 1024 {
748            score += 20;
749        }
750
751        // Risk factors
752        score += (tracker.risk_factors.len() as u32 * 10).min(30);
753
754        score.min(100)
755    }
756
757    /// Generate risk assessment
758    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        // Count risk distribution
807        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), // Will be calculated during layout
849        };
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,            // Would be calculated in real implementation
877            connected_components: 1, // Simplified
878        };
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        // Add some unsafe operations
1056        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}