diff --git a/server/src/coredb/array.rs b/server/src/coredb/array.rs index 9f220a25..b20f3c3c 100644 --- a/server/src/coredb/array.rs +++ b/server/src/coredb/array.rs @@ -41,12 +41,27 @@ use core::ptr; use core::slice; use serde::{de::SeqAccess, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +/// A compile-time, fixed size array that can have unintialized memory. This array is as +/// efficient as you'd expect a normal array to be, but with the added benefit that you +/// don't have to initialize all the elements. Safe abstractions are made available +/// enabling us to not enter uninitialized space and read the _available_ elements. The +/// array size is limited to 16 bits or 2 bytes to prevent stack overflows. +/// +/// ## Panics +/// To avoid stack corruption among other crazy things, several implementations like [`Extend`] +/// can panic. There are _silently corrupting_ methods too which can be used if you can uphold +/// the guarantees pub struct Array { + /// the maybe bad stack stack: [MaybeUninit; N], + /// the initialized length /// no stack should be more than 16 bytes init_len: u16, } +/// The len scopeguard is like a scopeguard that provides panic safety incase an append-like +/// operation involving iterators causes the iterator to panic. This makes sure that we still +/// set the len on panic pub struct LenScopeGuard<'a, T: Copy> { real_ref: &'a mut T, temp: T, @@ -74,7 +89,7 @@ impl<'a, T: Copy> Drop for LenScopeGuard<'a, T> { } } -// defy the compiler +// defy the compiler; just some silly hackery here -- move on struct UninitArray(PhantomData T>); impl UninitArray { @@ -83,38 +98,57 @@ impl UninitArray { } impl Array { + /// Create a new array pub const fn new() -> Self { Array { stack: UninitArray::ARRAY, init_len: 0, } } + /// Get the apparent length of the array pub const fn len(&self) -> usize { self.init_len as usize } + /// Get the capacity of the array pub const fn capacity(&self) -> usize { N } + /// Check if the array is full pub const fn is_full(&self) -> bool { N == self.len() } + /// Get the remaining capacity of the array pub const fn remaining_cap(&self) -> usize { self.capacity() - self.len() } + /// Set the length of the array + /// + /// ## Safety + /// This is one of those, use to leak memory functions. If you change the length, + /// you'll be reading random garbage from the memory and doing a double-free on drop pub unsafe fn set_len(&mut self, len: usize) { self.init_len = len as u16; // lossy cast, we maintain all invariants } + /// Get the array as a mut ptr unsafe fn as_mut_ptr(&mut self) -> *mut T { self.stack.as_mut_ptr() as *mut _ } + /// Get the array as a const ptr unsafe fn as_ptr(&self) -> *const T { self.stack.as_ptr() as *const _ } + /// Push an element into the array **without any bounds checking**. + /// + /// ## Safety + /// This function is **so unsafe** that you possibly don't want to call it, or + /// even think about calling it. You can end up corrupting your own stack or + /// other's valuable data pub unsafe fn push_unchecked(&mut self, element: T) { let len = self.len(); ptr::write(self.as_mut_ptr().add(len), element); self.set_len(len + 1); } + /// This is a nice version of a push that does bound checks pub fn push_panic(&mut self, element: T) -> Result<(), ()> { if self.len() < N { // so we can push it in @@ -124,9 +158,12 @@ impl Array { Err(()) } } + /// This is a _panicky_ but safer alternative to `push_unchecked` that panics on + /// incorrect lengths pub fn push(&mut self, element: T) { self.push_panic(element).unwrap(); } + /// Pop an item off the array pub fn pop(&mut self) -> Option { if self.len() == 0 { // nothing here @@ -140,6 +177,8 @@ impl Array { } } } + /// Truncate the array to a given size. This is super safe and doesn't even panic + /// if you provide a silly `new_len`. pub fn truncate(&mut self, new_len: usize) { let len = self.len(); if new_len < len { @@ -153,9 +192,11 @@ impl Array { } } } + /// Empty the internal array pub fn clear(&mut self) { self.truncate(0) } + /// Extend self from a slice pub fn extend_from_slice(&mut self, slice: &[T]) -> Result<(), ()> where T: Copy, @@ -164,14 +205,25 @@ impl Array { // no more space here return Err(()); } - let self_len = self.len(); - let other_len = slice.len(); unsafe { - ptr::copy_nonoverlapping(slice.as_ptr(), self.as_mut_ptr().add(self_len), other_len); - self.set_len(self_len + other_len); + self.extend_from_slice_unchecked(slice); } Ok(()) } + /// Extend self from a slice without doing a single check + /// + /// ## Safety + /// This function is just very very and. You can write giant things into your own + /// stack corrupting it, corrupting other people's things and creating undefined + /// behavior like no one else. + pub unsafe fn extend_from_slice_unchecked(&mut self, slice: &[T]) { + let self_len = self.len(); + let other_len = slice.len(); + ptr::copy_nonoverlapping(slice.as_ptr(), self.as_mut_ptr().add(self_len), other_len); + self.set_len(self_len + other_len); + } + /// Returns self as a `[T; N]` array if it is fully initialized. Else it will again return + /// itself pub fn into_array(self) -> Result<[T; N], Self> { if self.len() < self.capacity() { // not fully initialized @@ -188,9 +240,12 @@ impl Array { } // these operations are incredibly safe because we only pass the initialized part // of the array + /// Get self as a slice. Super safe because we guarantee that all the other invarians + /// are upheld fn as_slice(&self) -> &[T] { unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) } } + /// Get self as a mutable slice. Super safe (see comment above) fn as_slice_mut(&mut self) -> &mut [T] { unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) } } @@ -262,6 +317,12 @@ impl IntoIterator for Array { } impl Array { + /// Extend self using an iterator. + /// + /// ## Safety + /// This function can cause undefined damage to your application's stack and/or other's + /// data. Only use if you know what you're doing. If you don't use `extend_from_iter` + /// instead pub unsafe fn extend_from_iter_unchecked(&mut self, iterable: I) where I: IntoIterator, @@ -283,11 +344,42 @@ impl Array { } } } + pub fn extend_from_iter(&mut self, iterable: I) + where + I: IntoIterator, + { + unsafe { + // the ptr to start writing from + let mut ptr = Self::as_mut_ptr(self).add(self.len()); + let end_ptr = Self::as_ptr(self).add(self.remaining_cap()); + let mut guard = LenScopeGuard::new(&mut self.init_len); + let mut iter = iterable.into_iter(); + loop { + if let Some(element) = iter.next() { + // write the element + ptr.write(element); + // move to the next location + ptr = ptr.add(1); + // tell the guard to increment + guard.incr(1); + if end_ptr == ptr { + // our current ptr points to the end of the allocation + // oh no, time for corruption, if the user says so + panic!("Overflowed stack area.") + } + } else { + return; + } + } + } + } } impl Extend for Array { fn extend>(&mut self, iter: I) { - unsafe { self.extend_from_iter_unchecked(iter) } + { + self.extend_from_iter::<_>(iter) + } } } diff --git a/server/src/coredb/iarray.rs b/server/src/coredb/iarray.rs index 71e297f1..a0faf53b 100644 --- a/server/src/coredb/iarray.rs +++ b/server/src/coredb/iarray.rs @@ -49,8 +49,11 @@ use serde::{ }; use std::alloc as std_alloc; +/// An arbitrary trait used for identifying something as a contiguous block of memory pub trait MemoryBlock { + /// The type that will be used for the memory layout type LayoutItem; + /// The number of _units_ this memory block has fn size() -> usize; } @@ -61,48 +64,68 @@ impl MemoryBlock for [T; N] { } } +/// An union that either holds a stack (ptr) or a heap +/// +/// ## Safety +/// If you're trying to access a field without knowing the most recently created one, +/// behavior is undefined. pub union InlineArray { + /// the stack stack: ManuallyDrop>, + /// a pointer to the heap allocation and the allocation size heap_ptr_len: (*mut A::LayoutItem, usize), } impl InlineArray { + /// Get's the stack pointer. This is unsafe because it is not guranteed that the + /// stack pointer field is valid and the caller has to uphold this gurantee unsafe fn stack_ptr(&self) -> *const A::LayoutItem { self.stack.as_ptr() as *const _ } + /// Safe as `stack_ptr`, but returns a mutable pointer unsafe fn stack_ptr_mut(&mut self) -> *mut A::LayoutItem { self.stack.as_mut_ptr() as *mut _ } + /// Create a new union from a stack fn from_stack(stack: MaybeUninit) -> Self { Self { stack: ManuallyDrop::new(stack), } } + /// Create a new union from a heap (allocated). fn from_heap_ptr(start_ptr: *mut A::LayoutItem, len: usize) -> Self { Self { heap_ptr_len: (start_ptr, len), } } + /// Returns the allocation size of the heap unsafe fn heap_size(&self) -> usize { self.heap_ptr_len.1 } + /// Returns a raw ptr to the heap unsafe fn heap_ptr(&self) -> *const A::LayoutItem { self.heap_ptr_len.0 } + /// Returns a mut ptr to the heap unsafe fn heap_ptr_mut(&mut self) -> *mut A::LayoutItem { self.heap_ptr_len.0 as *mut _ } + /// Returns a mut ref to the heap allocation size unsafe fn heap_size_mut(&mut self) -> &mut usize { &mut self.heap_ptr_len.1 } + /// Returns the entire heap field unsafe fn heap(&self) -> (*mut A::LayoutItem, usize) { self.heap_ptr_len } + /// Returns a mutable reference to the entire heap field unsafe fn heap_mut(&mut self) -> (*mut A::LayoutItem, &mut usize) { (self.heap_ptr_mut(), &mut self.heap_ptr_len.1) } } +/// An utility tool for calculating the memory layout for a given `T`. Handles +/// any possible overflows pub fn calculate_memory_layout(count: usize) -> Result { let size = mem::size_of::().checked_mul(count).ok_or(())?; // err is cap overflow @@ -192,6 +215,7 @@ impl IArray { } } } + /// Returns the total capacity of the inline stack fn stack_capacity() -> usize { if mem::size_of::() > 0 { // not a ZST, so cap of array @@ -201,6 +225,7 @@ impl IArray { usize::MAX } } + /// Helper function that returns a ptr to the data, the len and the capacity fn meta_triple(&self) -> DataptrLenptrCapacity { unsafe { if unlikely(self.went_off_stack()) { @@ -212,6 +237,7 @@ impl IArray { } } } + /// Mutable version of `meta_triple` fn meta_triple_mut(&mut self) -> DataptrLenptrCapacityMut { unsafe { if unlikely(self.went_off_stack()) { @@ -228,6 +254,7 @@ impl IArray { } } } + /// Returns a raw ptr to the data fn get_data_ptr_mut(&mut self) -> *mut A::LayoutItem { if unlikely(self.went_off_stack()) { // get the heap ptr @@ -237,9 +264,11 @@ impl IArray { unsafe { self.store.stack_ptr_mut() } } } + /// Returns true if the allocation is now on the heap fn went_off_stack(&self) -> bool { self.cap > Self::stack_capacity() } + /// Returns the length pub fn len(&self) -> usize { if unlikely(self.went_off_stack()) { // so we're off the stack @@ -249,9 +278,11 @@ impl IArray { self.cap } } + /// Returns true if the IArray is empty pub fn is_empty(&self) -> bool { self.len() == 0 } + /// Returns the capacity fn get_capacity(&self) -> usize { if unlikely(self.went_off_stack()) { self.cap @@ -259,6 +290,8 @@ impl IArray { Self::stack_capacity() } } + /// Grow the allocation, if required, to make space for a total of `new_cap` + /// elements fn grow_block(&mut self, new_cap: usize) { // infallible unsafe { @@ -299,6 +332,7 @@ impl IArray { } } } + /// Reserve space for `additional` elements fn reserve(&mut self, additional: usize) { let (_, &mut len, cap) = self.meta_triple_mut(); if cap - len >= additional { @@ -311,6 +345,7 @@ impl IArray { .expect("Capacity overflow"); self.grow_block(new_cap) } + /// Push an element into this IArray pub fn push(&mut self, val: A::LayoutItem) { unsafe { let (mut data_ptr, mut len, cap) = self.meta_triple_mut(); @@ -324,6 +359,7 @@ impl IArray { *len += 1; } } + /// Pop an element off this IArray pub fn pop(&mut self) -> Option { unsafe { let (data_ptr, len_mut, _cap) = self.meta_triple_mut(); @@ -340,6 +376,8 @@ impl IArray { } } } + /// Shrink this IArray so that it only occupies the required space and not anything + /// more pub fn shrink(&mut self) { if unlikely(self.went_off_stack()) { // it's off the stack, so no chance of moving back to the stack @@ -363,6 +401,7 @@ impl IArray { self.grow_block(current_len); } } + /// Truncate the IArray to a given length. This **will** call the destructors pub fn truncate(&mut self, target_len: usize) { unsafe { let (data_ptr, len_mut, _cap) = self.meta_triple_mut(); @@ -376,10 +415,14 @@ impl IArray { } } } + /// Clear the internal store pub fn clear(&mut self) { // chop off the whole place self.truncate(0); } + /// Set the len, **without calling the destructor**. This is the ultimate function + /// to make valgrind unhappy, that is, **you can create memory leaks** if you don't + /// destroy the elements yourself unsafe fn set_len(&mut self, new_len: usize) { let (_dataptr, len_mut, _cap) = self.meta_triple_mut(); *len_mut = new_len; @@ -390,6 +433,8 @@ impl IArray where A::LayoutItem: Copy, { + /// Create an IArray from a slice by copying the elements of the slice into + /// the IArray pub fn from_slice(slice: &[A::LayoutItem]) -> Self { // FIXME(@ohsayan): Could we have had this as a From::from() method? let slice_len = slice.len(); @@ -419,6 +464,7 @@ where } } } + /// Insert a slice at the given index pub fn insert_slice_at_index(&mut self, slice: &[A::LayoutItem], index: usize) { self.reserve(slice.len()); let len = self.len(); @@ -434,10 +480,12 @@ where self.set_len(len + slice.len()); } } + /// Extend the IArray by using a slice pub fn extend_from_slice(&mut self, slice: &[A::LayoutItem]) { // at our len because we're appending it to the end self.insert_slice_at_index(slice, self.len()) } + /// Create a new IArray from a pre-defined stack pub fn from_stack(stack: A) -> Self { Self { cap: A::size(),