cargo : allocator-api2 @ 0.2.21
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

datastructure-impl-boundsdatastructure-impl-correctdatastructure-impl-safedatastructure-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignunsafe-documentedunsafe-minimalunsafe-safeunsafe-testeduses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

Stable-Rust polyfill for unstable allocator_api: near-verbatim ports of std's Box/Vec/RawVec parameterised over Allocator. Three quality findings: medium (no active tests in the published crate despite extensive unsafe and a documented past Box::into_inner double-free fix), and two lows (empty CHANGELOG placeholder, published archive uses CRLF/CR while upstream uses LF — content byte-identical after normalisation).

Report

Subject

allocator-api2 is a polyfill that mirrors Rust's unstable allocator_api feature (the Allocator trait plus Global / System allocators and allocator-parameterised Box, Vec, RawVec, and supporting iterators) on stable Rust. It is intended for library authors who want to write allocator-aware code without requiring nightly toolchains. The README states the code is "mostly verbatim copy from rust repository", and the published source confirms this: Box, Vec, and RawVec are near line-for-line copies of their counterparts in rust-lang/rust's alloc crate.

Methodology

The published crate contents were compared against the upstream Git repository at the commit recorded in .cargo_vcs_info.json using diff. The only differences were Cargo's standard Cargo.toml normalisation, the addition of Cargo.toml.orig and .cargo_vcs_info.json, the removal of the tests/ workspace member, and line-ending differences (CRLF in the published archive vs LF in the VCS tree); after stripping carriage returns, source files matched byte-for-byte. The repository's git remote (https://github.com/zakarumych/allocator-api2) matches the [package].repository field in Cargo.toml.

All source files under contents/src/ (~8 kLoC, primarily boxed.rs 2272 lines, vec/mod.rs 3232 lines, raw_vec.rs 642 lines, alloc/mod.rs 416 lines) were read. Each unsafe block in the alloc/Global/System modules and the high-traffic helpers (Box::into_inner, Box::leak, Box::from_raw_in, RawVec::grow_*, Vec::set_len, Vec::truncate, Drain::drop, IntoIter construction/drop, SliceExt::to_vec_in's panic-safety guard) was inspected against the SAFETY comments and the documented Allocator contract.

The published archive was checked for binaries, build scripts, and proc macros via filesystem search and inspection of Cargo.toml. The full crate source was grepped for std::{env,fs,net,process,thread, io,sync} and for extern "C" to confirm the absence of I/O, FFI, and runtime side effects. The VCS workspace tests/ member was inspected. The test suite was not run, as the published crate ships no tests of its own (see FINDING-1).

Tools: openvet 0.6.0 for workspace and audit data management; diff (Apple) for the byte-level comparison; git (2.51) for the upstream checkout; file/tr for line-ending inspection; grep/ripgrep for capability surveys.

Results

The comparison between the published crate contents and the upstream Git repository shows that, after CRLF normalisation, every source file matches the recorded commit byte-for-byte; manifest differences are limited to cargo's standard normalisation.

The crate ships no binary artefacts, justifying has-binaries. There is no build.rs, Cargo.toml sets build = false, and the crate is not a proc-macro library, justifying has-build-exec and has-install-exec. The crate performs no I/O of any kind: std::io::{Read,Write,Seek,BufRead} appears only as trait forwarding impls on Box<T, A> (delegating to the inner type) and io::Write for Vec<u8, A> (appending bytes to the vector, matching the rustc stdlib impl). There is no use of std::{env,fs,net, process,thread,sync} and no FFI. This justifies uses-network, uses-filesystem, uses-environment, uses-exec, uses-concurrency, uses-crypto, uses-jit, and uses-interpreter. No cryptographic, parser, interpreter, JIT, protocol, algorithm, or concurrency implementation is present, justifying impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, and impl-concurrency.

The crate implements its own Box, Vec, RawVec, Unique, and IntoIter / Drain / Splice types on stable Rust, justifying impl-datastructure; these implementations contain extensive unsafe (raw-pointer arithmetic, ptr::drop_in_place, ptr::read, manual deallocation, custom Send/Sync impls), justifying uses-unsafe. The unsafe is concentrated where it is structurally required (the data-structure internals and the allocator vtable), supporting unsafe-minimal. Spot-checks against the documented Allocator safety contract, the SAFETY comments at each unsafe block in alloc/{mod,global,system}.rs, and the drop-safety guards in Drain::drop, IntoIter::drop, and SliceExt::to_vec_in found no unsoundness, supporting unsafe-safe, datastructure-impl-safe, and datastructure-impl-correct. Capacity-overflow handling in RawVec::grow_amortized / grow_exact (via Layout::array, alloc_guard, and exponential doubling) matches the rustc stdlib bounds for Vec, supporting datastructure-impl-bounds. Not every unsafe block carries an explicit SAFETY comment (e.g. Box::drop, Vec::clear, Vec::Drop), so unsafe-documented is asserted false.

The VCS history records a prior Fix the Box::into_inner's double free. (commits 5ba2f05 / dedb0ed) which would have been caught by a unit test; the current Box::into_inner is the corrected version. The published crate nonetheless ships zero tests of any kind: Cargo.toml sets autotests = false, no tests/ directory is shipped, and the VCS sibling workspace member allocator-api2-tests contains only four generic helper functions and a tests! macro intended for downstream allocator authors to exercise the trait contract — no active #[test] invocations live in this repository. This justifies has-unit-tests, has-integration-tests, has-fuzz-tests, has-property-tests, unsafe-tested, and datastructure-impl-tested all as false, and is recorded as finding FINDING-1 (medium severity, given the surface area of unsafe).

Two additional low-severity quality findings were recorded: FINDING-2 (the CHANGELOG.md contains only an ## [Unreleased] placeholder despite many releases) and FINDING-3 (the published archive has CRLF line endings while the VCS tree uses LF, with boxed.rs even showing mixed CRLF+CR terminators, suggesting the crate was packed in an environment with Git autocrlf enabled). Neither affects correctness.

No malicious patterns, obfuscation, data exfiltration, time-bombs, or target-specific payloads were found in the source. The code is a transparent mirror of rustc's alloc crate, justifying is-benign.

Conclusion

allocator-api2 faithfully mirrors a small, well-understood subset of rust-lang/rust's alloc crate to provide allocator-parameterised data structures on stable Rust. The implementations are sound under spot-checking and the crate has no build-time, install-time, or runtime side effects beyond heap allocation. The principal concern is the complete absence of a test suite for the crate's own Box / Vec / RawVec (FINDING-1) despite the volume of unsafe; downstream consumers relying on this crate as a Box / Vec substitute should be aware that they are trusting the manual port rather than a tested implementation.

Findings(3)

FINDING-1 quality medium

No unit, integration, fuzz, or property tests in the published crate

The published crate ships zero tests of its Box, Vec, RawVec, Allocator, Global, or System implementations. Cargo.toml sets autotests = false and the published archive contains no tests/ directory.

The VCS workspace contains a sibling member tests/ (allocator-api2-tests), but its src/lib.rs contains only four generic helper functions (test_allocate_layout, test_sizes, test_vec, test_many_boxes) and a tests! macro intended for downstream allocator authors to register their own #[test] cases against the trait. The single #[test] token in the file lives inside that macro's body — it only becomes an active test when a downstream consumer invokes the macro. There are no top-level #[test] functions or active test invocations in this repository.

The implementations are near-verbatim copies from rust-lang/rust (which is extensively tested upstream), but the upstream tests were not ported. The crate contains many unsafe blocks performing manual memory management, raw-pointer arithmetic, drop-in-place, and uninitialized-memory writes. The VCS history shows at least one prior soundness fix (Fix the Box::into_inner's double free.), demonstrating that the manual porting can introduce bugs that upstream tests would have caught.

The result is that no testing happens at cargo test time for downstream consumers of allocator-api2, and no testing happens in this repository's CI for the data-structure implementations themselves.

FINDING-2 quality low

CHANGELOG.md contains only an empty "[Unreleased]" entry

CHANGELOG.md advertises Keep-a-Changelog format and Semantic Versioning, then contains only a ## [Unreleased] header with no entries. The crate is on version 0.2.21 and the VCS history shows multiple substantive changes (a Box::into_inner double-free fix, MSRV bump, build-error fixes, etc.) that are not reflected.

Downstream consumers cannot use this file to assess what changed between versions, defeating the purpose of having a changelog at all.

FINDING-3 quality low

Published crate uses CRLF line endings while VCS uses LF

Source files in the published .crate archive (e.g. src/lib.rs, src/stable/boxed.rs) have CRLF line terminators, while the same files in the upstream Git repository at the recorded commit have LF terminators. Content is byte-identical when normalised:

diff <(tr -d '\r' < contents/src/lib.rs) vcs/src/lib.rs   # empty
diff <(tr -d '\r' < contents/src/stable/boxed.rs) vcs/src/stable/boxed.rs   # empty

boxed.rs actually has a mix of CRLF and CR terminators (file reports with CRLF, CR line terminators), suggesting the file was passed through more than one line-ending conversion before packaging.

This points to the crate having been packaged in an environment with Git core.autocrlf enabled (or a Windows-built cargo), rather than from a clean checkout. There is no functional impact: rustc accepts both line endings. The observation is recorded so reviewers can verify the contents match the VCS commit byte-for-byte after normalisation.

Annotations(6)

Cargo.toml

Cargo.toml, line 39-43

alloc = []
default = ["std"]
fresh-rust = []
nightly = []
std = ["alloc"]

Features: default = ["std"], std = ["alloc"], plus opt-in nightly and fresh-rust. The nightly feature switches the public re-exports from this crate's stable shims to core::alloc / alloc::{alloc,boxed,vec,collections} from libcore/liballoc directly (see src/lib.rs:13-20 and src/nightly.rs). The fresh-rust feature gates additions that require an MSRV above the declared 1.63 (e.g. From<&core::ffi::CStr> for Box<core::ffi::CStr>, which needs 1.64+).

Cargo.toml, line 30-32

[lib]
name = "allocator_api2"
path = "src/lib.rs"

[lib] proc-macro is absent; the crate is a plain library. build = false is set at line 18 and there is no build.rs file in the package. The crate therefore performs no build-time or install-time code execution on consumer machines, supporting has-build-exec and has-install-exec.

src/stable/alloc/global.rs

src/stable/alloc/global.rs, line 22-94

    fn alloc_impl(&self, layout: Layout, zeroed: bool) -> Result<NonNull<[u8]>, AllocError> {
        match layout.size() {
            0 => Ok(unsafe {
                NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
                    invalid_mut(layout.align()),
                    0,
                ))
            }),
            // SAFETY: `layout` is non-zero in size,
            size => unsafe {
                let raw_ptr = if zeroed {
                    alloc_zeroed(layout)
                } else {
                    alloc(layout)
                };
                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
                Ok(NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
                    ptr.as_ptr(),
                    size,
                )))
            },
        }
    }

    // SAFETY: Same as `Allocator::grow`
    #[inline(always)]
    unsafe fn grow_impl(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
        zeroed: bool,
    ) -> Result<NonNull<[u8]>, AllocError> {
        debug_assert!(
            new_layout.size() >= old_layout.size(),
            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
        );

        match old_layout.size() {
            0 => self.alloc_impl(new_layout, zeroed),

            // SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
            // as required by safety conditions. Other conditions must be upheld by the caller
            old_size if old_layout.align() == new_layout.align() => unsafe {
                let new_size = new_layout.size();

                // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
                assume(new_size >= old_layout.size());

                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
                if zeroed {
                    raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
                }
                Ok(NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
                    ptr.as_ptr(),
                    new_size,
                )))
            },

            // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
            // both the old and new memory allocation are valid for reads and writes for `old_size`
            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
            // for `dealloc` must be upheld by the caller.
            old_size => unsafe {
                let new_ptr = self.alloc_impl(new_layout, zeroed)?;
                core::ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), old_size);
                self.deallocate(ptr, old_layout);
                Ok(new_ptr)
            },
        }
    }

Global::alloc_impl and Global::grow_impl forward to alloc_crate::alloc::{alloc, alloc_zeroed, dealloc, realloc} (the user-registered #[global_allocator]). Zero-sized layouts are short-circuited to a dangling non-null pointer per the documented Allocator contract. Same-alignment grow/shrink paths use realloc; differing-alignment paths fall back to allocate + copy_nonoverlapping + deallocate. src/stable/alloc/system.rs mirrors this structure against std::alloc::System. Justifies uses-unsafe, unsafe-safe, unsafe-minimal.

src/stable/alloc/mod.rs

src/stable/alloc/mod.rs, line 101-362

pub unsafe trait Allocator {
    /// Attempts to allocate a block of memory.
    ///
    /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment guarantees of `layout`.
    ///
    /// The returned block may have a larger size than specified by `layout.size()`, and may or may
    /// not have its contents initialized.
    ///
    /// # Errors
    ///
    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
    /// allocator's size or alignment constraints.
    ///
    /// 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
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;

    /// Behaves like `allocate`, but also ensures that the returned memory is zero-initialized.
    ///
    /// # Errors
    ///
    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
    /// allocator's size or alignment constraints.
    ///
    /// 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
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    #[inline(always)]
    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        let ptr = self.allocate(layout)?;
        // SAFETY: `alloc` returns a valid memory block
        unsafe { ptr.cast::<u8>().as_ptr().write_bytes(0, ptr.len()) }
        Ok(ptr)
    }

    /// Deallocates the memory referenced by `ptr`.
    ///
    /// # Safety
    ///
    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator, and
    /// * `layout` must [*fit*] that block of memory.
    ///
    /// [*currently allocated*]: #currently-allocated-memory
    /// [*fit*]: #memory-fitting
    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);

    /// Attempts to extend the memory block.
    ///
    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
    /// memory. The pointer is suitable for holding data described by `new_layout`. 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. Any access to the old `ptr` is Undefined Behavior, even if the
    /// allocation was grown in-place. The newly returned pointer is the only valid pointer
    /// for accessing this memory now.
    ///
    /// 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.
    ///
    /// # Safety
    ///
    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
    ///
    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
    ///
    /// [*currently allocated*]: #currently-allocated-memory
    /// [*fit*]: #memory-fitting
    ///
    /// # Errors
    ///
    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
    /// 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
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    #[inline(always)]
    unsafe fn grow(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> {
        debug_assert!(
            new_layout.size() >= old_layout.size(),
            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
        );

        let new_ptr = self.allocate(new_layout)?;

        // SAFETY: because `new_layout.size()` must be greater than or equal to
        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
        // safe. The safety contract for `dealloc` must be upheld by the caller.
        unsafe {
            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), old_layout.size());
            self.deallocate(ptr, old_layout);
        }

        Ok(new_ptr)
    }

    /// Behaves like `grow`, but also ensures that the new contents are set to zero before being
    /// returned.
    ///
    /// The memory block will contain the following contents after a successful call to
    /// `grow_zeroed`:
    ///   * Bytes `0..old_layout.size()` are preserved from the original allocation.
    ///   * Bytes `old_layout.size()..old_size` will either be preserved or zeroed, depending on
    ///     the allocator implementation. `old_size` refers to the size of the memory block prior
    ///     to the `grow_zeroed` call, which may be larger than the size that was originally
    ///     requested when it was allocated.
    ///   * Bytes `old_size..new_size` are zeroed. `new_size` refers to the size of the memory
    ///     block returned by the `grow_zeroed` call.
    ///
    /// # Safety
    ///
    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
    ///
    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
    ///
    /// [*currently allocated*]: #currently-allocated-memory
    /// [*fit*]: #memory-fitting
    ///
    /// # Errors
    ///
    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
    /// 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
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    #[inline(always)]
    unsafe fn grow_zeroed(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> {
        debug_assert!(
            new_layout.size() >= old_layout.size(),
            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
        );

        let new_ptr = self.allocate_zeroed(new_layout)?;

        // SAFETY: because `new_layout.size()` must be greater than or equal to
        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
        // safe. The safety contract for `dealloc` must be upheld by the caller.
        unsafe {
            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), old_layout.size());
            self.deallocate(ptr, old_layout);
        }

        Ok(new_ptr)
    }

    /// Attempts to shrink the memory block.
    ///
    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
    /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
    /// this, the allocator may shrink 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. Any access to the old `ptr` is Undefined Behavior, even if the
    /// allocation was shrunk in-place. The newly returned pointer is the only valid pointer
    /// for accessing this memory now.
    ///
    /// 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.
    ///
    /// # Safety
    ///
    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
    /// * `new_layout.size()` must be smaller than or equal to `old_layout.size()`.
    ///
    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
    ///
    /// [*currently allocated*]: #currently-allocated-memory
    /// [*fit*]: #memory-fitting
    ///
    /// # 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.
    ///
    /// 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
    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
    ///
    /// Clients wishing to abort computation in response to an allocation error are encouraged to
    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
    ///
    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
    #[inline(always)]
    unsafe fn shrink(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> {
        debug_assert!(
            new_layout.size() <= old_layout.size(),
            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
        );

        let new_ptr = self.allocate(new_layout)?;

        // SAFETY: because `new_layout.size()` must be lower than or equal to
        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
        // writes for `new_layout.size()` bytes. Also, because the old allocation wasn't yet
        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
        // safe. The safety contract for `dealloc` must be upheld by the caller.
        unsafe {
            ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), new_layout.size());
            self.deallocate(ptr, old_layout);
        }

        Ok(new_ptr)
    }

    /// Creates a "by reference" adapter for this instance of `Allocator`.
    ///
    /// The returned adapter also implements `Allocator` and will simply borrow this.
    #[inline(always)]
    fn by_ref(&self) -> &Self
    where
        Self: Sized,
    {
        self
    }
}

Definition of the public unsafe trait Allocator with default impls for allocate_zeroed, grow, grow_zeroed, and shrink. The default grow/shrink impls allocate a new block, ptr::copy_nonoverlapping from the old, then self.deallocate(...) — matching the rustc stable contract verbatim. SAFETY comments accompany each unsafe block in this file.

src/stable/boxed.rs

src/stable/boxed.rs, line 177-195

pub struct Box<T: ?Sized, A: Allocator = Global>(Unique<T>, A);

// Safety: Box owns both T and A, so sending is safe if
// sending is safe for T and A.
unsafe impl<T: ?Sized, A: Allocator> Send for Box<T, A>
where
    T: Send,
    A: Send,
{
}

// Safety: Box owns both T and A, so sharing is safe if
// sharing is safe for T and A.
unsafe impl<T: ?Sized, A: Allocator> Sync for Box<T, A>
where
    T: Sync,
    A: Sync,
{
}

Defines Box<T: ?Sized, A: Allocator = Global>(Unique<T>, A) along with manual unsafe impl Send / unsafe impl Sync bounds. The remainder of this file (and src/stable/vec/mod.rs, src/stable/raw_vec.rs, src/stable/vec/{into_iter,drain,splice}.rs) re-implements Rust's standard Box and supporting collection machinery on stable. This is the bulk of the impl-datastructure surface and the largest contributor to uses-unsafe. Combined with no I/O imports outside std::io::{Read,Write,Seek,BufRead} trait forwarding impls, justifies uses-network, uses-filesystem, uses-environment, uses-exec, uses-concurrency, uses-crypto, uses-jit, uses-interpreter, impl-crypto, impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-algorithm, impl-concurrency.

src/stable/boxed.rs, line 586-598

    pub fn into_inner(boxed: Self) -> T {
        // Override our default `Drop` implementation.
        // Though the default `Drop` implementation drops the both the pointer and the allocator,
        // here we only want to drop the allocator.
        let boxed = mem::ManuallyDrop::new(boxed);
        let alloc = unsafe { ptr::read(&boxed.1) };

        let ptr = boxed.0;
        let unboxed = unsafe { ptr.as_ptr().read() };
        unsafe { alloc.deallocate(ptr.as_non_null_ptr().cast(), Layout::new::<T>()) };

        unboxed
    }

Box::into_inner. Wraps self in ManuallyDrop (to suppress Box::drop), ptr::reads the allocator out, ptr::reads the inner value, then deallocates the backing memory. The VCS history records a prior Fix the Box::into_inner's double free. (commits 5ba2f05, dedb0ed) that introduced the ManuallyDrop wrapper; this version is the corrected one. Supports datastructure-impl-correct.

src/stable/mod.rs

src/stable/mod.rs, line 67-87

#[cfg(feature = "alloc")]
#[track_caller]
#[inline(always)]
#[cfg(debug_assertions)]
unsafe fn assume(v: bool) {
    if !v {
        core::unreachable!()
    }
}

#[cfg(feature = "alloc")]
#[track_caller]
#[inline(always)]
#[cfg(not(debug_assertions))]
unsafe fn assume(v: bool) {
    if !v {
        unsafe {
            core::hint::unreachable_unchecked();
        }
    }
}

assume(v: bool) and invalid_mut/addr helpers. assume reduces to unreachable!() in debug and unreachable_unchecked() in release. invalid_mut / addr use mem::transmute to round-trip between usize and *mut T because the strict-provenance APIs (ptr::without_provenance, <*mut _>::expose_provenance) are unstable; on stable, transmute is the only provenance-preserving option here.

src/stable/raw_vec.rs

src/stable/raw_vec.rs, line 458-595

impl<T, A: Allocator> 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`.
    #[inline(always)]
    fn needs_to_grow(&self, len: usize, additional: usize) -> bool {
        additional > self.capacity().wrapping_sub(len)
    }

    #[inline(always)]
    fn set_ptr_and_cap(&mut self, ptr: NonNull<[u8]>, cap: usize) {
        // Allocators currently return a `NonNull<[u8]>` whose length matches
        // the size requested. If that ever changes, the capacity here should
        // change to `ptr.len() / mem::size_of::<T>()`.
        self.ptr = unsafe { NonNull::new_unchecked(ptr.cast().as_ptr()) };
        self.cap = cap;
    }

    // This method is usually instantiated many times. So we want it to be as
    // small as possible, to improve compile times. But we also want as much of
    // its contents to be statically computable as possible, to make the
    // generated code run faster. Therefore, this method is carefully written
    // so that all of the code that depends on `T` is within it, while as much
    // of the code that doesn't depend on `T` as possible is in functions that
    // are non-generic over `T`.
    #[inline(always)]
    fn grow_amortized(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
        // This is ensured by the calling contexts.
        debug_assert!(additional > 0);

        if mem::size_of::<T>() == 0 {
            // Since we return a capacity of `usize::MAX` when `elem_size` is
            // 0, getting to here necessarily means the `RawVec` is overfull.
            return Err(CapacityOverflow.into());
        }

        // Nothing we can really do about these checks, sadly.
        let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?;

        // This guarantees exponential growth. The doubling cannot overflow
        // because `cap <= isize::MAX` and the type of `cap` is `usize`.
        let cap = cmp::max(self.cap * 2, required_cap);
        let cap = cmp::max(Self::MIN_NON_ZERO_CAP, cap);

        let new_layout = Layout::array::<T>(cap);

        // `finish_grow` is non-generic over `T`.
        let ptr = finish_grow(new_layout, self.current_memory(), &mut self.alloc)?;
        self.set_ptr_and_cap(ptr, cap);
        Ok(())
    }

    // The constraints on this method are much the same as those on
    // `grow_amortized`, but this method is usually instantiated less often so
    // it's less critical.
    #[inline(always)]
    fn grow_exact(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
        if mem::size_of::<T>() == 0 {
            // Since we return a capacity of `usize::MAX` when the type size is
            // 0, getting to here necessarily means the `RawVec` is overfull.
            return Err(CapacityOverflow.into());
        }

        let cap = len.checked_add(additional).ok_or(CapacityOverflow)?;
        let new_layout = Layout::array::<T>(cap);

        // `finish_grow` is non-generic over `T`.
        let ptr = finish_grow(new_layout, self.current_memory(), &mut self.alloc)?;
        self.set_ptr_and_cap(ptr, cap);
        Ok(())
    }

    #[cfg(not(no_global_oom_handling))]
    #[inline(always)]
    fn shrink(&mut self, cap: usize) -> Result<(), TryReserveError> {
        assert!(
            cap <= self.capacity(),
            "Tried to shrink to a larger capacity"
        );

        let (ptr, layout) = if let Some(mem) = self.current_memory() {
            mem
        } else {
            return Ok(());
        };

        let ptr = unsafe {
            // `Layout::array` cannot overflow here because it would have
            // overflowed earlier when capacity was larger.
            let new_layout = Layout::array::<T>(cap).unwrap_unchecked();
            self.alloc
                .shrink(ptr, layout, new_layout)
                .map_err(|_| AllocError {
                    layout: new_layout,
                    non_exhaustive: (),
                })?
        };
        self.set_ptr_and_cap(ptr, cap);
        Ok(())
    }
}

// This function is outside `RawVec` to minimize compile times. See the comment
// above `RawVec::grow_amortized` for details. (The `A` parameter isn't
// significant, because the number of different `A` types seen in practice is
// much smaller than the number of `T` types.)
#[inline(always)]
fn finish_grow<A>(
    new_layout: Result<Layout, LayoutError>,
    current_memory: Option<(NonNull<u8>, Layout)>,
    alloc: &mut A,
) -> Result<NonNull<[u8]>, TryReserveError>
where
    A: Allocator,
{
    // Check for the error here to minimize the size of `RawVec::grow_*`.
    let new_layout = new_layout.map_err(|_| CapacityOverflow)?;

    alloc_guard(new_layout.size())?;

    let memory = if let Some((ptr, old_layout)) = current_memory {
        debug_assert_eq!(old_layout.align(), new_layout.align());
        unsafe {
            // The allocator checks for alignment equality
            assume(old_layout.align() == new_layout.align());
            alloc.grow(ptr, old_layout, new_layout)
        }
    } else {
        alloc.allocate(new_layout)
    };

    memory.map_err(|_| {
        AllocError {
            layout: new_layout,
            non_exhaustive: (),
        }
        .into()
    })
}

RawVec::grow_amortized / grow_exact / shrink / finish_grow — capacity-overflow-checked growth via Layout::array::<T>, alloc_guard (rejects >isize::MAX on <64-bit targets), and exponential doubling (cap = max(cap * 2, required_cap)) with a small MIN_NON_ZERO_CAP floor. Matches the rustc stdlib bounds documented for Vec, supporting datastructure-impl-bounds, datastructure-impl-safe.