Skip to content

Commit

Permalink
Avoid double-drops when clone panics in slice push. Fixes #5
Browse files Browse the repository at this point in the history
  • Loading branch information
thepowersgang committed Feb 23, 2021
1 parent 807e9d4 commit 2a4d538
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 11 deletions.
35 changes: 24 additions & 11 deletions src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ impl<T: ?Sized, D: ::DataBuf> StackA<T, D> {
mem::size_of::<&T>() / mem::size_of::<usize>() - 1
}

fn push_inner(&mut self, fat_ptr: &T) -> Result<&mut [usize], ()> {
/// Returns the metadata and data slots
fn push_inner(&mut self, fat_ptr: &T) -> Result<(&mut [usize],&mut [usize]), ()> {
let bytes = mem::size_of_val(fat_ptr);
let words = super::round_to_words(bytes) + Self::meta_words();
// Check if there is sufficient space for the new item
Expand All @@ -71,7 +72,7 @@ impl<T: ?Sized, D: ::DataBuf> StackA<T, D> {
meta.clone_from_slice(&ptr_words[1..]);

// Increment offset and return
Ok(rv)
Ok( (meta, rv) )
} else {
Err(())
}
Expand All @@ -94,7 +95,7 @@ impl<T: ?Sized, D: ::DataBuf> StackA<T, D> {
);

match self.push_inner(f(&v)) {
Ok(d) => {
Ok((_,d)) => {
// SAFE: Destination address is valid
unsafe {
ptr::write(d.as_mut_ptr() as *mut U, v);
Expand Down Expand Up @@ -143,20 +144,32 @@ impl<T: ?Sized, D: ::DataBuf> StackA<T, D> {
impl<D: ::DataBuf> StackA<str, D> {
/// Push the contents of a string slice as an item onto the stack
pub fn push_str(&mut self, v: &str) -> Result<(), ()> {
self.push_inner(v).map(|d| unsafe {
self.push_inner(v).map(|(_,d)| unsafe {
ptr::copy(v.as_bytes().as_ptr(), d.as_mut_ptr() as *mut u8, v.len());
})
}
}
impl<D: ::DataBuf, T: Clone> StackA<[T], D> {
/// Pushes a set of items (cloning out of the input slice)
pub fn push_cloned(&mut self, v: &[T]) -> Result<(), ()> {
self.push_inner(&v).map(|d| unsafe {
let mut ptr = d.as_mut_ptr() as *mut T;
for val in v {
ptr::write(ptr, val.clone());
ptr = ptr.offset(1);
}
})
let (meta,d) = self.push_inner(&v)?;
// Prepare the slot with zeros (as if it's an empty slice)
// The length is updated as each item is written
// - This ensures that there's no drop issues during write
meta[0] = 0;
for v in d.iter_mut() {
*v = 0;
}

unsafe {
let mut ptr = d.as_mut_ptr() as *mut T;
for val in v {
ptr::write(ptr, val.clone());
meta[0] += 1;
ptr = ptr.offset(1);
}
}

Ok( () )
}
}
31 changes: 31 additions & 0 deletions tests/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,34 @@ fn destructors() {
drop(stack);
assert_eq!(v.get(), 2 + 3);
}

#[test]
fn slice_push_panic_safety() {
use std::sync::atomic::{AtomicUsize,Ordering};
static COUNT: AtomicUsize = AtomicUsize::new(0);
struct Sentinel(bool);
impl Clone for Sentinel {
fn clone(&self) -> Self {
if self.0 {
panic!();
}
else {
Sentinel(self.0)
}
}
}
impl Drop for Sentinel {
fn drop(&mut self) {
COUNT.fetch_add(1, Ordering::SeqCst);
}
}
let input = [Sentinel(false), Sentinel(true)];


let _ = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
let mut stack = ::stack_dst::StackA::<[Sentinel], [usize; 8]>::new();
let _ = stack.push_cloned(&input);
}));
assert_eq!(COUNT.load(Ordering::SeqCst), 1);
}

0 comments on commit 2a4d538

Please sign in to comment.