Skip to content

Commit

Permalink
Fix miri issues (#30)
Browse files Browse the repository at this point in the history
* Use strict provenance APIs

* Allow compiling on stable again

* Enable nightly feature for miri CI

* Add more backports for MSRV
  • Loading branch information
GnomedDev authored Sep 23, 2024
1 parent 4a36e5f commit 10e923d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ jobs:
rustup override set nightly
cargo miri setup
- name: Test with Miri
run: cargo miri test
run: cargo miri test --features nightly
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ categories = ["data-structures"]
readme = "README.md"
keywords = ["box", "alloc", "dst", "stack", "no_std"]
license = "MIT"
rust-version = "1.56"
edition = "2021"

[features]
default = ["std"]
std = []
coerce = []
nightly = ["coerce"]
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
//! - Require nightly rust
//! - Allow automatic coersion from sized `SmallBox` to unsized `SmallBox`.
//!
//! - `nightly`
//! - Optional
//! - Enables `coerce`
//! - Requires nightly rust
//! - Uses strict provenance operations to be compatible with `miri`
//!
//! # Unsized Type
//!
Expand Down Expand Up @@ -149,13 +154,15 @@
//! Please note that the space alignment is also important. If the alignment
//! of the space is smaller than the alignment of the value, the value
//! will be stored in the heap.

#![cfg_attr(feature = "nightly", feature(strict_provenance, set_ptr_value))]
#![cfg_attr(feature = "coerce", feature(unsize, coerce_unsized))]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(clippy::as_conversions)]

extern crate alloc;

mod smallbox;
pub mod space;
mod sptr;

pub use crate::smallbox::SmallBox;
65 changes: 29 additions & 36 deletions src/smallbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use core::ptr;
use ::alloc::alloc;
use ::alloc::alloc::Layout;

use crate::sptr;

#[cfg(feature = "coerce")]
impl<T: ?Sized + Unsize<U>, U: ?Sized, Space> CoerceUnsized<SmallBox<U, Space>>
for SmallBox<T, Space>
Expand Down Expand Up @@ -55,7 +57,7 @@ impl<T: ?Sized + Unsize<U>, U: ?Sized, Space> CoerceUnsized<SmallBox<U, Space>>
macro_rules! smallbox {
( $e: expr ) => {{
let val = $e;
let ptr = &val as *const _;
let ptr = ::core::ptr::addr_of!(val);
#[allow(unsafe_code)]
unsafe {
$crate::SmallBox::new_unchecked(val, ptr)
Expand All @@ -66,7 +68,7 @@ macro_rules! smallbox {
/// An optimized box that store value on stack or on heap depending on its size
pub struct SmallBox<T: ?Sized, Space> {
space: MaybeUninit<Space>,
ptr: *const T,
ptr: *mut T,
_phantom: PhantomData<T>,
}

Expand Down Expand Up @@ -130,7 +132,7 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {
}
} else {
let val: &T = &this;
unsafe { SmallBox::<T, ToSpace>::new_copy(val, val as *const T) }
unsafe { SmallBox::<T, ToSpace>::new_copy(val, sptr::from_ref(val)) }
}
}

Expand All @@ -153,15 +155,15 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {
!self.ptr.is_null()
}

unsafe fn new_copy<U>(val: &U, ptr: *const T) -> SmallBox<T, Space>
unsafe fn new_copy<U>(val: &U, metadata_ptr: *const T) -> SmallBox<T, Space>
where U: ?Sized {
let size = mem::size_of_val::<U>(val);
let align = mem::align_of_val::<U>(val);

let mut space = MaybeUninit::<Space>::uninit();

let (ptr_addr, ptr_copy): (*const u8, *mut u8) = if size == 0 {
(ptr::null(), align as *mut u8)
let (ptr_this, val_dst): (*mut u8, *mut u8) = if size == 0 {
(ptr::null_mut(), sptr::without_provenance_mut(align))
} else if size > mem::size_of::<Space>() || align > mem::align_of::<Space>() {
// Heap
let layout = Layout::for_value::<U>(val);
Expand All @@ -170,15 +172,13 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {
(heap_ptr, heap_ptr)
} else {
// Stack
(ptr::null(), space.as_mut_ptr() as *mut u8)
(ptr::null_mut(), space.as_mut_ptr().cast())
};

// Overwrite the pointer but retain any extra data inside the fat pointer.
let mut ptr = ptr;
let ptr_ptr = &mut ptr as *mut _ as *mut usize;
ptr_ptr.write(ptr_addr as usize);
// `self.ptr` always holds the metadata, even if stack allocated
let ptr = sptr::with_metadata_of_mut(ptr_this, metadata_ptr);

ptr::copy_nonoverlapping(val as *const _ as *const u8, ptr_copy, size);
ptr::copy_nonoverlapping(sptr::from_ref(val).cast(), val_dst, size);

SmallBox {
space,
Expand All @@ -192,14 +192,14 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {
let mut space = MaybeUninit::<Space>::uninit();

if !self.is_heap() {
ptr::copy_nonoverlapping(
self.space.as_ptr() as *const u8,
space.as_mut_ptr() as *mut u8,
ptr::copy_nonoverlapping::<u8>(
self.space.as_ptr().cast(),
space.as_mut_ptr().cast(),
size,
);
};

let ptr = self.ptr as *const U;
let ptr = self.ptr.cast();

mem::forget(self);

Expand All @@ -212,28 +212,20 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {

#[inline]
unsafe fn as_ptr(&self) -> *const T {
let mut ptr = self.ptr;

if !self.is_heap() {
// Overwrite the pointer but retain any extra data inside the fat pointer.
let ptr_ptr = &mut ptr as *mut _ as *mut usize;
ptr_ptr.write(self.space.as_ptr() as *const () as usize);
if self.is_heap() {
self.ptr
} else {
sptr::with_metadata_of(self.space.as_ptr(), self.ptr)
}

ptr
}

#[inline]
unsafe fn as_mut_ptr(&mut self) -> *mut T {
let mut ptr = self.ptr;

if !self.is_heap() {
// Overwrite the pointer but retain any extra data inside the fat pointer.
let ptr_ptr = &mut ptr as *mut _ as *mut usize;
ptr_ptr.write(self.space.as_mut_ptr() as *mut () as usize);
if self.is_heap() {
self.ptr
} else {
sptr::with_metadata_of_mut(self.space.as_mut_ptr(), self.ptr)
}

ptr as *mut _
}

/// Consumes the SmallBox and returns ownership of the boxed value
Expand Down Expand Up @@ -261,7 +253,7 @@ impl<T: ?Sized, Space> SmallBox<T, Space> {
if this.is_heap() {
let layout = Layout::new::<T>();
unsafe {
alloc::dealloc(this.ptr as *mut u8, layout);
alloc::dealloc(this.ptr.cast(), layout);
}
}

Expand Down Expand Up @@ -365,7 +357,7 @@ impl<T: ?Sized, Space> ops::Drop for SmallBox<T, Space> {
let layout = Layout::for_value::<T>(&*self);
ptr::drop_in_place::<T>(&mut **self);
if self.is_heap() {
alloc::dealloc(self.ptr as *mut u8, layout);
alloc::dealloc(self.ptr.cast(), layout);
}
}
}
Expand Down Expand Up @@ -445,6 +437,7 @@ unsafe impl<T: ?Sized + Sync, Space> Sync for SmallBox<T, Space> {}
#[cfg(test)]
mod tests {
use core::any::Any;
use core::ptr::addr_of;

use ::alloc::boxed::Box;
use ::alloc::vec;
Expand All @@ -464,7 +457,7 @@ mod tests {
#[test]
fn test_new_unchecked() {
let val = [0usize, 1];
let ptr = &val as *const _;
let ptr = addr_of!(val);

unsafe {
let stacked: SmallBox<[usize], S2> = SmallBox::new_unchecked(val, ptr);
Expand All @@ -473,7 +466,7 @@ mod tests {
}

let val = [0usize, 1, 2];
let ptr = &val as *const _;
let ptr = addr_of!(val);

unsafe {
let heaped: SmallBox<dyn Any, S2> = SmallBox::new_unchecked(val, ptr);
Expand Down
42 changes: 42 additions & 0 deletions src/sptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#[cfg(feature = "nightly")]
mod implementation {
pub use core::ptr::without_provenance_mut;

pub fn with_metadata_of<T: ?Sized, U: ?Sized>(ptr: *const T, meta: *const U) -> *const U {
ptr.with_metadata_of(meta)
}

pub fn with_metadata_of_mut<T: ?Sized, U: ?Sized>(ptr: *mut T, meta: *const U) -> *mut U {
ptr.with_metadata_of(meta)
}
}

#[cfg(not(feature = "nightly"))]
#[allow(clippy::as_conversions)]
mod implementation {
use core::ptr::addr_of_mut;

fn cast_to_mut<T: ?Sized>(ptr: *const T) -> *mut T {
ptr as _
}

pub fn without_provenance_mut<T>(addr: usize) -> *mut T {
addr as _
}

pub fn with_metadata_of<T: ?Sized, U: ?Sized>(ptr: *const T, meta: *const U) -> *const U {
with_metadata_of_mut(cast_to_mut(ptr), meta)
}

pub fn with_metadata_of_mut<T: ?Sized, U: ?Sized>(ptr: *mut T, mut meta: *const U) -> *mut U {
let meta_ptr = addr_of_mut!(meta).cast::<usize>();
unsafe { meta_ptr.write(ptr.cast::<u8>() as usize) }
cast_to_mut(meta)
}
}

pub fn from_ref<T: ?Sized>(val: &T) -> *const T {
val
}

pub use implementation::*;

0 comments on commit 10e923d

Please sign in to comment.