about summary refs log tree commit diff
path: root/src/libcore/alloc
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/libcore/alloc
parent56cbf2f22aeb6448acd7eb49e9b2554c80bdbf79 (diff)
downloadrust-2526accdd35c564eee80b6453a0b4965e6a76afd.tar.gz
rust-2526accdd35c564eee80b6453a0b4965e6a76afd.zip
Fix issues from review and unsoundness of `RawVec::into_box`
Diffstat (limited to 'src/libcore/alloc')
-rw-r--r--src/libcore/alloc/mod.rs317
1 files changed, 137 insertions, 180 deletions
diff --git a/src/libcore/alloc/mod.rs b/src/libcore/alloc/mod.rs
index 0c5a70bee1a..e693f50846b 100644
--- a/src/libcore/alloc/mod.rs
+++ b/src/libcore/alloc/mod.rs
@@ -11,7 +11,8 @@ pub use self::global::GlobalAlloc;
 pub use self::layout::{Layout, LayoutErr};
 
 use crate::fmt;
-use crate::ptr::{self, NonNull};
+use crate::mem;
+use crate::ptr::{self, NonNull, Unique};
 
 /// The `AllocErr` error indicates an allocation failure
 /// that may be due to resource exhaustion or to
@@ -41,49 +42,91 @@ pub enum AllocInit {
     Zeroed,
 }
 
-impl AllocInit {
-    /// Initialize the memory block referenced by `ptr` and specified by `Layout`.
+/// Represents a block of allocated memory returned by an allocator.
+#[derive(Debug)]
+#[unstable(feature = "allocator_api", issue = "32838")]
+#[must_use = "`MemoryBlock` should be passed to `AllocRef::dealloc`"]
+pub struct MemoryBlock {
+    ptr: Unique<u8>,
+    layout: Layout,
+}
+
+impl MemoryBlock {
+    /// Creates a new `MemoryBlock`.
     ///
-    /// This behaves like calling [`AllocInit::initialize_offset(ptr, layout, 0)`][off].
+    /// # Safety
     ///
-    /// [off]: AllocInit::initialize_offset
+    /// * The block must be allocated with the same alignment as [`layout.align()`], and
+    /// * The provided [`layout.size()`] must fall in the range `min ..= max`, where:
+    ///   - `min` is the size requested size when allocating the block, and
+    ///   - `max` is the size of the memory block.
+    #[inline]
+    #[unstable(feature = "allocator_api", issue = "32838")]
+    pub const unsafe fn new(ptr: NonNull<u8>, layout: Layout) -> Self {
+        Self { ptr: Unique::new_unchecked(ptr.as_ptr()), layout }
+    }
+
+    /// Acquires the underlying `NonNull<u8>` pointer.
+    #[inline]
+    #[unstable(feature = "allocator_api", issue = "32838")]
+    pub const fn ptr(&self) -> NonNull<u8> {
+        // SAFETY: Unique<T> is always non-null
+        unsafe { NonNull::new_unchecked(self.ptr.as_ptr()) }
+    }
+
+    /// Returns the layout describing the memory block.
+    #[inline]
+    #[unstable(feature = "allocator_api", issue = "32838")]
+    pub const fn layout(&self) -> Layout {
+        self.layout
+    }
+
+    /// Returns the size of the memory block.
+    #[inline]
+    #[unstable(feature = "allocator_api", issue = "32838")]
+    pub const fn size(&self) -> usize {
+        self.layout().size()
+    }
+
+    /// Returns the minimum alignment of the memory block.
+    #[inline]
+    #[unstable(feature = "allocator_api", issue = "32838")]
+    pub const fn align(&self) -> usize {
+        self.layout().align()
+    }
+
+    /// Initialize the memory block like specified by `init`.
     ///
-    /// # Safety
+    /// This behaves like calling [`MemoryBlock::initialize_offset(ptr, layout, 0)`][off].
     ///
-    /// * `layout` must [*fit*] the block of memory referenced by `ptr`
+    /// [off]: MemoryBlock::init_offset
     ///
     /// [*fit*]: trait.AllocRef.html#memory-fitting
     #[inline]
     #[unstable(feature = "allocator_api", issue = "32838")]
-    pub unsafe fn initialize(self, ptr: NonNull<u8>, layout: Layout) {
-        self.initialize_offset(ptr, layout, 0)
+    pub fn init(&mut self, init: AllocInit) {
+        // SAFETY: 0 is always smaller or equal to the size
+        unsafe { self.init_offset(init, 0) }
     }
 
-    /// Initialize the memory block referenced by `ptr` and specified by `Layout` at the specified
-    /// `offset`.
+    /// Initialize the memory block like specified by `init` at the specified `offset`.
     ///
     /// This is a no-op for [`AllocInit::Uninitialized`] and writes zeroes for [`AllocInit::Zeroed`]
     /// at `ptr + offset` until `ptr + layout.size()`.
     ///
     /// # Safety
     ///
-    /// * `layout` must [*fit*] the block of memory referenced by `ptr`
-    ///
-    /// * `offset` must be smaller than or equal to `layout.size()`
+    /// * `offset` must be smaller than or equal to `size()`
     ///
     /// [*fit*]: trait.AllocRef.html#memory-fitting
+    #[inline]
     #[unstable(feature = "allocator_api", issue = "32838")]
-    pub unsafe fn initialize_offset(self, ptr: NonNull<u8>, layout: Layout, offset: usize) {
-        debug_assert!(
-            offset <= layout.size(),
-            "`offset` must be smaller than or equal to `layout.size()`"
-        );
-        match self {
+    pub unsafe fn init_offset(&mut self, init: AllocInit, offset: usize) {
+        debug_assert!(offset <= self.size(), "`offset` must be smaller than or equal to `size()`");
+        match init {
             AllocInit::Uninitialized => (),
             AllocInit::Zeroed => {
-                let new_ptr = ptr.as_ptr().add(offset);
-                let size = layout.size() - offset;
-                ptr::write_bytes(new_ptr, 0, size);
+                self.ptr().as_ptr().add(offset).write_bytes(0, self.size() - offset)
             }
         }
     }
@@ -116,70 +159,23 @@ pub enum ReallocPlacement {
 ///
 /// Unlike [`GlobalAlloc`][], zero-sized allocations are allowed in `AllocRef`. If an underlying
 /// allocator does not support this (like jemalloc) or return a null pointer (such as
-/// `libc::malloc`), this case must be caught. [`Layout::dangling()`][] then can be used to create
-/// an aligned `NonNull<u8>`.
-///
-/// ### Currently allocated memory
-///
-/// Some of the methods require that a memory block be *currently allocated* via an allocator. This
-/// means that:
-///
-/// * the starting address for that memory block was previously returned by [`alloc`], [`grow`], or
-///   [`shrink`], and
-///
-/// * the memory block has not been subsequently deallocated, where blocks are either deallocated
-///   directly by being passed to [`dealloc`] or were changed by being passed to [`grow`] or
-///   [`shrink`] that returns `Ok`. If `grow` or `shrink` have returned `Err`, the passed pointer
-///   remains valid.
-///
-/// [`alloc`]: AllocRef::alloc
-/// [`grow`]: AllocRef::grow
-/// [`shrink`]: AllocRef::shrink
-/// [`dealloc`]: AllocRef::dealloc
-///
-/// ### Memory fitting
-///
-/// Some of the methods require that a layout *fit* a memory block. What it means for a layout to
-/// "fit" a memory block means (or equivalently, for a memory block to "fit" a layout) is that the
-/// following conditions must hold:
-///
-/// * The block must be allocated with the same alignment as [`layout.align()`], and
-///
-/// * The provided [`layout.size()`] must fall in the range `min ..= max`, where:
-///   - `min` is the size of the layout most recently used to allocate the block, and
-///   - `max` is the latest actual size returned from [`alloc`], [`grow`], or [`shrink`].
-///
-/// [`layout.align()`]: Layout::align
-/// [`layout.size()`]: Layout::size
-///
-/// ### Notes
-///
-///  * if a layout `k` fits a memory block (denoted by `ptr`) currently allocated via an allocator
-///    `a`, then it is legal to use that layout to deallocate it, i.e.,
-///    [`a.dealloc(ptr, k);`][`dealloc`], and
-///
-///  * if an allocator does not support overallocating, it is fine to simply return
-///    [`layout.size()`] as the actual size.
+/// `libc::malloc`), this case must be caught.
 ///
 /// # Safety
 ///
-/// * Pointers returned from an allocator must point to valid memory and retain their validity until
-///   the instance and all of its clones are dropped,
-///
-/// * cloning or moving the allocator must not invalidate pointers returned from this allocator.
-///   A cloned allocator must behave like the same allocator, and
+/// * Memory blocks returned from an allocator must point to valid memory and retain their validity
+///   until the instance and all of its clones are dropped, and
 ///
-/// * any pointer to a memory block which is [*currently allocated*] may be passed to any other
-///   method of the allocator.
+/// * cloning or moving the allocator must not invalidate memory blocks returned from this
+///   allocator. A cloned allocator must behave like the same allocator.
 ///
 /// [*currently allocated*]: #currently-allocated-memory
 #[unstable(feature = "allocator_api", issue = "32838")]
 pub unsafe trait AllocRef {
-    /// On success, returns a pointer meeting the size and alignment guarantees of `layout` and the
-    /// actual size of the allocated block, which is greater than or equal to `layout.size()`.
+    /// On success, returns a memory block meeting the size and alignment guarantees of `layout`.
     ///
-    /// The returned block of storage is initialized as specified by [`init`], all the way up to
-    /// the returned `actual_size`.
+    /// The returned block may have a larger size than specified by `layout.size()` and is
+    /// initialized as specified by [`init`], all the way up to the returned size of the block.
     ///
     /// [`init`]: AllocInit
     ///
@@ -196,58 +192,32 @@ pub unsafe trait AllocRef {
     /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
     ///
     /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
-    fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<(NonNull<u8>, usize), AllocErr>;
+    fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr>;
 
-    /// Deallocates the memory referenced by `ptr`.
+    /// Deallocates the memory denoted by `memory`.
     ///
     /// # Safety
     ///
-    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator,
-    ///
-    /// * `layout` must [*fit*] that block of memory, and
-    ///
-    /// * the alignment of the `layout` must match the alignment used to allocate that block of
-    ///   memory.
-    ///
-    /// [*currently allocated*]: #currently-allocated-memory
-    /// [*fit*]: #memory-fitting
-    unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout);
+    /// `memory` must be a memory block returned by this allocator.
+    unsafe fn dealloc(&mut self, memory: MemoryBlock);
 
-    /// Attempts to extend the allocation referenced by `ptr` to fit `new_size`.
-    ///
-    /// Returns a pointer and the actual size of the allocated block. The pointer is suitable for
-    /// holding data described by a new layout with `layout`’s alignment and a size given by
-    /// `new_size`. To accomplish this, the allocator may extend the allocation referenced by `ptr`
-    /// to fit the new layout.
-    ///
-    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
-    /// transferred to this allocator. The memory may or may not have been freed, and should be
-    /// considered unusable (unless of course it was transferred back to the caller again via the
-    /// return value of this method).
-    ///
-    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
-    /// this allocator, and the contents of the memory block are unaltered.
+    /// Attempts to extend the memory block.
     ///
     /// The behavior of how the allocator tries to grow the memory is specified by [`placement`].
-    /// The first `layout.size()` bytes of memory are preserved or copied as appropriate from `ptr`,
-    /// and the remaining bytes, from `layout.size()` to the returned actual size, are initialized
-    /// according to [`init`].
+    /// The first `memory.size()` bytes are preserved or copied as appropriate from `ptr`, and the
+    /// remaining bytes up to the new `memory.size()` are initialized according to [`init`].
     ///
     /// [`placement`]: ReallocPlacement
     /// [`init`]: AllocInit
     ///
     /// # Safety
     ///
-    /// * `ptr` must be [*currently allocated*] via this allocator,
-    ///
-    /// * `layout` must [*fit*] the `ptr`. (The `new_size` argument need not fit it.)
-    ///
-    // We can't require that `new_size` is strictly greater than `layout.size()` because of ZSTs.
+    /// * `memory` must be a memory block returned by this allocator.
+    // We can't require that `new_size` is strictly greater than `memory.size()` because of ZSTs.
     // An alternative would be
-    // * `new_size must be strictly greater than `layout.size()` or both are zero
-    /// * `new_size` must be greater than or equal to `layout.size()`
-    ///
-    /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must not overflow
+    // * `new_size must be strictly greater than `memory.size()` or both are zero
+    /// * `new_size` must be greater than or equal to `memory.size()`
+    /// * `new_size`, when rounded up to the nearest multiple of `memory.align()`, must not overflow
     ///   (i.e., the rounded value must be less than `usize::MAX`).
     ///
     /// [*currently allocated*]: #currently-allocated-memory
@@ -268,64 +238,50 @@ pub unsafe trait AllocRef {
     /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
     unsafe fn grow(
         &mut self,
-        ptr: NonNull<u8>,
-        layout: Layout,
+        memory: &mut MemoryBlock,
         new_size: usize,
         placement: ReallocPlacement,
         init: AllocInit,
-    ) -> Result<(NonNull<u8>, usize), AllocErr> {
-        let old_size = layout.size();
-        debug_assert!(
-            new_size >= old_size,
-            "`new_size` must be greater than or equal to `layout.size()`"
-        );
-
-        if new_size == old_size {
-            return Ok((ptr, new_size));
-        }
-
+    ) -> Result<(), AllocErr> {
         match placement {
+            ReallocPlacement::InPlace => Err(AllocErr),
             ReallocPlacement::MayMove => {
-                let (new_ptr, alloc_size) =
-                    self.alloc(Layout::from_size_align_unchecked(new_size, layout.align()), init)?;
-                ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size);
-                self.dealloc(ptr, layout);
-                Ok((new_ptr, alloc_size))
+                let old_size = memory.size();
+                debug_assert!(
+                    new_size >= old_size,
+                    "`new_size` must be greater than or equal to `memory.size()`"
+                );
+
+                if new_size == old_size {
+                    return Ok(());
+                }
+
+                let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
+                let new_memory = self.alloc(new_layout, init)?;
+                ptr::copy_nonoverlapping(
+                    memory.ptr().as_ptr(),
+                    new_memory.ptr().as_ptr(),
+                    old_size,
+                );
+                self.dealloc(mem::replace(memory, new_memory));
+                Ok(())
             }
-            ReallocPlacement::InPlace => Err(AllocErr),
         }
     }
 
-    /// Attempts to shrink the allocation referenced by `ptr` to fit `new_size`.
+    /// Attempts to shrink the memory block.
     ///
-    /// Returns a pointer and the actual size of the allocated block. The pointer is suitable for
-    /// holding data described by a new layout with `layout`’s alignment and a size given by
-    /// `new_size`. To accomplish this, the allocator may shrink the allocation referenced by `ptr`
-    /// to fit the new layout.
-    ///
-    /// The behavior on how the allocator tries to shrink the memory can be specified by
-    /// [`placement`].
-    ///
-    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
-    /// transferred to this allocator. The memory may or may not have been freed, and should be
-    /// considered unusable unless it was transferred back to the caller again via the
-    /// return value of this method.
-    ///
-    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
-    /// this allocator, and the contents of the memory block are unaltered.
+    /// The behavior of how the allocator tries to shrink the memory is specified by [`placement`].
     ///
     /// [`placement`]: ReallocPlacement
     ///
     /// # Safety
     ///
-    /// * `ptr` must be [*currently allocated*] via this allocator,
-    ///
-    /// * `layout` must [*fit*] the `ptr`. (The `new_size` argument need not fit it.)
-    ///
-    // We can't require that `new_size` is strictly smaller than `layout.size()` because of ZSTs.
+    /// * `memory` must be a memory block returned by this allocator.
+    // We can't require that `new_size` is strictly smaller than `memory.size()` because of ZSTs.
     // An alternative would be
-    // * `new_size must be strictly smaller than `layout.size()` or both are zero
-    /// * `new_size` must be smaller than or equal to `layout.size()`
+    // * `new_size must be strictly smaller than `memory.size()` or both are zero
+    /// * `new_size` must be smaller than or equal to `memory.size()`
     ///
     /// [*currently allocated*]: #currently-allocated-memory
     /// [*fit*]: #memory-fitting
@@ -333,7 +289,7 @@ pub unsafe trait AllocRef {
     /// # Errors
     ///
     /// Returns `Err` if the new layout does not meet the allocator's size and alignment
-    /// constraints of the allocator, or if shrinking otherwise fails.
+    /// constraints of the allocator, or if growing otherwise fails.
     ///
     /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
     /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
@@ -345,32 +301,33 @@ pub unsafe trait AllocRef {
     /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
     unsafe fn shrink(
         &mut self,
-        ptr: NonNull<u8>,
-        layout: Layout,
+        memory: &mut MemoryBlock,
         new_size: usize,
         placement: ReallocPlacement,
-    ) -> Result<(NonNull<u8>, usize), AllocErr> {
-        let old_size = layout.size();
-        debug_assert!(
-            new_size <= old_size,
-            "`new_size` must be smaller than or equal to `layout.size()`"
-        );
-
-        if new_size == old_size {
-            return Ok((ptr, new_size));
-        }
-
+    ) -> Result<(), AllocErr> {
         match placement {
+            ReallocPlacement::InPlace => Err(AllocErr),
             ReallocPlacement::MayMove => {
-                let (new_ptr, alloc_size) = self.alloc(
-                    Layout::from_size_align_unchecked(new_size, layout.align()),
-                    AllocInit::Uninitialized,
-                )?;
-                ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_size);
-                self.dealloc(ptr, layout);
-                Ok((new_ptr, alloc_size))
+                let old_size = memory.size();
+                debug_assert!(
+                    new_size <= old_size,
+                    "`new_size` must be smaller than or equal to `layout.size()`"
+                );
+
+                if new_size == old_size {
+                    return Ok(());
+                }
+
+                let new_layout = Layout::from_size_align_unchecked(new_size, memory.align());
+                let new_memory = self.alloc(new_layout, AllocInit::Uninitialized)?;
+                ptr::copy_nonoverlapping(
+                    memory.ptr().as_ptr(),
+                    new_memory.ptr().as_ptr(),
+                    new_size,
+                );
+                self.dealloc(mem::replace(memory, new_memory));
+                Ok(())
             }
-            ReallocPlacement::InPlace => Err(AllocErr),
         }
     }
 }