use crate::analysis::detectors::{
DetectionResult, DetectionStatistics, Detector, DetectorConfig, DetectorError, Issue,
IssueCategory, IssueSeverity,
};
use crate::capture::types::AllocationInfo;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct SafetyDetectorConfig {
pub enable_unsafe_detection: bool,
pub enable_ffi_detection: bool,
pub enable_static_mut_detection: bool,
pub enable_raw_pointer_detection: bool,
pub enable_data_race_detection: bool,
}
impl Default for SafetyDetectorConfig {
fn default() -> Self {
Self {
enable_unsafe_detection: true,
enable_ffi_detection: true,
enable_static_mut_detection: true,
enable_raw_pointer_detection: true,
enable_data_race_detection: true,
}
}
}
#[derive(Debug)]
pub struct SafetyDetector {
config: SafetyDetectorConfig,
base_config: DetectorConfig,
}
impl SafetyDetector {
pub fn new(config: SafetyDetectorConfig) -> Self {
Self {
config,
base_config: DetectorConfig::default(),
}
}
pub fn safety_config(&self) -> &SafetyDetectorConfig {
&self.config
}
pub fn update_safety_config(&mut self, config: SafetyDetectorConfig) {
self.config = config;
}
}
impl Detector for SafetyDetector {
fn name(&self) -> &str {
"SafetyDetector"
}
fn version(&self) -> &str {
"1.0.0"
}
fn detect(&self, allocations: &[AllocationInfo]) -> DetectionResult {
let start_time = std::time::Instant::now();
let mut statistics = DetectionStatistics::new();
statistics.total_allocations = allocations.len();
let mut issues = Vec::new();
if self.config.enable_unsafe_detection {
let unsafe_issues = self.detect_unsafe_patterns(allocations, &mut statistics);
issues.extend(unsafe_issues);
}
if self.config.enable_data_race_detection {
let race_issues = self.detect_data_races(allocations, &mut statistics);
issues.extend(race_issues);
}
let detection_time_ms = start_time.elapsed().as_millis() as u64;
DetectionResult {
detector_name: self.name().to_string(),
issues,
statistics,
detection_time_ms,
}
}
fn config(&self) -> &DetectorConfig {
&self.base_config
}
fn update_config(&mut self, config: DetectorConfig) -> Result<(), DetectorError> {
self.base_config = config;
Ok(())
}
}
impl SafetyDetector {
fn detect_unsafe_patterns(
&self,
allocations: &[AllocationInfo],
statistics: &mut DetectionStatistics,
) -> Vec<Issue> {
let mut issues = Vec::new();
for (index, alloc) in allocations.iter().enumerate() {
if self.config.enable_raw_pointer_detection && self.is_raw_pointer_type(alloc) {
let issue_id = format!("unsafe_raw_pointer_{}", index);
let severity = self.assess_unsafe_severity(alloc);
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Raw pointer usage detected at 0x{:x}: type {}",
alloc.ptr,
alloc.type_name.as_deref().unwrap_or("unknown")
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Consider using safe alternatives (Box, Rc, Arc, references)".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if self.config.enable_ffi_detection && self.is_ffi_related(alloc) {
let issue_id = format!("unsafe_ffi_{}", index);
let severity = IssueSeverity::High;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"FFI-related allocation detected at 0x{:x}: {}",
alloc.ptr,
alloc.type_name.as_deref().unwrap_or("unknown")
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Ensure proper FFI safety: use libc types, validate inputs, manage memory correctly".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if self.config.enable_static_mut_detection && self.is_static_mutable(alloc) {
let issue_id = format!("unsafe_static_mut_{}", index);
let severity = IssueSeverity::Critical;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Static mutable allocation detected at 0x{:x}",
alloc.ptr
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Consider using atomic types, Mutex, or lazy_static for thread-safe static data".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if self.is_unsafe_allocation(alloc) {
let issue_id = format!("unsafe_allocation_{}", index);
let severity = IssueSeverity::Medium;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!("Unsafe allocation pattern detected at 0x{:x}", alloc.ptr),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Review unsafe code and consider safe alternatives".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
issues
}
fn detect_data_races(
&self,
allocations: &[AllocationInfo],
statistics: &mut DetectionStatistics,
) -> Vec<Issue> {
let mut issues = Vec::new();
let mut thread_allocations: HashMap<std::thread::ThreadId, Vec<&AllocationInfo>> =
HashMap::new();
for alloc in allocations {
thread_allocations
.entry(alloc.thread_id)
.or_default()
.push(alloc);
}
for (index, alloc) in allocations.iter().enumerate() {
if self.has_concurrent_access_pattern(alloc) {
let issue_id = format!("data_race_{}", index);
let severity = self.assess_data_race_severity(alloc);
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Potential data race detected at 0x{:x}: accessed by multiple threads",
alloc.ptr
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Use proper synchronization: Mutex, RwLock, atomic types, or channels"
.to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if alloc.borrow_count > 0
&& thread_allocations.len() > 1
&& self.has_unsynchronized_borrows(alloc)
{
let issue_id = format!("unsynchronized_borrow_{}", index);
let severity = IssueSeverity::Critical;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Unsynchronized mutable borrow detected at 0x{:x}: {} active borrows in multi-threaded context",
alloc.ptr, alloc.borrow_count
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Use Mutex, RwLock, or Arc for thread-safe shared mutable state".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if let Some(clone_info) = &alloc.clone_info {
if clone_info.clone_count > 0
&& thread_allocations.len() > 1
&& !self.is_thread_safe_type(alloc)
{
let issue_id = format!("unsafe_clone_{}", index);
let severity = IssueSeverity::High;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Unsafe clone across threads detected at 0x{:x}: {} clones in multi-threaded context",
alloc.ptr, clone_info.clone_count
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Use Arc for thread-safe reference counting".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
issues
}
fn assess_unsafe_severity(&self, alloc: &AllocationInfo) -> IssueSeverity {
if self.is_static_mutable(alloc) {
return IssueSeverity::Critical;
}
if self.is_ffi_related(alloc) {
return IssueSeverity::High;
}
IssueSeverity::Medium
}
fn assess_data_race_severity(&self, alloc: &AllocationInfo) -> IssueSeverity {
if alloc.borrow_count > 1 {
return IssueSeverity::Critical;
}
if let Some(access_tracking) = &alloc.access_tracking {
let write_count = access_tracking
.access_events
.iter()
.filter(|e| {
matches!(
e.access_type,
crate::capture::types::MemoryAccessType::Write
)
})
.count();
if write_count > 0 {
return IssueSeverity::High;
}
}
IssueSeverity::Medium
}
fn is_raw_pointer_type(&self, alloc: &AllocationInfo) -> bool {
alloc
.type_name
.as_ref()
.map(|t| t.contains("*const") || t.contains("*mut"))
.unwrap_or(false)
}
fn is_ffi_related(&self, alloc: &AllocationInfo) -> bool {
alloc
.type_name
.as_ref()
.map(|t| {
t.contains("c_")
|| t.contains("libc")
|| t.contains("extern")
|| t.contains("CStr")
|| t.contains("CString")
})
.unwrap_or(false)
}
fn is_static_mutable(&self, alloc: &AllocationInfo) -> bool {
alloc
.type_name
.as_ref()
.map(|t| t.contains("static") && t.contains("mut"))
.unwrap_or(false)
}
fn is_unsafe_allocation(&self, alloc: &AllocationInfo) -> bool {
alloc
.scope_name
.as_ref()
.map(|s| s.contains("unsafe"))
.unwrap_or(false)
}
fn has_concurrent_access_pattern(&self, alloc: &AllocationInfo) -> bool {
if let Some(access_tracking) = &alloc.access_tracking {
let unique_functions: std::collections::HashSet<&str> = access_tracking
.access_events
.iter()
.map(|e| e.function_name.as_str())
.collect();
return unique_functions.len() > 1;
}
false
}
fn has_unsynchronized_borrows(&self, alloc: &AllocationInfo) -> bool {
if let Some(borrow_info) = &alloc.borrow_info {
return borrow_info.mutable_borrows > 1
|| (borrow_info.mutable_borrows > 0 && borrow_info.immutable_borrows > 0);
}
false
}
fn is_thread_safe_type(&self, alloc: &AllocationInfo) -> bool {
alloc
.type_name
.as_ref()
.map(|t| {
t.contains("Arc")
|| t.contains("Mutex")
|| t.contains("RwLock")
|| t.contains("Atomic")
|| t.contains("atomic")
})
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capture::types::AllocationInfo;
use std::thread;
#[test]
fn test_safety_detector_creation() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
assert_eq!(detector.name(), "SafetyDetector");
assert_eq!(detector.version(), "1.0.0");
}
#[test]
fn test_safety_detector_config() {
let config = SafetyDetectorConfig {
enable_unsafe_detection: false,
enable_ffi_detection: false,
enable_static_mut_detection: false,
enable_raw_pointer_detection: false,
enable_data_race_detection: false,
};
let detector = SafetyDetector::new(config);
let safety_config = detector.safety_config();
assert!(!safety_config.enable_unsafe_detection);
assert!(!safety_config.enable_ffi_detection);
assert!(!safety_config.enable_static_mut_detection);
assert!(!safety_config.enable_raw_pointer_detection);
assert!(!safety_config.enable_data_race_detection);
}
#[test]
fn test_safety_detector_detect() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let allocations = vec![
AllocationInfo::new(0x1000, 1024),
AllocationInfo::new(0x2000, 2048),
];
let result = detector.detect(&allocations);
assert_eq!(result.detector_name, "SafetyDetector");
assert_eq!(result.statistics.total_allocations, 2);
}
#[test]
fn test_detect_raw_pointer() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].type_name = Some("*mut i32".to_string());
let issues = detector.detect_unsafe_patterns(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Raw pointer usage")));
}
#[test]
fn test_detect_ffi_related() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].type_name = Some("libc::c_void".to_string());
let issues = detector.detect_unsafe_patterns(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues.iter().any(|i| i.description.contains("FFI-related")));
}
#[test]
fn test_detect_static_mut() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].type_name = Some("static mut Vec<i32>".to_string());
let issues = detector.detect_unsafe_patterns(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Static mutable")));
assert!(issues.iter().any(|i| i.severity == IssueSeverity::Critical));
}
#[test]
fn test_detect_data_race() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].borrow_count = 2;
use crate::capture::types::{
AddressRange, BandwidthUtilization, CacheAccessInfo, CacheLatencyBreakdown,
LocalityMetrics, MemoryAccessEvent, MemoryAccessPerformanceImpact,
MemoryAccessTrackingInfo, MemoryAccessType,
};
allocations[0].access_tracking = Some(MemoryAccessTrackingInfo {
region_id: 0,
address_range: AddressRange {
start_address: 0x1000,
end_address: 0x1000 + 1024,
size: 1024,
},
access_events: vec![
MemoryAccessEvent {
access_type: MemoryAccessType::Write,
timestamp: 100,
address: 0x1000,
size: 4,
function_name: "thread1".to_string(),
latency_ns: 100,
cache_info: CacheAccessInfo {
l1_hit: true,
l2_hit: false,
l3_hit: false,
memory_access: false,
latency_breakdown: CacheLatencyBreakdown {
l1_latency_ns: 1.0,
l2_latency_ns: 5.0,
l3_latency_ns: 20.0,
memory_latency_ns: 100.0,
},
},
},
MemoryAccessEvent {
access_type: MemoryAccessType::Write,
timestamp: 200,
address: 0x1000,
size: 4,
function_name: "thread2".to_string(),
latency_ns: 100,
cache_info: CacheAccessInfo {
l1_hit: true,
l2_hit: false,
l3_hit: false,
memory_access: false,
latency_breakdown: CacheLatencyBreakdown {
l1_latency_ns: 1.0,
l2_latency_ns: 5.0,
l3_latency_ns: 20.0,
memory_latency_ns: 100.0,
},
},
},
],
access_statistics: crate::capture::types::MemoryAccessStatistics {
total_reads: 0,
total_writes: 2,
read_write_ratio: 0.0,
avg_access_frequency: 10.0,
peak_access_frequency: 20.0,
locality_metrics: LocalityMetrics {
temporal_locality: 0.8,
spatial_locality: 0.9,
sequential_access_percent: 95.0,
random_access_percent: 5.0,
stride_patterns: vec![],
},
bandwidth_utilization: BandwidthUtilization {
peak_bandwidth: 100.0,
avg_bandwidth: 50.0,
efficiency_percent: 80.0,
bottlenecks: vec![],
},
},
access_patterns: vec![],
performance_impact: MemoryAccessPerformanceImpact {
performance_score: 0.9,
cache_efficiency_impact: 0.1,
bandwidth_impact: 0.05,
pipeline_impact: 0.03,
optimization_recommendations: vec![],
},
});
let issues = detector.detect_data_races(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Potential data race")));
}
#[test]
fn test_is_raw_pointer_type() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut alloc = AllocationInfo::new(0x1000, 1024);
alloc.type_name = Some("*const i32".to_string());
assert!(detector.is_raw_pointer_type(&alloc));
alloc.type_name = Some("*mut u8".to_string());
assert!(detector.is_raw_pointer_type(&alloc));
alloc.type_name = Some("Box<i32>".to_string());
assert!(!detector.is_raw_pointer_type(&alloc));
}
#[test]
fn test_is_ffi_related() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut alloc = AllocationInfo::new(0x1000, 1024);
alloc.type_name = Some("libc::c_void".to_string());
assert!(detector.is_ffi_related(&alloc));
alloc.type_name = Some("CString".to_string());
assert!(detector.is_ffi_related(&alloc));
alloc.type_name = Some("String".to_string());
assert!(!detector.is_ffi_related(&alloc));
}
#[test]
fn test_is_thread_safe_type() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut alloc = AllocationInfo::new(0x1000, 1024);
alloc.type_name = Some("Arc<i32>".to_string());
assert!(detector.is_thread_safe_type(&alloc));
alloc.type_name = Some("Mutex<Vec<i32>>".to_string());
assert!(detector.is_thread_safe_type(&alloc));
alloc.type_name = Some("Rc<i32>".to_string());
assert!(!detector.is_thread_safe_type(&alloc));
}
#[test]
fn test_safety_detector_disabled() {
let config = SafetyDetectorConfig {
enable_unsafe_detection: false,
enable_ffi_detection: false,
enable_static_mut_detection: false,
enable_raw_pointer_detection: false,
enable_data_race_detection: false,
};
let detector = SafetyDetector::new(config);
let allocations = vec![AllocationInfo::new(0x1000, 1024)];
let result = detector.detect(&allocations);
assert_eq!(result.issues.len(), 0);
}
#[test]
fn test_detect_unsafe_clone() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let _thread1_id = thread::current().id();
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].type_name = Some("Rc<i32>".to_string()); allocations[0].clone_info = Some(crate::capture::types::CloneInfo {
clone_count: 3,
is_clone: true,
original_ptr: Some(0x1000),
_source: None,
_confidence: None,
});
let _thread2_alloc = AllocationInfo::new(0x2000, 1024);
let _issues = detector.detect_data_races(&allocations, &mut DetectionStatistics::new());
assert!(!detector.is_thread_safe_type(&allocations[0]));
}
#[test]
fn test_assess_unsafe_severity() {
let config = SafetyDetectorConfig::default();
let detector = SafetyDetector::new(config);
let mut alloc = AllocationInfo::new(0x1000, 1024);
alloc.type_name = Some("static mut Vec<i32>".to_string());
assert_eq!(
detector.assess_unsafe_severity(&alloc),
IssueSeverity::Critical
);
alloc.type_name = Some("libc::c_void".to_string());
assert_eq!(detector.assess_unsafe_severity(&alloc), IssueSeverity::High);
alloc.type_name = Some("*mut i32".to_string());
assert_eq!(
detector.assess_unsafe_severity(&alloc),
IssueSeverity::Medium
);
}
}