Skip to content

Commit

Permalink
Track (and move) owned slice variants by raw pointer
Browse files Browse the repository at this point in the history
  • Loading branch information
lopopolo committed Jul 26, 2023
1 parent 744bdb1 commit bfbea04
Showing 1 changed file with 75 additions and 12 deletions.
87 changes: 75 additions & 12 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//! the `'static` slices handed out to the interner.

use core::fmt;
use core::marker::PhantomData;
use core::ptr::NonNull;
use std::borrow::Cow;
#[cfg(feature = "cstr")]
use std::ffi::{CStr, CString};
Expand Down Expand Up @@ -59,7 +61,7 @@ where
{
/// Return a reference to the inner slice.
#[inline]
pub const fn as_slice(&self) -> &T {
pub fn as_slice(&self) -> &T {
self.0.as_slice()
}

Expand Down Expand Up @@ -127,7 +129,7 @@ enum Slice<T: 'static + ?Sized> {
/// True `'static` references.
Static(&'static T),
/// Owned `'static` references.
Owned(Box<T>),
Owned(PinBox<T>),
}

impl<T> From<&'static T> for Slice<T>
Expand All @@ -143,7 +145,7 @@ where
impl From<String> for Slice<str> {
#[inline]
fn from(owned: String) -> Self {
Self::Owned(owned.into_boxed_str())
Self::Owned(PinBox::new(owned.into_boxed_str()))
}
}

Expand All @@ -161,7 +163,7 @@ impl From<Cow<'static, str>> for Slice<str> {
impl From<Vec<u8>> for Slice<[u8]> {
#[inline]
fn from(owned: Vec<u8>) -> Self {
Self::Owned(owned.into_boxed_slice())
Self::Owned(PinBox::new(owned.into_boxed_slice()))
}
}

Expand All @@ -180,7 +182,7 @@ impl From<Cow<'static, [u8]>> for Slice<[u8]> {
impl From<CString> for Slice<CStr> {
#[inline]
fn from(owned: CString) -> Self {
Self::Owned(owned.into_boxed_c_str())
Self::Owned(PinBox::new(owned.into_boxed_c_str()))
}
}

Expand All @@ -199,7 +201,7 @@ impl From<Cow<'static, CStr>> for Slice<CStr> {
impl From<OsString> for Slice<OsStr> {
#[inline]
fn from(owned: OsString) -> Self {
Self::Owned(owned.into_boxed_os_str())
Self::Owned(PinBox::new(owned.into_boxed_os_str()))
}
}

Expand All @@ -218,7 +220,7 @@ impl From<Cow<'static, OsStr>> for Slice<OsStr> {
impl From<PathBuf> for Slice<Path> {
#[inline]
fn from(owned: PathBuf) -> Self {
Self::Owned(owned.into_boxed_path())
Self::Owned(PinBox::new(owned.into_boxed_path()))
}
}

Expand All @@ -239,10 +241,13 @@ where
{
/// Return a reference to the inner slice.
#[inline]
const fn as_slice(&self) -> &T {
fn as_slice(&self) -> &T {
match self {
Self::Static(slice) => slice,
Self::Owned(owned) => owned,
Self::Owned(owned) => {
// SAFETY: `PinBox` acts like `Box`.
unsafe { owned.as_ref() }
}
}
}

Expand All @@ -258,8 +263,6 @@ where
match self {
Self::Static(slice) => slice,
Self::Owned(owned) => {
// Coerce the `Box<T>` to a pointer.
let ptr: *const T = &**owned;
// SAFETY: This expression creates a reference with a `'static`
// lifetime from an owned buffer, which is permissible because:
//
Expand All @@ -270,9 +273,10 @@ where
// - The `map` field of the various symbol tables which contains
// the `'static` references, is dropped before the owned buffers
// stored in this `Slice`.
// - `PinBox` acts like `Box`.
unsafe {
// Coerce the pointer to a `&'static T`.
&*ptr
owned.as_ref()
}
}
}
Expand Down Expand Up @@ -353,3 +357,62 @@ impl fmt::Debug for Slice<Path> {
}
}
}

// An abstraction over a `Box<T>` where T is an unsized slice type which moves
// the box by raw pointer. This type is required to satisfy Miri with
// `-Zmiri-retag-fields`. See #235, #236.
//
// The `PinBox` type is derived from:
//
// - <https://github.com/CAD97/simple-interner/blob/24a836e9f8a0173faf48438d711442c2a86659c1/src/interner.rs#L26-L56>
// - <https://github.com/artichoke/intaglio/pull/236#issuecomment-1651058752>
// - <https://github.com/artichoke/intaglio/pull/236#issuecomment-1652003240>

/// A wrapper around box that does not provide &mut access to the pointee and
/// uses raw-pointer borrowing rules to avoid invalidating extant references.
///
/// The resolved reference is guaranteed valid until the `PinBox` is dropped.
///
/// This type is meant to allow the owned data in the given `Box<T>` to be moved
/// without being retagged by Miri. See #235, #236.
struct PinBox<T: ?Sized> {
ptr: NonNull<T>,
_marker: PhantomData<Box<T>>,
}

impl<T: ?Sized> PinBox<T> {
#[inline]
fn new(x: Box<T>) -> Self {
let ptr = Box::into_raw(x);
// SAFETY: `ptr` is derived from `Box::into_raw` and can never be null.
let ptr = unsafe { NonNull::new_unchecked(ptr) };
Self {
ptr,
_marker: PhantomData,
}
}

#[inline]
unsafe fn as_ref<'a>(&self) -> &'a T {
// SAFETY: `PinBox` acts like `Box`, `self.ptr` is non-null and points
// to a live `Box`.
unsafe { self.ptr.as_ref() }
}
}

impl<T: ?Sized> Drop for PinBox<T> {
fn drop(&mut self) {
// SAFETY: `PinBox` acts like `Box`.
unsafe {
let _drop_me = Box::from_raw(self.ptr.as_ptr());
}
}
}

impl<T: ?Sized + fmt::Debug> fmt::Debug for PinBox<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// SAFETY: `PinBox` acts like `Box`.
let s = unsafe { self.as_ref() };
s.fmt(f)
}
}

0 comments on commit bfbea04

Please sign in to comment.