about summary refs log tree commit diff
path: root/src/liballoc/raw_vec.rs
diff options
context:
space:
mode:
authorTim Diekmann <tim.diekmann@3dvision.de>2020-03-26 17:11:47 +0100
committerTim Diekmann <tim.diekmann@3dvision.de>2020-03-26 17:11:47 +0100
commit2526accdd35c564eee80b6453a0b4965e6a76afd (patch)
tree76705d1a424dc9682b2e1c2599db6a0985d25335 /src/liballoc/raw_vec.rs
parent56cbf2f22aeb6448acd7eb49e9b2554c80bdbf79 (diff)
Fix issues from review and unsoundness of `RawVec::into_box`
Diffstat (limited to 'src/liballoc/raw_vec.rs')
-rw-r--r--src/liballoc/raw_vec.rs228
1 files changed, 114 insertions, 114 deletions
diff --git a/src/liballoc/raw_vec.rs b/src/liballoc/raw_vec.rs
index 3a108adb218..aee2367bd95 100644
--- a/src/liballoc/raw_vec.rs
+++ b/src/liballoc/raw_vec.rs
@@ -1,6 +1,7 @@
 #![unstable(feature = "raw_vec_internals", reason = "implementation detail", issue = "none")]
 #![doc(hidden)]
 
+use core::alloc::MemoryBlock;
 use core::cmp;
 use core::mem::{self, MaybeUninit};
 use core::ops::Drop;
@@ -24,6 +25,9 @@ mod tests;
 /// involved. This type is excellent for building your own data structures like Vec and VecDeque.
 /// In particular:
 ///
+/// * Produces `Unique::empty()` on zero-sized types.
+/// * Produces `Unique::empty()` on zero-length allocations.
+/// * Avoids freeing `Unique::empty()`.
 /// * Catches all overflows in capacity computations (promotes them to "capacity overflow" panics).
 /// * Guards against 32-bit systems allocating more than isize::MAX bytes.
 /// * Guards against overflowing your length.
@@ -44,38 +48,7 @@ mod tests;
 pub struct RawVec<T, A: AllocRef = Global> {
     ptr: Unique<T>,
     cap: usize,
-    a: A,
-}
-
-impl<T, A: AllocRef> RawVec<T, A> {
-    /// Like `new`, but parameterized over the choice of allocator for
-    /// the returned `RawVec`.
-    pub const fn new_in(a: A) -> Self {
-        // `cap: 0` means "unallocated". zero-sized allocations are handled by `AllocRef`
-        Self { ptr: Unique::empty(), cap: 0, a }
-    }
-
-    /// Like `with_capacity`, but parameterized over the choice of
-    /// allocator for the returned `RawVec`.
-    #[inline]
-    pub fn with_capacity_in(capacity: usize, a: A) -> Self {
-        Self::allocate_in(capacity, Uninitialized, a)
-    }
-
-    /// Like `with_capacity_zeroed`, but parameterized over the choice
-    /// of allocator for the returned `RawVec`.
-    #[inline]
-    pub fn with_capacity_zeroed_in(capacity: usize, a: A) -> Self {
-        Self::allocate_in(capacity, Zeroed, a)
-    }
-
-    fn allocate_in(capacity: usize, init: AllocInit, mut a: A) -> Self {
-        let layout = Layout::array::<T>(capacity).unwrap_or_else(|_| capacity_overflow());
-        alloc_guard(layout.size()).unwrap_or_else(|_| capacity_overflow());
-
-        let (ptr, excess) = a.alloc(layout, init).unwrap_or_else(|_| handle_alloc_error(layout));
-        Self { ptr: ptr.cast().into(), cap: Self::capacity_from_bytes(excess), a }
-    }
+    alloc: A,
 }
 
 impl<T> RawVec<T, Global> {
@@ -126,23 +99,7 @@ impl<T> RawVec<T, Global> {
     pub fn with_capacity_zeroed(capacity: usize) -> Self {
         Self::with_capacity_zeroed_in(capacity, Global)
     }
-}
 
-impl<T, A: AllocRef> RawVec<T, A> {
-    /// Reconstitutes a `RawVec` from a pointer, capacity, and allocator.
-    ///
-    /// # Undefined Behavior
-    ///
-    /// The `ptr` must be allocated (via the given allocator `a`), and with the given `capacity`.
-    /// The `capacity` cannot exceed `isize::MAX` (only a concern on 32-bit systems).
-    /// If the `ptr` and `capacity` come from a `RawVec` created via `a`, then this is guaranteed.
-    #[inline]
-    pub unsafe fn from_raw_parts_in(ptr: *mut T, capacity: usize, a: A) -> Self {
-        Self { ptr: Unique::new_unchecked(ptr), cap: capacity, a }
-    }
-}
-
-impl<T> RawVec<T, Global> {
     /// Reconstitutes a `RawVec` from a pointer and capacity.
     ///
     /// # Undefined Behavior
@@ -166,6 +123,55 @@ impl<T> RawVec<T, Global> {
 }
 
 impl<T, A: AllocRef> RawVec<T, A> {
+    /// Like `new`, but parameterized over the choice of allocator for
+    /// the returned `RawVec`.
+    pub const fn new_in(alloc: A) -> Self {
+        // `cap: 0` means "unallocated". zero-sized types are ignored.
+        Self { ptr: Unique::empty(), cap: 0, alloc }
+    }
+
+    /// Like `with_capacity`, but parameterized over the choice of
+    /// allocator for the returned `RawVec`.
+    #[inline]
+    pub fn with_capacity_in(capacity: usize, alloc: A) -> Self {
+        Self::allocate_in(capacity, Uninitialized, alloc)
+    }
+
+    /// Like `with_capacity_zeroed`, but parameterized over the choice
+    /// of allocator for the returned `RawVec`.
+    #[inline]
+    pub fn with_capacity_zeroed_in(capacity: usize, alloc: A) -> Self {
+        Self::allocate_in(capacity, Zeroed, alloc)
+    }
+
+    fn allocate_in(capacity: usize, init: AllocInit, mut alloc: A) -> Self {
+        if mem::size_of::<T>() == 0 {
+            Self::new_in(alloc)
+        } else {
+            let layout = Layout::array::<T>(capacity).unwrap_or_else(|_| capacity_overflow());
+            alloc_guard(layout.size()).unwrap_or_else(|_| capacity_overflow());
+
+            let memory = alloc.alloc(layout, init).unwrap_or_else(|_| handle_alloc_error(layout));
+            Self {
+                ptr: memory.ptr().cast().into(),
+                cap: Self::capacity_from_bytes(memory.size()),
+                alloc,
+            }
+        }
+    }
+
+    /// Reconstitutes a `RawVec` from a pointer, capacity, and allocator.
+    ///
+    /// # Undefined Behavior
+    ///
+    /// The `ptr` must be allocated (via the given allocator `a`), and with the given `capacity`.
+    /// The `capacity` cannot exceed `isize::MAX` (only a concern on 32-bit systems).
+    /// If the `ptr` and `capacity` come from a `RawVec` created via `a`, then this is guaranteed.
+    #[inline]
+    pub unsafe fn from_raw_parts_in(ptr: *mut T, capacity: usize, a: A) -> Self {
+        Self { ptr: Unique::new_unchecked(ptr), cap: capacity, alloc: a }
+    }
+
     /// Gets a raw pointer to the start of the allocation. Note that this is
     /// `Unique::empty()` if `capacity == 0` or `T` is zero-sized. In the former case, you must
     /// be careful.
@@ -183,16 +189,16 @@ impl<T, A: AllocRef> RawVec<T, A> {
 
     /// Returns a shared reference to the allocator backing this `RawVec`.
     pub fn alloc(&self) -> &A {
-        &self.a
+        &self.alloc
     }
 
     /// Returns a mutable reference to the allocator backing this `RawVec`.
     pub fn alloc_mut(&mut self) -> &mut A {
-        &mut self.a
+        &mut self.alloc
     }
 
-    fn current_layout(&self) -> Option<Layout> {
-        if self.cap == 0 {
+    fn current_memory(&self) -> Option<MemoryBlock> {
+        if mem::size_of::<T>() == 0 || self.cap == 0 {
             None
         } else {
             // We have an allocated chunk of memory, so we can bypass runtime
@@ -200,7 +206,8 @@ impl<T, A: AllocRef> RawVec<T, A> {
             unsafe {
                 let align = mem::align_of::<T>();
                 let size = mem::size_of::<T>() * self.cap;
-                Some(Layout::from_size_align_unchecked(size, align))
+                let layout = Layout::from_size_align_unchecked(size, align);
+                Some(MemoryBlock::new(self.ptr.cast().into(), layout))
             }
         }
     }
@@ -454,14 +461,19 @@ impl<T, A: AllocRef> RawVec<T, A> {
     /// Returns if the buffer needs to grow to fulfill the needed extra capacity.
     /// Mainly used to make inlining reserve-calls possible without inlining `grow`.
     fn needs_to_grow(&self, used_capacity: usize, needed_extra_capacity: usize) -> bool {
-        needed_extra_capacity > self.capacity().wrapping_sub(used_capacity)
+        mem::size_of::<T>() != 0
+            && needed_extra_capacity > self.capacity().wrapping_sub(used_capacity)
     }
 
     fn capacity_from_bytes(excess: usize) -> usize {
-        match mem::size_of::<T>() {
-            0 => usize::MAX,
-            elem_size => excess / elem_size,
-        }
+        debug_assert_ne!(mem::size_of::<T>(), 0);
+        excess / mem::size_of::<T>()
+    }
+
+    fn set_memory(&mut self, memory: MemoryBlock) {
+        self.ptr = memory.ptr().cast().into();
+        self.cap = Self::capacity_from_bytes(memory.size());
+        drop(memory);
     }
 
     /// Single method to handle all possibilities of growing the buffer.
@@ -471,9 +483,9 @@ impl<T, A: AllocRef> RawVec<T, A> {
         placement: ReallocPlacement,
         init: AllocInit,
     ) -> Result<(), TryReserveError> {
-        let elem_size = mem::size_of::<T>();
-        let new_layout = match strategy {
+        let layout = match strategy {
             Double => unsafe {
+                let elem_size = mem::size_of::<T>();
                 if elem_size == 0 {
                     // Since we return a capacity of `usize::MAX` when `elem_size` is
                     // 0, getting to here necessarily means the `RawVec` is overfull.
@@ -511,24 +523,24 @@ impl<T, A: AllocRef> RawVec<T, A> {
             }
         };
 
-        let allocation = if let Some(old_layout) = self.current_layout() {
-            debug_assert!(old_layout.align() == new_layout.align());
+        let memory = if let Some(mut memory) = self.current_memory() {
+            debug_assert_eq!(memory.align(), layout.align());
             unsafe {
-                self.a.grow(self.ptr.cast().into(), old_layout, new_layout.size(), placement, init)
-            }
+                self.alloc
+                    .grow(&mut memory, layout.size(), placement, init)
+                    .map_err(|_| AllocError { layout, non_exhaustive: () })?
+            };
+            memory
         } else {
             match placement {
-                MayMove => self.a.alloc(new_layout, init),
+                MayMove => self.alloc.alloc(layout, init),
                 InPlace => Err(AllocErr),
             }
+            .map_err(|_| AllocError { layout, non_exhaustive: () })?
         };
 
-        allocation
-            .map(|(ptr, excess)| {
-                self.ptr = ptr.cast().into();
-                self.cap = Self::capacity_from_bytes(excess);
-            })
-            .map_err(|_| TryReserveError::AllocError { layout: new_layout, non_exhaustive: () })
+        self.set_memory(memory);
+        Ok(())
     }
 
     fn shrink(
@@ -538,64 +550,52 @@ impl<T, A: AllocRef> RawVec<T, A> {
     ) -> Result<(), TryReserveError> {
         assert!(amount <= self.cap, "Tried to shrink to a larger capacity");
 
-        let elem_size = mem::size_of::<T>();
-        let old_layout =
-            if let Some(layout) = self.current_layout() { layout } else { return Ok(()) };
-        let old_ptr = self.ptr.cast().into();
-        let new_size = amount * elem_size;
-
-        let allocation = unsafe {
-            if amount == 0 && placement == MayMove {
-                self.dealloc_buffer();
-                Ok((old_layout.dangling(), 0))
-            } else {
-                self.a.shrink(old_ptr, old_layout, new_size, placement)
-            }
-        };
+        let mut memory = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
+        let new_size = amount * mem::size_of::<T>();
 
-        allocation
-            .map(|(ptr, excess)| {
-                self.ptr = ptr.cast().into();
-                self.cap = Self::capacity_from_bytes(excess);
-            })
-            .map_err(|_| TryReserveError::AllocError {
-                layout: unsafe { Layout::from_size_align_unchecked(new_size, old_layout.align()) },
-                non_exhaustive: (),
-            })
+        unsafe {
+            self.alloc.shrink(&mut memory, new_size, placement).map_err(|_| {
+                TryReserveError::AllocError {
+                    layout: Layout::from_size_align_unchecked(new_size, memory.align()),
+                    non_exhaustive: (),
+                }
+            })?;
+        }
+
+        self.set_memory(memory);
+        Ok(())
     }
 }
 
 impl<T> RawVec<T, Global> {
-    /// Converts the entire buffer into `Box<[T]>`.
+    /// Converts the entire buffer into `Box<[T]>` with the specified `len`.
     ///
     /// Note that this will correctly reconstitute any `cap` changes
     /// that may have been performed. (See description of type for details.)
-    pub fn into_box(self) -> Box<[MaybeUninit<T>]> {
-        unsafe {
-            // NOTE: not calling `capacity()` here; actually using the real `cap` field!
-            let slice = slice::from_raw_parts_mut(self.ptr() as *mut MaybeUninit<T>, self.cap);
-            let output = Box::from_raw(slice);
-            mem::forget(self);
-            output
-        }
-    }
-}
+    ///
+    /// # Safety
+    ///
+    /// * `len` must be smaller than or equal to `self.capacity()`
+    pub unsafe fn into_box(self, len: usize) -> Box<[MaybeUninit<T>]> {
+        debug_assert!(
+            len <= self.capacity(),
+            "`len` must be smaller than or equal to `self.capacity()`"
+        );
 
-impl<T, A: AllocRef> RawVec<T, A> {
-    /// Frees the memory owned by the `RawVec` *without* trying to drop its contents.
-    pub unsafe fn dealloc_buffer(&mut self) {
-        if let Some(layout) = self.current_layout() {
-            self.a.dealloc(self.ptr.cast().into(), layout);
-            self.ptr = Unique::empty();
-            self.cap = 0;
-        }
+        // NOTE: not calling `capacity()` here; actually using the real `cap` field!
+        let slice = slice::from_raw_parts_mut(self.ptr() as *mut MaybeUninit<T>, len);
+        let output = Box::from_raw(slice);
+        mem::forget(self);
+        output
     }
 }
 
 unsafe impl<#[may_dangle] T, A: AllocRef> Drop for RawVec<T, A> {
     /// Frees the memory owned by the `RawVec` *without* trying to drop its contents.
     fn drop(&mut self) {
-        unsafe { self.dealloc_buffer() }
+        if let Some(memory) = self.current_memory() {
+            unsafe { self.alloc.dealloc(memory) }
+        }
     }
 }