use std::alloc::{alloc, dealloc, Layout};
use std::collections::HashMap;
use std::fmt;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, SystemTime};
use crate::diagnostics::Error;
use crate::ffi::c_types::CType;
#[derive(Debug, Clone)]
pub enum MemoryError {
AllocationFailed {
size: usize,
alignment: usize,
},
InvalidPointer(*const u8),
DoubleFree(*const u8),
LeakDetected {
ptr: *const u8,
size: usize,
allocated_at: SystemTime,
},
BufferOverflow {
ptr: *const u8,
size: usize,
accessed_size: usize,
},
AlignmentError {
ptr: *const u8,
required_alignment: usize,
actual_alignment: usize,
},
PoolExhausted {
pool_name: String,
requested_size: usize,
},
}
impl fmt::Display for MemoryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MemoryError::AllocationFailed { size, alignment } => {
write!(f, "Memory allocation failed: {size} bytes with {alignment} alignment")
}
MemoryError::InvalidPointer(ptr) => {
write!(f, "Invalid pointer: {ptr:p}")
}
MemoryError::DoubleFree(ptr) => {
write!(f, "Double free detected for pointer: {ptr:p}")
}
MemoryError::LeakDetected { ptr, size, allocated_at } => {
write!(f, "Memory leak detected: {ptr:p} ({size} bytes, allocated at {allocated_at:?})")
}
MemoryError::BufferOverflow { ptr, size, accessed_size } => {
write!(f, "Buffer overflow: pointer {ptr:p}, buffer size {size}, accessed size {accessed_size}")
}
MemoryError::AlignmentError { ptr, required_alignment, actual_alignment } => {
write!(f, "Memory alignment error: pointer {ptr:p}, required {required_alignment}, actual {actual_alignment}")
}
MemoryError::PoolExhausted { pool_name, requested_size } => {
write!(f, "Memory pool '{pool_name}' exhausted, requested {requested_size} bytes")
}
}
}
}
impl std::error::Error for MemoryError {}
impl From<MemoryError> for Error {
fn from(memory_error: MemoryError) -> Self {
Error::runtime_error(memory_error.to_string(), None)
}
}
#[derive(Debug, Clone)]
pub struct AllocationInfo {
pub ptr: NonNull<u8>,
pub size: usize,
pub layout: Layout,
pub allocated_at: SystemTime,
pub ref_count: Arc<Mutex<usize>>,
pub c_type: Option<CType>,
pub debug_info: Option<String>,
}
impl AllocationInfo {
pub fn new(ptr: NonNull<u8>, layout: Layout, c_type: Option<CType>) -> Self {
Self {
ptr,
size: layout.size(),
layout,
allocated_at: SystemTime::now(),
ref_count: Arc::new(Mutex::new(1)),
c_type,
debug_info: None,
}
}
pub fn add_ref(&self) {
let mut count = self.ref_count.lock().unwrap();
*count += 1;
}
pub fn release_ref(&self) -> usize {
let mut count = self.ref_count.lock().unwrap();
if *count > 0 {
*count -= 1;
}
*count
}
pub fn ref_count(&self) -> usize {
*self.ref_count.lock().unwrap()
}
pub fn is_aligned(&self) -> bool {
let addr = self.ptr.as_ptr() as usize;
addr % self.layout.align() == 0
}
}
#[derive(Debug)]
pub struct FfiMemoryManager {
allocations: RwLock<HashMap<*const u8, AllocationInfo>>,
stats: RwLock<MemoryStats>,
config: RwLock<MemoryConfig>,
pools: RwLock<HashMap<String, MemoryPool>>,
}
#[derive(Debug, Default, Clone)]
pub struct MemoryStats {
pub total_allocated: usize,
pub total_freed: usize,
pub current_usage: usize,
pub peak_usage: usize,
pub active_allocations: usize,
pub allocation_count: u64,
pub deallocation_count: u64,
pub leaks_detected: u64,
pub double_frees_prevented: u64,
}
#[derive(Debug, Clone)]
pub struct MemoryConfig {
pub leak_detection: bool,
pub double_free_protection: bool,
pub buffer_overflow_protection: bool,
pub max_memory_usage: usize,
pub use_memory_pools: bool,
pub pool_sizes: Vec<usize>,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
leak_detection: true,
double_free_protection: true,
buffer_overflow_protection: true,
max_memory_usage: 0, use_memory_pools: true,
pool_sizes: vec![32, 64, 128, 256, 512, 1024, 2048, 4096],
}
}
}
impl Default for FfiMemoryManager {
fn default() -> Self {
Self::new()
}
}
impl FfiMemoryManager {
pub fn new() -> Self {
Self {
allocations: RwLock::new(HashMap::new()),
stats: RwLock::new(MemoryStats::default()),
config: RwLock::new(MemoryConfig::default()),
pools: RwLock::new(HashMap::new()),
}
}
pub fn configure(&self, config: MemoryConfig) {
let mut current_config = self.config.write().unwrap();
*current_config = config.clone();
if config.use_memory_pools {
let mut pools = self.pools.write().unwrap();
for &size in &config.pool_sizes {
let pool_name = format!("pool_{size}");
pools.insert(pool_name.clone(), MemoryPool::new(pool_name, size, 16)); }
}
}
pub fn allocate(&self, size: usize, c_type: Option<CType>) -> std::result::Result<NonNull<u8>, MemoryError> {
let config = self.config.read().unwrap();
if config.max_memory_usage > 0 {
let stats = self.stats.read().unwrap();
if stats.current_usage + size > config.max_memory_usage {
return Err(MemoryError::AllocationFailed { size, alignment: 1 });
}
}
let alignment = if let Some(ref c_type) = c_type {
c_type.alignment()
} else {
std::mem::align_of::<*const u8>()
};
if config.use_memory_pools {
if let Some(ptr) = self.try_allocate_from_pool(size) {
self.track_allocation(ptr, size, alignment, c_type)?;
return Ok(ptr);
}
}
let layout = Layout::from_size_align(size, alignment)
.map_err(|_| MemoryError::AllocationFailed { size, alignment })?;
let ptr = unsafe {
let raw_ptr = alloc(layout);
if raw_ptr.is_null() {
return Err(MemoryError::AllocationFailed { size, alignment });
}
NonNull::new_unchecked(raw_ptr)
};
self.track_allocation(ptr, size, alignment, c_type)?;
Ok(ptr)
}
pub fn deallocate(&self, ptr: NonNull<u8>) -> std::result::Result<(), MemoryError> {
let config = self.config.read().unwrap();
if config.double_free_protection {
let allocations = self.allocations.read().unwrap();
if !allocations.contains_key(&(ptr.as_ptr() as *const u8)) {
let mut stats = self.stats.write().unwrap();
stats.double_frees_prevented += 1;
return Err(MemoryError::DoubleFree(ptr.as_ptr()));
}
}
let allocation_info = {
let mut allocations = self.allocations.write().unwrap();
allocations.remove(&(ptr.as_ptr() as *const u8))
};
if let Some(info) = allocation_info {
if config.use_memory_pools
&& self.try_return_to_pool(ptr, info.size) {
self.update_deallocation_stats(info.size);
return Ok(());
}
unsafe {
dealloc(ptr.as_ptr(), info.layout);
}
self.update_deallocation_stats(info.size);
Ok(())
} else {
Err(MemoryError::InvalidPointer(ptr.as_ptr()))
}
}
fn track_allocation(&self, ptr: NonNull<u8>, size: usize, alignment: usize, c_type: Option<CType>)
-> std::result::Result<(), MemoryError> {
let layout = Layout::from_size_align(size, alignment)
.map_err(|_| MemoryError::AllocationFailed { size, alignment })?;
let info = AllocationInfo::new(ptr, layout, c_type);
if !info.is_aligned() {
return Err(MemoryError::AlignmentError {
ptr: ptr.as_ptr(),
required_alignment: alignment,
actual_alignment: ptr.as_ptr() as usize % alignment,
});
}
{
let mut allocations = self.allocations.write().unwrap();
allocations.insert(ptr.as_ptr(), info);
}
{
let mut stats = self.stats.write().unwrap();
stats.total_allocated += size;
stats.current_usage += size;
stats.active_allocations += 1;
stats.allocation_count += 1;
if stats.current_usage > stats.peak_usage {
stats.peak_usage = stats.current_usage;
}
}
Ok(())
}
fn update_deallocation_stats(&self, size: usize) {
let mut stats = self.stats.write().unwrap();
stats.total_freed += size;
stats.current_usage = stats.current_usage.saturating_sub(size);
stats.active_allocations = stats.active_allocations.saturating_sub(1);
stats.deallocation_count += 1;
}
fn try_allocate_from_pool(&self, size: usize) -> Option<NonNull<u8>> {
let pools = self.pools.read().unwrap();
for &pool_size in [32, 64, 128, 256, 512, 1024, 2048, 4096].iter() {
if size <= pool_size {
let pool_name = format!("pool_{pool_size}");
if let Some(pool) = pools.get(&pool_name) {
if let Some(ptr) = pool.allocate() {
return Some(ptr);
}
}
}
}
None
}
fn try_return_to_pool(&self, ptr: NonNull<u8>, size: usize) -> bool {
let pools = self.pools.read().unwrap();
for &pool_size in [32, 64, 128, 256, 512, 1024, 2048, 4096].iter() {
if size <= pool_size {
let pool_name = format!("pool_{pool_size}");
if let Some(pool) = pools.get(&pool_name) {
return pool.deallocate(ptr);
}
}
}
false
}
pub fn check_leaks(&self) -> Vec<MemoryError> {
let config = self.config.read().unwrap();
if !config.leak_detection {
return vec![];
}
let allocations = self.allocations.read().unwrap();
let now = SystemTime::now();
let mut leaks = Vec::new();
for (ptr, info) in allocations.iter() {
if info.ref_count() == 0 {
if let Ok(duration) = now.duration_since(info.allocated_at) {
if duration > Duration::from_secs(300) { leaks.push(MemoryError::LeakDetected {
ptr: *ptr,
size: info.size,
allocated_at: info.allocated_at,
});
}
}
}
}
{
let mut stats = self.stats.write().unwrap();
stats.leaks_detected += leaks.len() as u64;
}
leaks
}
pub fn stats(&self) -> MemoryStats {
self.stats.read().unwrap().clone()
}
pub fn get_allocation_info(&self, ptr: *const u8) -> Option<AllocationInfo> {
let allocations = self.allocations.read().unwrap();
allocations.get(&ptr).cloned()
}
pub fn list_allocations(&self) -> Vec<(*const u8, AllocationInfo)> {
let allocations = self.allocations.read().unwrap();
allocations.iter().map(|(&ptr, info)| (ptr, info.clone())).collect()
}
pub fn cleanup_all(&self) -> std::result::Result<(), Vec<MemoryError>> {
let allocation_ptrs: Vec<*const u8> = {
let allocations = self.allocations.read().unwrap();
allocations.keys().cloned().collect()
};
let mut errors = Vec::new();
for ptr in allocation_ptrs {
if let Some(non_null_ptr) = NonNull::new(ptr as *mut u8) {
if let Err(e) = self.deallocate(non_null_ptr) {
errors.push(e);
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug)]
pub struct MemoryPool {
name: String,
block_size: usize,
total_blocks: usize,
free_blocks: Mutex<Vec<NonNull<u8>>>,
pool_memory: NonNull<u8>,
layout: Layout,
}
impl MemoryPool {
pub fn new(name: String, block_size: usize, block_count: usize) -> Self {
let total_size = block_size * block_count;
let alignment = std::mem::align_of::<*const u8>();
let layout = Layout::from_size_align(total_size, alignment).unwrap();
let pool_memory = unsafe {
let raw_ptr = alloc(layout);
assert!(!raw_ptr.is_null(), "Failed to allocate memory pool");
NonNull::new_unchecked(raw_ptr)
};
let mut free_blocks = Vec::with_capacity(block_count);
for i in 0..block_count {
unsafe {
let block_ptr = pool_memory.as_ptr().add(i * block_size);
free_blocks.push(NonNull::new_unchecked(block_ptr));
}
}
Self {
name,
block_size,
total_blocks: block_count,
free_blocks: Mutex::new(free_blocks),
pool_memory,
layout,
}
}
pub fn allocate(&self) -> Option<NonNull<u8>> {
let mut free_blocks = self.free_blocks.lock().unwrap();
free_blocks.pop()
}
pub fn deallocate(&self, ptr: NonNull<u8>) -> bool {
let pool_start = self.pool_memory.as_ptr() as usize;
let pool_end = pool_start + self.layout.size();
let ptr_addr = ptr.as_ptr() as usize;
if ptr_addr >= pool_start && ptr_addr < pool_end {
let offset = ptr_addr - pool_start;
if offset % self.block_size == 0 {
let mut free_blocks = self.free_blocks.lock().unwrap();
free_blocks.push(ptr);
return true;
}
}
false
}
pub fn stats(&self) -> PoolStats {
let free_blocks = self.free_blocks.lock().unwrap();
PoolStats {
name: self.name.clone(),
block_size: self.block_size,
total_blocks: self.total_blocks,
free_blocks: free_blocks.len(),
used_blocks: self.total_blocks - free_blocks.len(),
}
}
}
impl Drop for MemoryPool {
fn drop(&mut self) {
unsafe {
dealloc(self.pool_memory.as_ptr(), self.layout);
}
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub name: String,
pub block_size: usize,
pub total_blocks: usize,
pub free_blocks: usize,
pub used_blocks: usize,
}
lazy_static::lazy_static! {
pub static ref GLOBAL_FFI_MEMORY_MANAGER: FfiMemoryManager = FfiMemoryManager::new();
}
pub fn ffi_allocate(size: usize, c_type: Option<CType>) -> std::result::Result<NonNull<u8>, MemoryError> {
GLOBAL_FFI_MEMORY_MANAGER.allocate(size, c_type)
}
pub fn ffi_deallocate(ptr: NonNull<u8>) -> std::result::Result<(), MemoryError> {
GLOBAL_FFI_MEMORY_MANAGER.deallocate(ptr)
}
pub fn ffi_check_leaks() -> Vec<MemoryError> {
GLOBAL_FFI_MEMORY_MANAGER.check_leaks()
}
pub fn ffi_memory_stats() -> MemoryStats {
GLOBAL_FFI_MEMORY_MANAGER.stats()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_manager_creation() {
let manager = FfiMemoryManager::new();
let stats = manager.stats();
assert_eq!(stats.current_usage, 0);
assert_eq!(stats.active_allocations, 0);
}
#[test]
fn test_basic_allocation() {
let manager = FfiMemoryManager::new();
let ptr = manager.allocate(64, None).unwrap();
assert!(!ptr.as_ptr().is_null());
let stats = manager.stats();
assert_eq!(stats.active_allocations, 1);
assert!(stats.current_usage >= 64);
manager.deallocate(ptr).unwrap();
let stats = manager.stats();
assert_eq!(stats.active_allocations, 0);
}
#[test]
fn test_double_free_protection() {
let manager = FfiMemoryManager::new();
let ptr = manager.allocate(64, None).unwrap();
manager.deallocate(ptr).unwrap();
let result = manager.deallocate(ptr);
assert!(matches!(result, Err(MemoryError::DoubleFree(_))));
}
#[test]
fn test_memory_pool() {
let pool = MemoryPool::new("test_pool".to_string(), 64, 4);
let ptr1 = pool.allocate().unwrap();
let ptr2 = pool.allocate().unwrap();
let stats = pool.stats();
assert_eq!(stats.used_blocks, 2);
assert_eq!(stats.free_blocks, 2);
assert!(pool.deallocate(ptr1));
assert!(pool.deallocate(ptr2));
let stats = pool.stats();
assert_eq!(stats.used_blocks, 0);
assert_eq!(stats.free_blocks, 4);
}
#[test]
fn test_allocation_info() {
let manager = FfiMemoryManager::new();
let ptr = manager.allocate(128, Some(CType::Int32)).unwrap();
let info = manager.get_allocation_info(ptr.as_ptr()).unwrap();
assert_eq!(info.size, 128);
assert_eq!(info.c_type, Some(CType::Int32));
assert!(info.is_aligned());
manager.deallocate(ptr).unwrap();
}
#[test]
fn test_memory_configuration() {
let manager = FfiMemoryManager::new();
let config = MemoryConfig {
max_memory_usage: 1024,
use_memory_pools: false,
..Default::default()
};
manager.configure(config);
let ptr1 = manager.allocate(512, None).unwrap();
let result = manager.allocate(600, None);
assert!(matches!(result, Err(MemoryError::AllocationFailed { .. })));
manager.deallocate(ptr1).unwrap();
}
}
unsafe impl Send for FfiMemoryManager {}
unsafe impl Sync for FfiMemoryManager {}