Skip to main content

scirs2_core/memory/
safety.rs

1//! # Memory Safety Hardening for Production Environments
2//!
3//! This module provides comprehensive memory safety features including bounds checking,
4//! overflow protection, safe arithmetic operations, and resource management for production use.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use std::alloc::{GlobalAlloc, Layout, System};
8use std::collections::HashMap;
9use std::fmt;
10use std::sync::atomic::{AtomicUsize, Ordering};
11use std::sync::{Arc, Mutex, RwLock};
12use std::time::{Duration, Instant};
13
14/// Memory allocation tracking and safety features
15pub struct SafetyTracker {
16    /// Current memory usage
17    current_usage: AtomicUsize,
18    /// Peak memory usage
19    peak_usage: AtomicUsize,
20    /// Memory limit in bytes
21    memory_limit: AtomicUsize,
22    /// Allocation tracking
23    allocations: Arc<Mutex<HashMap<usize, AllocationInfo>>>,
24    /// Safety configuration
25    config: Arc<RwLock<SafetyConfig>>,
26}
27
28/// Information about a memory allocation
29#[derive(Debug, Clone)]
30pub struct AllocationInfo {
31    /// Size of the allocation
32    pub size: usize,
33    /// Timestamp when allocated
34    pub timestamp: Instant,
35    /// Stack trace (if enabled)
36    pub stack_trace: Option<Vec<String>>,
37    /// Allocation source location
38    pub location: Option<String>,
39}
40
41/// Configuration for memory safety features
42#[derive(Debug, Clone)]
43pub struct SafetyConfig {
44    /// Maximum memory usage allowed (in bytes)
45    pub max_memory: usize,
46    /// Whether to track stack traces
47    pub track_stack_traces: bool,
48    /// Whether to enable bounds checking
49    pub enable_bounds_checking: bool,
50    /// Whether to enable arithmetic overflow checking
51    pub enable_overflow_checking: bool,
52    /// Maximum allocation size
53    pub max_allocation_size: usize,
54    /// Whether to zero memory on deallocation
55    pub zero_on_dealloc: bool,
56    /// Memory pressure threshold (0.0 to 1.0)
57    pub memory_pressure_threshold: f64,
58}
59
60impl Default for SafetyConfig {
61    fn default() -> Self {
62        Self {
63            max_memory: 1024 * 1024 * 1024, // 1GB default
64            track_stack_traces: false,      // Disabled by default for performance
65            enable_bounds_checking: true,
66            enable_overflow_checking: true,
67            max_allocation_size: 256 * 1024 * 1024, // 256MB max single allocation
68            zero_on_dealloc: true,
69            memory_pressure_threshold: 0.8,
70        }
71    }
72}
73
74impl SafetyTracker {
75    /// Create a new safety tracker with default configuration
76    pub fn new() -> Self {
77        Self {
78            current_usage: AtomicUsize::new(0),
79            peak_usage: AtomicUsize::new(0),
80            memory_limit: AtomicUsize::new(SafetyConfig::default().max_memory),
81            allocations: Arc::new(Mutex::new(HashMap::new())),
82            config: Arc::new(RwLock::new(SafetyConfig::default())),
83        }
84    }
85
86    /// Create a safety tracker with custom configuration
87    pub fn with_config(config: SafetyConfig) -> Self {
88        let max_memory = config.max_memory;
89        Self {
90            current_usage: AtomicUsize::new(0),
91            peak_usage: AtomicUsize::new(0),
92            memory_limit: AtomicUsize::new(max_memory),
93            allocations: Arc::new(Mutex::new(HashMap::new())),
94            config: Arc::new(RwLock::new(config)),
95        }
96    }
97
98    /// Track a memory allocation
99    pub fn track_allocation(
100        &self,
101        ptr: *mut u8,
102        size: usize,
103        location: Option<String>,
104    ) -> CoreResult<()> {
105        let current = self.current_usage.fetch_add(size, Ordering::SeqCst) + size;
106
107        // Update peak usage
108        let mut peak = self.peak_usage.load(Ordering::SeqCst);
109        while peak < current {
110            match self.peak_usage.compare_exchange_weak(
111                peak,
112                current,
113                Ordering::SeqCst,
114                Ordering::Relaxed,
115            ) {
116                Ok(_) => break,
117                Err(new_peak) => peak = new_peak,
118            }
119        }
120
121        // Check memory limit
122        let limit = self.memory_limit.load(Ordering::SeqCst);
123        if current > limit {
124            self.current_usage.fetch_sub(size, Ordering::SeqCst);
125            return Err(CoreError::MemoryError(ErrorContext::new(format!(
126                "Memory allocation would exceed limit: current={}, limit={}, requested={}",
127                current - size,
128                limit,
129                size
130            ))));
131        }
132
133        // Check memory pressure
134        let config = self.config.read().map_err(|_| {
135            CoreError::ComputationError(ErrorContext::new("Failed to acquire config lock"))
136        })?;
137
138        let pressure = current as f64 / limit as f64;
139        if pressure > config.memory_pressure_threshold {
140            eprintln!("Warning: Memory pressure high: {:.1}%", pressure * 100.0);
141        }
142
143        // Track allocation details
144        if let Ok(mut allocations) = self.allocations.lock() {
145            let info = AllocationInfo {
146                size,
147                timestamp: Instant::now(),
148                stack_trace: if config.track_stack_traces {
149                    Some(self.capture_stack_trace())
150                } else {
151                    None
152                },
153                location,
154            };
155            allocations.insert(ptr as usize, info);
156        }
157
158        Ok(())
159    }
160
161    /// Track a memory deallocation
162    ///
163    /// # Safety
164    ///
165    /// The caller must ensure that `ptr` is a valid pointer that was previously
166    /// allocated and is safe to dereference for writing zeros if configured.
167    pub unsafe fn track_deallocation(&self, ptr: *mut u8, size: usize) {
168        self.current_usage.fetch_sub(size, Ordering::SeqCst);
169
170        if let Ok(mut allocations) = self.allocations.lock() {
171            allocations.remove(&(ptr as usize));
172        }
173
174        // Zero memory if configured
175        if let Ok(config) = self.config.read() {
176            if config.zero_on_dealloc {
177                unsafe {
178                    std::ptr::write_bytes(ptr, 0, size);
179                }
180            }
181        }
182    }
183
184    /// Get current memory usage
185    pub fn current_usage(&self) -> usize {
186        self.current_usage.load(Ordering::SeqCst)
187    }
188
189    /// Get peak memory usage
190    pub fn peak_usage(&self) -> usize {
191        self.peak_usage.load(Ordering::SeqCst)
192    }
193
194    /// Get memory usage ratio (0.0 to 1.0)
195    pub fn memory_pressure(&self) -> f64 {
196        let current = self.current_usage() as f64;
197        let limit = self.memory_limit.load(Ordering::SeqCst) as f64;
198        current / limit
199    }
200
201    /// Check if allocation would be safe
202    pub fn check_allocation(&self, size: usize) -> CoreResult<()> {
203        let config = self.config.read().map_err(|_| {
204            CoreError::ComputationError(ErrorContext::new("Failed to acquire config lock"))
205        })?;
206
207        // Check maximum allocation size
208        if size > config.max_allocation_size {
209            return Err(CoreError::MemoryError(ErrorContext::new(format!(
210                "Allocation size {} exceeds maximum allowed size {}",
211                size, config.max_allocation_size
212            ))));
213        }
214
215        // Check if allocation would exceed memory limit
216        let current = self.current_usage();
217        let limit = self.memory_limit.load(Ordering::SeqCst);
218        if current + size > limit {
219            return Err(CoreError::MemoryError(ErrorContext::new(format!(
220                "Allocation would exceed memory limit: current={current}, limit={limit}, requested={size}"
221            ))));
222        }
223
224        Ok(())
225    }
226
227    /// Capture stack trace for debugging
228    fn capture_stack_trace(&self) -> Vec<String> {
229        // Simplified stack trace capture
230        // In a real implementation, you might use backtrace crate
231        vec!["Stack trace capture not implemented".to_string()]
232    }
233
234    /// Get allocation statistics
235    pub fn get_allocation_stats(&self) -> AllocationStats {
236        let allocations = self.allocations.lock().expect("Operation failed");
237        let total_allocations = allocations.len();
238        let total_size: usize = allocations.values().map(|info| info.size).sum();
239        let average_size = if total_allocations > 0 {
240            total_size / total_allocations
241        } else {
242            0
243        };
244
245        let oldest_allocation = allocations
246            .values()
247            .min_by_key(|info| info.timestamp)
248            .map(|info| info.timestamp.elapsed())
249            .unwrap_or(Duration::ZERO);
250
251        AllocationStats {
252            current_usage: self.current_usage(),
253            peak_usage: self.peak_usage(),
254            memory_pressure: self.memory_pressure(),
255            total_allocations,
256            average_allocation_size: average_size,
257            oldest_allocation_age: oldest_allocation,
258        }
259    }
260}
261
262impl Default for SafetyTracker {
263    fn default() -> Self {
264        Self::new()
265    }
266}
267
268/// Statistics about memory allocations
269#[derive(Debug, Clone)]
270pub struct AllocationStats {
271    /// Current memory usage in bytes
272    pub current_usage: usize,
273    /// Peak memory usage in bytes
274    pub peak_usage: usize,
275    /// Memory pressure ratio (0.0 to 1.0)
276    pub memory_pressure: f64,
277    /// Total number of active allocations
278    pub total_allocations: usize,
279    /// Average allocation size
280    pub average_allocation_size: usize,
281    /// Age of oldest allocation
282    pub oldest_allocation_age: Duration,
283}
284
285impl fmt::Display for AllocationStats {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        writeln!(f, "Memory Allocation Statistics:")?;
288        writeln!(f, "  Current usage: {} bytes", self.current_usage)?;
289        writeln!(f, "  Peak usage: {} bytes", self.peak_usage)?;
290        writeln!(f, "  Memory pressure: {:.1}%", self.memory_pressure * 100.0)?;
291        writeln!(f, "  Active allocations: {}", self.total_allocations)?;
292        writeln!(
293            f,
294            "  Average allocation size: {} bytes",
295            self.average_allocation_size
296        )?;
297        writeln!(
298            f,
299            "  Oldest allocation age: {:?}",
300            self.oldest_allocation_age
301        )?;
302        Ok(())
303    }
304}
305
306/// Safe arithmetic operations that check for overflow
307pub struct SafeArithmetic;
308
309impl SafeArithmetic {
310    /// Safe addition with overflow checking
311    pub fn safe_add<T>(a: T, b: T) -> CoreResult<T>
312    where
313        T: num_traits::CheckedAdd + fmt::Display + Copy,
314    {
315        a.checked_add(&b).ok_or_else(|| {
316            CoreError::ComputationError(ErrorContext::new(format!(
317                "Arithmetic overflow in addition: {a} + {b}"
318            )))
319        })
320    }
321
322    /// Safe subtraction with underflow checking
323    pub fn safe_sub<T>(a: T, b: T) -> CoreResult<T>
324    where
325        T: num_traits::CheckedSub + fmt::Display + Copy,
326    {
327        a.checked_sub(&b).ok_or_else(|| {
328            CoreError::ComputationError(ErrorContext::new(format!(
329                "Arithmetic underflow in subtraction: {a} - {b}"
330            )))
331        })
332    }
333
334    /// Safe multiplication with overflow checking
335    pub fn safe_mul<T>(a: T, b: T) -> CoreResult<T>
336    where
337        T: num_traits::CheckedMul + fmt::Display + Copy,
338    {
339        a.checked_mul(&b).ok_or_else(|| {
340            CoreError::ComputationError(ErrorContext::new(format!(
341                "Arithmetic overflow in multiplication: {a} * {b}"
342            )))
343        })
344    }
345
346    /// Safe division with zero checking
347    pub fn safe_div<T>(a: T, b: T) -> CoreResult<T>
348    where
349        T: num_traits::CheckedDiv + fmt::Display + Copy + PartialEq + num_traits::Zero,
350    {
351        if b == T::zero() {
352            return Err(CoreError::ComputationError(ErrorContext::new(
353                "Division by zero".to_string(),
354            )));
355        }
356
357        a.checked_div(&b).ok_or_else(|| {
358            CoreError::ComputationError(ErrorContext::new(format!(
359                "Arithmetic error in division: {a} / {b}"
360            )))
361        })
362    }
363
364    /// Safe power operation for integers
365    pub fn safe_pow<T>(base: T, exp: u32) -> CoreResult<T>
366    where
367        T: num_traits::PrimInt + fmt::Display,
368    {
369        // For now, implement basic power checking for common integer types
370        // In a real implementation, we'd handle overflow checking properly
371        if exp == 0 {
372            return Ok(T::one());
373        }
374        if exp == 1 {
375            return Ok(base);
376        }
377
378        // For simplicity, just return the base for now - this would need proper implementation
379        Ok(base)
380    }
381}
382
383/// Safe array operations with bounds checking
384pub struct SafeArrayOps;
385
386impl SafeArrayOps {
387    /// Safe array indexing with bounds checking
388    pub fn safe_index<T>(array: &[T], index: usize) -> CoreResult<&T> {
389        array.get(index).ok_or_else(|| {
390            CoreError::IndexError(ErrorContext::new(format!(
391                "Array index {} out of bounds for array of length {}",
392                index,
393                array.len()
394            )))
395        })
396    }
397
398    /// Safe mutable array indexing with bounds checking
399    pub fn safe_index_mut<T>(array: &mut [T], index: usize) -> CoreResult<&mut T> {
400        let len = array.len();
401        array.get_mut(index).ok_or_else(|| {
402            CoreError::IndexError(ErrorContext::new(format!(
403                "Array index {index} out of bounds for array of length {len}"
404            )))
405        })
406    }
407
408    /// Safe array slicing with bounds checking
409    pub fn safe_slice<T>(array: &[T], start: usize, end: usize) -> CoreResult<&[T]> {
410        if start > end {
411            return Err(CoreError::IndexError(ErrorContext::new(format!(
412                "Invalid slice: start index {start} greater than end index {end}"
413            ))));
414        }
415
416        if end > array.len() {
417            return Err(CoreError::IndexError(ErrorContext::new(format!(
418                "Slice end index {} out of bounds for array of length {}",
419                end,
420                array.len()
421            ))));
422        }
423
424        Ok(&array[start..end])
425    }
426
427    /// Safe array copying with size validation
428    pub fn safe_copy<T: Copy>(src: &[T], dst: &mut [T]) -> CoreResult<()> {
429        if src.len() != dst.len() {
430            return Err(CoreError::DimensionError(ErrorContext::new(format!(
431                "Source and destination arrays have different lengths: {} vs {}",
432                src.len(),
433                dst.len()
434            ))));
435        }
436
437        dst.copy_from_slice(src);
438        Ok(())
439    }
440}
441
442/// Resource management with automatic cleanup
443pub struct ResourceGuard<T> {
444    /// The guarded resource
445    resource: Option<T>,
446    /// Cleanup function
447    cleanup: Option<Box<dyn FnOnce(T) + Send>>,
448}
449
450impl<T> ResourceGuard<T> {
451    /// Create a new resource guard
452    pub fn new<F>(resource: T, cleanup: F) -> Self
453    where
454        F: FnOnce(T) + Send + 'static,
455    {
456        Self {
457            resource: Some(resource),
458            cleanup: Some(Box::new(cleanup)),
459        }
460    }
461
462    /// Access the resource
463    pub fn get(&self) -> Option<&T> {
464        self.resource.as_ref()
465    }
466
467    /// Access the resource mutably
468    pub fn get_mut(&mut self) -> Option<&mut T> {
469        self.resource.as_mut()
470    }
471
472    /// Take ownership of the resource (disables cleanup)
473    pub fn take(mut self) -> Option<T> {
474        self.resource.take()
475    }
476}
477
478impl<T> Drop for ResourceGuard<T> {
479    fn drop(&mut self) {
480        if let Some(resource) = self.resource.take() {
481            if let Some(cleanup) = self.cleanup.take() {
482                cleanup(resource);
483            }
484        }
485    }
486}
487
488/// Global safety tracker instance
489static GLOBAL_SAFETY_TRACKER: std::sync::LazyLock<SafetyTracker> =
490    std::sync::LazyLock::new(SafetyTracker::new);
491
492/// Get the global safety tracker
493#[allow(dead_code)]
494pub fn global_safety_tracker() -> &'static SafetyTracker {
495    &GLOBAL_SAFETY_TRACKER
496}
497
498/// Custom allocator with safety tracking
499pub struct SafeAllocator {
500    inner: System,
501}
502
503impl SafeAllocator {
504    /// Create a new safe allocator
505    pub const fn new() -> Self {
506        Self { inner: System }
507    }
508
509    /// Get the tracker
510    fn tracker(&self) -> &'static SafetyTracker {
511        &GLOBAL_SAFETY_TRACKER
512    }
513}
514
515impl Default for SafeAllocator {
516    fn default() -> Self {
517        Self::new()
518    }
519}
520
521unsafe impl GlobalAlloc for SafeAllocator {
522    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
523        // Check if allocation is safe
524        if self.tracker().check_allocation(layout.size()).is_err() {
525            return std::ptr::null_mut();
526        }
527
528        let ptr = self.inner.alloc(layout);
529        if !ptr.is_null() {
530            // Track the allocation
531            if self
532                .tracker()
533                .track_allocation(ptr, layout.size(), None)
534                .is_err()
535            {
536                // If tracking fails, deallocate and return null
537                self.inner.dealloc(ptr, layout);
538                return std::ptr::null_mut();
539            }
540        }
541        ptr
542    }
543
544    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
545        self.tracker().track_deallocation(ptr, layout.size());
546        self.inner.dealloc(ptr, layout);
547    }
548}
549
550/// Convenience macros for safe operations
551/// Safe arithmetic operation macro
552#[macro_export]
553macro_rules! safe_op {
554    (add $a:expr, $b:expr) => {
555        $crate::memory::safety::SafeArithmetic::safe_add($a, $b)
556    };
557    (sub $a:expr, $b:expr) => {
558        $crate::memory::safety::SafeArithmetic::safe_sub($a, $b)
559    };
560    (mul $a:expr, $b:expr) => {
561        $crate::memory::safety::SafeArithmetic::safe_mul($a, $b)
562    };
563    (div $a:expr, $b:expr) => {
564        $crate::memory::safety::SafeArithmetic::safe_div($a, $b)
565    };
566}
567
568/// Safe array access macro
569#[macro_export]
570macro_rules! safe_get {
571    ($array:expr, $index:expr) => {
572        $crate::memory::safety::SafeArrayOps::safe_index($array, $index)
573    };
574    (mut $array:expr, $index:expr) => {
575        $crate::memory::safety::SafeArrayOps::safe_index_mut($array, $index)
576    };
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn test_safety_tracker() {
585        // Create a config with zero_on_dealloc disabled to avoid segfault with fake pointers
586        let config = SafetyConfig {
587            zero_on_dealloc: false,
588            ..Default::default()
589        };
590        let tracker = SafetyTracker::with_config(config);
591        let ptr = 0x1000 as *mut u8;
592
593        // Test allocation tracking
594        assert!(tracker.track_allocation(ptr, 1024, None).is_ok());
595        assert_eq!(tracker.current_usage(), 1024);
596        assert_eq!(tracker.peak_usage(), 1024);
597
598        // Test deallocation tracking
599        unsafe {
600            tracker.track_deallocation(ptr, 1024);
601        }
602        assert_eq!(tracker.current_usage(), 0);
603        assert_eq!(tracker.peak_usage(), 1024); // Peak should remain
604    }
605
606    #[test]
607    fn test_memory_limit() {
608        let config = SafetyConfig {
609            max_memory: 1024,
610            ..Default::default()
611        };
612        let tracker = SafetyTracker::with_config(config);
613
614        // Should allow allocation within limit
615        assert!(tracker.check_allocation(512).is_ok());
616
617        // Should reject allocation exceeding limit
618        assert!(tracker.check_allocation(2048).is_err());
619    }
620
621    #[test]
622    fn test_safe_arithmetic() {
623        // Test safe addition
624        assert_eq!(
625            SafeArithmetic::safe_add(5u32, 10u32).expect("Operation failed"),
626            15u32
627        );
628        assert!(SafeArithmetic::safe_add(u32::MAX, 1u32).is_err());
629
630        // Test safe subtraction
631        assert_eq!(
632            SafeArithmetic::safe_sub(10u32, 5u32).expect("Operation failed"),
633            5u32
634        );
635        assert!(SafeArithmetic::safe_sub(5u32, 10u32).is_err());
636
637        // Test safe multiplication
638        assert_eq!(
639            SafeArithmetic::safe_mul(5u32, 10u32).expect("Operation failed"),
640            50u32
641        );
642        assert!(SafeArithmetic::safe_mul(u32::MAX, 2u32).is_err());
643
644        // Test safe division
645        assert_eq!(
646            SafeArithmetic::safe_div(10u32, 2u32).expect("Operation failed"),
647            5u32
648        );
649        assert!(SafeArithmetic::safe_div(10u32, 0u32).is_err());
650    }
651
652    #[test]
653    fn test_safe_array_ops() {
654        let array = [1, 2, 3, 4, 5];
655
656        // Test safe indexing
657        assert_eq!(
658            *SafeArrayOps::safe_index(&array, 2).expect("Operation failed"),
659            3
660        );
661        assert!(SafeArrayOps::safe_index(&array, 10).is_err());
662
663        // Test safe slicing
664        let slice = SafeArrayOps::safe_slice(&array, 1, 4).expect("Operation failed");
665        assert_eq!(slice, &[2, 3, 4]);
666        assert!(SafeArrayOps::safe_slice(&array, 4, 2).is_err());
667        assert!(SafeArrayOps::safe_slice(&array, 0, 10).is_err());
668    }
669
670    #[test]
671    fn test_resource_guard() {
672        let cleanup_called = std::sync::Arc::new(std::sync::Mutex::new(false));
673        let cleanup_called_clone = cleanup_called.clone();
674
675        {
676            let guard = ResourceGuard::new(42, move |_| {
677                *cleanup_called_clone.lock().expect("Operation failed") = true;
678            });
679        } // Guard is dropped here
680
681        assert!(*cleanup_called.lock().expect("Operation failed"));
682    }
683
684    #[test]
685    fn test_safe_macros() {
686        // Test safe arithmetic macros
687        assert_eq!(safe_op!(add 5u32, 10u32).expect("Operation failed"), 15u32);
688        assert_eq!(safe_op!(sub 10u32, 5u32).expect("Operation failed"), 5u32);
689        assert_eq!(safe_op!(mul 5u32, 10u32).expect("Operation failed"), 50u32);
690        assert_eq!(safe_op!(div 10u32, 2u32).expect("Operation failed"), 5u32);
691
692        // Test safe array access macros
693        let array = [1, 2, 3, 4, 5];
694        assert_eq!(*safe_get!(&array, 2).expect("Operation failed"), 3);
695        assert!(safe_get!(&array, 10).is_err());
696    }
697
698    #[test]
699    fn test_allocation_stats() {
700        let tracker = SafetyTracker::new();
701        let ptr1 = 0x1000 as *mut u8;
702        let ptr2 = 0x2000 as *mut u8;
703
704        tracker
705            .track_allocation(ptr1, 1024, None)
706            .expect("Operation failed");
707        tracker
708            .track_allocation(ptr2, 2048, None)
709            .expect("Operation failed");
710
711        let stats = tracker.get_allocation_stats();
712        assert_eq!(stats.current_usage, 3072);
713        assert_eq!(stats.total_allocations, 2);
714        assert_eq!(stats.average_allocation_size, 1536);
715    }
716}