use crate::analysis::detectors::{
DetectionResult, DetectionStatistics, Detector, DetectorConfig, DetectorError, Issue,
IssueCategory, IssueSeverity,
};
use crate::capture::types::AllocationInfo;
#[derive(Debug, Clone)]
pub struct OverflowDetectorConfig {
pub enable_heap_overflow_detection: bool,
pub enable_stack_overflow_detection: bool,
pub enable_integer_overflow_detection: bool,
pub min_buffer_size_bytes: usize,
pub max_array_size: usize,
}
impl Default for OverflowDetectorConfig {
fn default() -> Self {
Self {
enable_heap_overflow_detection: true,
enable_stack_overflow_detection: true,
enable_integer_overflow_detection: true,
min_buffer_size_bytes: 64,
max_array_size: 1024 * 1024, }
}
}
#[derive(Debug)]
pub struct OverflowDetector {
config: OverflowDetectorConfig,
base_config: DetectorConfig,
}
impl OverflowDetector {
pub fn new(config: OverflowDetectorConfig) -> Self {
Self {
config,
base_config: DetectorConfig::default(),
}
}
pub fn overflow_config(&self) -> &OverflowDetectorConfig {
&self.config
}
pub fn update_overflow_config(&mut self, config: OverflowDetectorConfig) {
self.config = config;
}
}
impl Detector for OverflowDetector {
fn name(&self) -> &str {
"OverflowDetector"
}
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_heap_overflow_detection {
let heap_issues = self.detect_buffer_overflow(allocations, &mut statistics);
issues.extend(heap_issues);
}
if self.config.enable_stack_overflow_detection {
let stack_issues = self.detect_stack_overflow(allocations, &mut statistics);
issues.extend(stack_issues);
}
if self.config.enable_integer_overflow_detection {
let int_issues = self.detect_integer_overflow(allocations, &mut statistics);
issues.extend(int_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 OverflowDetector {
fn detect_buffer_overflow(
&self,
allocations: &[AllocationInfo],
statistics: &mut DetectionStatistics,
) -> Vec<Issue> {
let mut issues = Vec::new();
for (index, alloc) in allocations.iter().enumerate() {
if alloc.size < self.config.min_buffer_size_bytes {
continue;
}
if self.is_buffer_type(alloc) {
if let Some(access_tracking) = &alloc.access_tracking {
for event in &access_tracking.access_events {
if event.address < alloc.ptr || event.address >= alloc.ptr + alloc.size {
let issue_id = format!("buffer_overflow_{}_{}", index, event.timestamp);
let severity = self.assess_overflow_severity(alloc, event);
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Buffer overflow detected at 0x{:x}: access at 0x{:x} exceeds buffer bounds [0x{:x}-0x{:x}]",
alloc.ptr, event.address, alloc.ptr, alloc.ptr + alloc.size
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Use bounds-checked access methods (get, get_mut) or validate indices before access".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
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 > 100 && write_count > access_tracking.access_events.len() / 2 {
let issue_id = format!("suspicious_write_pattern_{}", index);
let severity = IssueSeverity::Medium;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Suspicious write pattern detected at 0x{:x}: {}/{} writes",
alloc.ptr,
write_count,
access_tracking.access_events.len()
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Review write operations for potential buffer overflow".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
if alloc.size > self.config.max_array_size {
let issue_id = format!("large_array_{}", index);
let severity = IssueSeverity::High;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Large array detected at 0x{:x}: {} bytes (max: {} bytes)",
alloc.ptr, alloc.size, self.config.max_array_size
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Consider heap allocation (Vec, Box) instead of stack allocation"
.to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
issues
}
fn detect_stack_overflow(
&self,
allocations: &[AllocationInfo],
statistics: &mut DetectionStatistics,
) -> Vec<Issue> {
let mut issues = Vec::new();
for (index, alloc) in allocations.iter().enumerate() {
if self.is_stack_allocation(alloc) {
if alloc.size > 8 * 1024 {
let issue_id = format!("large_stack_allocation_{}", index);
let severity = self.assess_stack_overflow_severity(alloc);
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Large stack allocation detected at 0x{:x}: {} bytes",
alloc.ptr, alloc.size
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Consider heap allocation (Box, Vec) to avoid stack overflow".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if let Some(lifecycle) = &alloc.lifecycle_tracking {
if lifecycle.lifecycle_events.len() > 100 {
let issue_id = format!("deep_recursion_{}", index);
let severity = IssueSeverity::High;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Deep recursion pattern detected: {} lifecycle events",
lifecycle.lifecycle_events.len()
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Consider iterative approach or increase stack size".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
}
issues
}
fn detect_integer_overflow(
&self,
allocations: &[AllocationInfo],
statistics: &mut DetectionStatistics,
) -> Vec<Issue> {
let mut issues = Vec::new();
for (index, alloc) in allocations.iter().enumerate() {
if let Some(type_name) = &alloc.type_name {
if self.is_integer_type(type_name) {
if alloc.size > usize::MAX / 2 {
let issue_id = format!("integer_overflow_risk_{}", index);
let severity = IssueSeverity::Critical;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Integer overflow risk detected: allocation size {} bytes exceeds safe range",
alloc.size
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Use checked arithmetic methods (checked_add, checked_mul) or saturating operations".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
if alloc.size > usize::MAX - 1024 {
let issue_id = format!("near_max_allocation_{}", index);
let severity = IssueSeverity::Critical;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!("Allocation near usize::MAX detected: {} bytes", alloc.size),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Reduce allocation size or use checked arithmetic".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
if let Some(clone_info) = &alloc.clone_info {
if clone_info.clone_count > 0
&& alloc.size * clone_info.clone_count > self.config.max_array_size
{
let issue_id = format!("clone_overflow_risk_{}", index);
let severity = IssueSeverity::High;
let issue = Issue::new(
issue_id,
severity,
IssueCategory::Safety,
format!(
"Clone operation overflow risk: {} clones of {} bytes = {} bytes",
clone_info.clone_count,
alloc.size,
alloc.size * clone_info.clone_count
),
)
.with_allocation_ptr(alloc.ptr)
.with_suggested_fix(
"Validate clone counts or use Arc for shared ownership".to_string(),
);
issues.push(issue);
statistics.allocations_with_issues += 1;
}
}
}
issues
}
fn assess_overflow_severity(
&self,
alloc: &AllocationInfo,
event: &crate::capture::types::MemoryAccessEvent,
) -> IssueSeverity {
let start_distance = alloc.ptr.saturating_sub(event.address);
let end_distance = event.address.saturating_sub(alloc.ptr + alloc.size);
if start_distance > 1024 || end_distance > 1024 {
return IssueSeverity::Critical;
}
if matches!(
event.access_type,
crate::capture::types::MemoryAccessType::Write
) {
return IssueSeverity::High;
}
IssueSeverity::Medium
}
fn assess_stack_overflow_severity(&self, alloc: &AllocationInfo) -> IssueSeverity {
if alloc.size > 1024 * 1024 {
return IssueSeverity::Critical;
}
if alloc.size > 256 * 1024 {
return IssueSeverity::High;
}
IssueSeverity::Medium
}
fn is_buffer_type(&self, alloc: &AllocationInfo) -> bool {
alloc
.type_name
.as_ref()
.map(|t| {
t.contains('[') || t.contains("Vec") || t.contains("slice") || t.contains("array")
})
.unwrap_or(false)
}
fn is_stack_allocation(&self, alloc: &AllocationInfo) -> bool {
alloc.stack_allocation.is_some()
}
fn is_integer_type(&self, type_name: &str) -> bool {
type_name.contains("i8")
|| type_name.contains("i16")
|| type_name.contains("i32")
|| type_name.contains("i64")
|| type_name.contains("i128")
|| type_name.contains("isize")
|| type_name.contains("u8")
|| type_name.contains("u16")
|| type_name.contains("u32")
|| type_name.contains("u64")
|| type_name.contains("u128")
|| type_name.contains("usize")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capture::types::AllocationInfo;
use crate::capture::types::LocalityMetrics;
#[test]
fn test_overflow_detector_creation() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
assert_eq!(detector.name(), "OverflowDetector");
assert_eq!(detector.version(), "1.0.0");
}
#[test]
fn test_overflow_detector_config() {
let config = OverflowDetectorConfig {
enable_heap_overflow_detection: false,
enable_stack_overflow_detection: false,
enable_integer_overflow_detection: false,
min_buffer_size_bytes: 128,
max_array_size: 2048,
};
let detector = OverflowDetector::new(config);
let overflow_config = detector.overflow_config();
assert!(!overflow_config.enable_heap_overflow_detection);
assert!(!overflow_config.enable_stack_overflow_detection);
assert!(!overflow_config.enable_integer_overflow_detection);
assert_eq!(overflow_config.min_buffer_size_bytes, 128);
assert_eq!(overflow_config.max_array_size, 2048);
}
#[test]
fn test_overflow_detector_detect() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let allocations = vec![
AllocationInfo::new(0x1000, 1024),
AllocationInfo::new(0x2000, 2048),
];
let result = detector.detect(&allocations);
assert_eq!(result.detector_name, "OverflowDetector");
assert_eq!(result.statistics.total_allocations, 2);
}
#[test]
fn test_detect_buffer_overflow() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 1024)];
allocations[0].type_name = Some("Vec<i32>".to_string());
use crate::capture::types::{
AddressRange, CacheAccessInfo, CacheLatencyBreakdown, 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::Read,
timestamp: 100,
address: 0x1000, size: 4,
function_name: "test".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: 0x2000, size: 4,
function_name: "test".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: 1,
total_writes: 1,
read_write_ratio: 1.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: crate::capture::types::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_buffer_overflow(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Buffer overflow detected")));
}
#[test]
fn test_detect_large_stack_allocation() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 16 * 1024)]; allocations[0].type_name = Some("[u8; 16384]".to_string());
use crate::capture::types::{ScopeType, StackAllocationInfo, StackScopeInfo};
allocations[0].stack_allocation = Some(StackAllocationInfo {
frame_id: 0,
var_name: "buffer".to_string(),
stack_offset: 1024,
size: 16 * 1024,
function_name: "test_function".to_string(),
stack_depth: 1,
scope_info: StackScopeInfo {
scope_type: ScopeType::Function,
start_line: Some(10),
end_line: Some(50),
parent_scope: None,
nesting_level: 0,
},
});
let issues = detector.detect_stack_overflow(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Large stack allocation")));
}
#[test]
fn test_detect_integer_overflow_risk() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, usize::MAX / 2 + 1)];
allocations[0].type_name = Some("Vec<i32>".to_string());
let issues =
detector.detect_integer_overflow(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Integer overflow risk")));
}
#[test]
fn test_is_buffer_type() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut alloc = AllocationInfo::new(0x1000, 1024);
alloc.type_name = Some("Vec<i32>".to_string());
assert!(detector.is_buffer_type(&alloc));
alloc.type_name = Some("[u8; 1024]".to_string());
assert!(detector.is_buffer_type(&alloc));
alloc.type_name = Some("Box<i32>".to_string());
assert!(!detector.is_buffer_type(&alloc));
}
#[test]
fn test_is_integer_type() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
assert!(detector.is_integer_type("i32"));
assert!(detector.is_integer_type("u64"));
assert!(detector.is_integer_type("usize"));
assert!(!detector.is_integer_type("f32"));
assert!(!detector.is_integer_type("String"));
}
#[test]
fn test_overflow_assessment_severity() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let alloc = AllocationInfo::new(0x1000, 1024);
use crate::capture::types::{
CacheAccessInfo, CacheLatencyBreakdown, MemoryAccessEvent, MemoryAccessType,
};
let event = MemoryAccessEvent {
access_type: MemoryAccessType::Write,
timestamp: 100,
address: 0x3000, size: 4,
function_name: "test".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,
},
},
};
let severity = detector.assess_overflow_severity(&alloc, &event);
assert_eq!(severity, IssueSeverity::Critical);
}
#[test]
fn test_overflow_detector_disabled() {
let config = OverflowDetectorConfig {
enable_heap_overflow_detection: false,
enable_stack_overflow_detection: false,
enable_integer_overflow_detection: false,
min_buffer_size_bytes: 64,
max_array_size: 1024 * 1024,
};
let detector = OverflowDetector::new(config);
let allocations = vec![AllocationInfo::new(0x1000, 1024)];
let result = detector.detect(&allocations);
assert_eq!(result.issues.len(), 0);
}
#[test]
fn test_detect_large_array() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 2 * 1024 * 1024)]; allocations[0].type_name = Some("Vec<i32>".to_string());
let issues = detector.detect_buffer_overflow(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Large array detected")));
}
#[test]
fn test_clone_overflow_risk() {
let config = OverflowDetectorConfig::default();
let detector = OverflowDetector::new(config);
let mut allocations = vec![AllocationInfo::new(0x1000, 512 * 1024)]; allocations[0].type_name = Some("Vec<i32>".to_string());
use crate::capture::types::CloneInfo;
allocations[0].clone_info = Some(CloneInfo {
clone_count: 3, is_clone: true,
original_ptr: Some(0x1000),
});
let issues =
detector.detect_integer_overflow(&allocations, &mut DetectionStatistics::new());
assert!(!issues.is_empty());
assert!(issues
.iter()
.any(|i| i.description.contains("Clone operation overflow risk")));
}
}