use std::error::Error;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::io;
use std::ops::Deref;
use std::os::raw::c_void;
mod sys;
#[derive(Debug)]
pub enum StackError {
ExceedsMaximumSize(usize),
IoError(io::Error),
}
impl Display for StackError {
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
match *self {
StackError::ExceedsMaximumSize(size) => {
write!(
fmt,
"Requested more than max size of {} bytes for a stack",
size
)
}
StackError::IoError(ref e) => std::fmt::Display::fmt(&e, fmt),
}
}
}
impl Error for StackError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
StackError::ExceedsMaximumSize(_) => None,
StackError::IoError(ref e) => Some(e),
}
}
}
#[derive(Debug)]
pub struct Stack {
top: *mut c_void,
bottom: *mut c_void,
}
impl Stack {
#[inline]
pub(crate) unsafe fn new(top: *mut c_void, bottom: *mut c_void) -> Stack {
debug_assert!(top >= bottom);
Stack { top, bottom }
}
#[inline]
pub fn top(&self) -> *mut c_void {
self.top
}
#[inline]
pub fn bottom(&self) -> *mut c_void {
self.bottom
}
#[inline]
pub fn len(&self) -> usize {
self.top as usize - self.bottom as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn min_size() -> usize {
sys::min_stack_size()
}
#[inline]
pub fn max_size() -> usize {
sys::max_stack_size(true)
}
#[inline]
pub fn default_size() -> usize {
sys::default_stack_size()
}
fn allocate(mut size: usize) -> Result<Stack, StackError> {
let page_size = sys::page_size();
let min_stack_size = sys::min_stack_size();
let max_stack_size = sys::max_stack_size(false);
let add = page_size << 1;
if size < min_stack_size {
size = min_stack_size;
}
size = (size - 1) & !(page_size - 1);
if let Some(size) = size.checked_add(add) {
if size <= max_stack_size {
let mut ret = unsafe { sys::allocate_stack(size) };
if let Ok(stack) = ret {
ret = unsafe { sys::protect_stack(&stack) };
}
return ret.map_err(StackError::IoError);
}
}
Err(StackError::ExceedsMaximumSize(max_stack_size - add))
}
}
unsafe impl Send for Stack {}
#[derive(Debug)]
pub struct ProtectedFixedSizeStack(Stack);
impl ProtectedFixedSizeStack {
pub fn new(size: usize) -> Result<ProtectedFixedSizeStack, StackError> {
Stack::allocate(size).map(ProtectedFixedSizeStack)
}
}
impl Deref for ProtectedFixedSizeStack {
type Target = Stack;
fn deref(&self) -> &Stack {
&self.0
}
}
impl Default for ProtectedFixedSizeStack {
fn default() -> ProtectedFixedSizeStack {
ProtectedFixedSizeStack::new(Stack::default_size()).unwrap_or_else(|err| {
panic!("Failed to allocate ProtectedFixedSizeStack with {:?}", err)
})
}
}
impl Drop for ProtectedFixedSizeStack {
fn drop(&mut self) {
let page_size = sys::page_size();
let guard = (self.0.bottom() as usize - page_size) as *mut c_void;
let size_with_guard = self.0.len() + page_size;
unsafe {
sys::deallocate_stack(guard, size_with_guard);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stack_size_too_small() {
let stack = ProtectedFixedSizeStack::new(0).unwrap();
assert_eq!(stack.len(), Stack::min_size());
unsafe { std::ptr::write_bytes(stack.bottom() as *mut u8, 0x1d, stack.len()) };
}
#[test]
fn stack_size_too_large() {
let stack_size = Stack::max_size();
match ProtectedFixedSizeStack::new(stack_size) {
Err(StackError::ExceedsMaximumSize(..)) => panic!(),
_ => {}
}
let stack_size = stack_size + 1;
match ProtectedFixedSizeStack::new(stack_size) {
Err(StackError::ExceedsMaximumSize(..)) => {}
_ => panic!(),
}
}
}