use core::mem::ManuallyDrop;
use core::ops::Range;
use core::ptr;
use crate::{Error, Protection, Result, os, page, util};
pub struct Allocation {
base: *const (),
size: usize,
}
impl Allocation {
#[inline(always)]
pub fn as_ptr<T>(&self) -> *const T {
self.base.cast()
}
#[inline(always)]
pub fn as_mut_ptr<T>(&mut self) -> *mut T {
self.base.cast_mut().cast()
}
#[inline(always)]
pub fn as_ptr_range<T>(&self) -> Range<*const T> {
let range = self.as_range();
ptr::with_exposed_provenance::<T>(range.start)..ptr::with_exposed_provenance::<T>(range.end)
}
#[inline(always)]
pub fn as_mut_ptr_range<T>(&mut self) -> Range<*mut T> {
let range = self.as_range();
ptr::with_exposed_provenance_mut::<T>(range.start)
..ptr::with_exposed_provenance_mut::<T>(range.end)
}
#[inline(always)]
pub fn as_range(&self) -> Range<usize> {
let start = self.base.addr();
start..start.saturating_add(self.size)
}
#[inline(always)]
pub fn len(&self) -> usize {
self.size
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.size == 0
}
#[inline]
pub fn into_raw_parts<T>(self) -> (*mut T, usize) {
let mut this = ManuallyDrop::new(self);
(this.as_mut_ptr(), this.len())
}
#[inline(always)]
pub unsafe fn from_raw_parts<T>(ptr: *mut T, length: usize) -> Self {
Self {
base: ptr.cast(),
size: length,
}
}
}
impl Drop for Allocation {
#[inline]
fn drop(&mut self) {
let result = unsafe { os::free(self.base, self.size) };
debug_assert!(result.is_ok(), "freeing region: {:?}", result);
}
}
#[inline]
pub fn alloc(size: usize, protection: Protection) -> Result<Allocation> {
if size == 0 {
return Err(Error::InvalidParameter("size"));
}
let size = page::ceil(ptr::without_provenance::<()>(size)).addr();
unsafe {
let base = os::alloc(ptr::null::<()>(), size, protection)?;
Ok(Allocation { base, size })
}
}
#[inline]
pub fn alloc_at<T>(address: *const T, size: usize, protection: Protection) -> Result<Allocation> {
let (address, size) = util::round_to_page_boundaries(address, size)?;
unsafe {
let base = os::alloc(address.cast(), size, protection)?;
Ok(Allocation { base, size })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_size_is_aligned_to_page_size() -> Result<()> {
let memory = alloc(1, Protection::NONE)?;
assert_eq!(memory.len(), page::size());
Ok(())
}
#[test]
fn alloc_rejects_empty_allocation() {
assert!(matches!(
alloc(0, Protection::NONE),
Err(Error::InvalidParameter(_))
));
}
#[test]
fn alloc_obtains_correct_properties() -> Result<()> {
let memory = alloc(1, Protection::READ_WRITE)?;
let region = crate::query(memory.as_ptr::<()>())?;
assert_eq!(region.protection(), Protection::READ_WRITE);
assert!(region.len() >= memory.len());
assert!(!region.is_guarded());
assert!(!region.is_shared());
assert!(region.is_committed());
Ok(())
}
#[test]
#[cfg(not(target_os = "netbsd"))]
fn alloc_frees_memory_when_dropped() -> Result<()> {
let buffers = (0..8)
.map(|_| alloc(1, Protection::READ_WRITE))
.collect::<Result<alloc::vec::Vec<_>>>()?;
let start = alloc(1, Protection::READ_WRITE)?;
let base = start.as_ptr::<()>();
drop(start);
let query = crate::query(base);
assert!(
matches!(query, Err(Error::UnmappedRegion)),
"expected unmapped region after free, got {query:?}; retained {} buffers",
buffers.len()
);
Ok(())
}
#[test]
fn alloc_can_allocate_unused_region() -> Result<()> {
let base = alloc(1, Protection::NONE)?.as_ptr::<()>();
let memory = alloc_at(base, 1, Protection::READ_WRITE)?;
assert_eq!(memory.as_ptr(), base);
Ok(())
}
#[test]
#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))]
fn alloc_can_allocate_executable_region() -> Result<()> {
let memory = alloc(1, Protection::WRITE_EXECUTE)?;
assert_eq!(memory.len(), page::size());
Ok(())
}
#[test]
#[cfg(all(windows, target_pointer_width = "64"))]
fn alloc_can_reserve_large_parts_of_address_space() -> Result<()> {
let base = alloc(1 << 40, Protection::NONE)?.as_ptr::<()>();
let memory = alloc_at(base, 1, Protection::READ_WRITE)?;
assert_eq!(memory.as_ptr(), base);
Ok(())
}
}