From b5290e0a440f1237b502384d06cc84c6eaf6773b Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:29:11 +0200 Subject: [PATCH] add docs --- src/lib.rs | 227 +++++++++++++++++++++++++++++++++++++++++----- src/strategies.rs | 13 ++- 2 files changed, 213 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 377db9d..8c66a02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,19 @@ +#![warn(rust_2018_idioms)] +#![deny(unsafe_op_in_unsafe_fn)] +#![warn(missing_docs)] + +//! A crate for stuffing things into a pointer. + mod strategies; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; +use std::mem; use std::ops::Not; use sptr::Strict; +/// A union of a pointer and some extra data. pub struct StuffedPtr(*mut T, PhantomData) where S: StuffingStrategy; @@ -14,10 +22,12 @@ impl StuffedPtr where S: StuffingStrategy, { + /// Create a new `StuffedPtr` from a pointer pub fn new_ptr(ptr: *mut T) -> Self { Self(map_ptr(ptr, S::stuff_ptr), PhantomData) } + /// Create a new `StuffPtr` from extra pub fn new_extra(extra: S::Extra) -> Self { // this doesn't have any provenance, which is ok, since it's never a pointer anyways. // if the user calls `set_ptr` it will use the new provenance from that ptr @@ -26,26 +36,56 @@ where Self(ptr, PhantomData) } - pub unsafe fn get_ptr(&self) -> Option<*mut T> { - self.is_extra().not().then(|| self.get_ptr_unchecked()) + /// Get the pointer data, or `None` if it contains extra + pub fn get_ptr(&self) -> Option<*mut T> { + self.is_extra().not().then(|| { + // SAFETY: We have done a check that it's not extra + unsafe { self.get_ptr_unchecked() } + }) } + /// Get the pointer data + /// # Safety + /// Must contain pointer data and not extra pub unsafe fn get_ptr_unchecked(&self) -> *mut T { map_ptr(self.0, S::extract_ptr) } - pub unsafe fn into_extra_unchecked(self) -> S::Extra { - let data = self.addr(); - S::extract_extra(data) + /// Get owned extra data from this, or `None` if it contains pointer data + pub fn into_extra(self) -> Option { + self.is_extra().then(|| { + // SAFETY: We checked that it contains an extra above + unsafe { self.into_extra_unchecked() } + }) } + /// Get owned extra data from this + /// # Safety + /// Must contain extra data and not pointer + pub unsafe fn into_extra_unchecked(self) -> S::Extra { + // SAFETY: `self` is consumed and forgotten after this call + let extra = unsafe { self.get_extra_unchecked() }; + mem::forget(self); + extra + } + + /// Get extra data from this, or `None` if it contains pointer data + /// # Safety + /// The caller must guarantee that only ever on `Extra` exists if `Extra: !Copy` + pub unsafe fn get_extra(&self) -> Option { + self.is_extra().then(|| { + // SAFETY: We checked that it contains extra above, the caller guarantees the rest + unsafe { self.get_extra_unchecked() } + }) + } + + /// Get extra data from this + /// # Safety + /// Must contain extra data and not pointer data, + /// and the caller must guarantee that only ever on `Extra` exists if `Extra: !Copy` pub unsafe fn get_extra_unchecked(&self) -> S::Extra { let data = self.addr(); - S::extract_extra(data) - } - - pub unsafe fn get_extra(&self) -> Option { - self.is_extra().then(|| self.get_extra_unchecked()) + unsafe { S::extract_extra(data) } } fn addr(&self) -> usize { @@ -57,22 +97,41 @@ where } } +impl StuffedPtr +where + S: StuffingStrategy, + S::Extra: Copy, +{ + /// Get extra data from this, or `None` if it's pointer data + pub fn copy_extra(&self) -> Option { + // SAFETY: `S::Extra: Copy` + unsafe { self.get_extra() } + } + + /// Get extra data from this + /// # Safety + /// Must contain extra data and not pointer data, + pub unsafe fn copy_extra_unchecked(&self) -> S::Extra { + // SAFETY: `S::Extra: Copy`, and the caller guarantees that it's extra + unsafe { self.get_extra_unchecked() } + } +} + impl Debug for StuffedPtr where S: StuffingStrategy, S::Extra: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.is_extra() { - // SAFETY: We checked that self contains the extra - // Note: if S::Extra: !Copy, we can't just copy it out and call it a day - // For example, if it's a Box, not forgetting it here would lead to a double free - // So we just format it and forget it afterwards - let extra = unsafe { self.get_extra_unchecked() }; + // SAFETY: + // If S::Extra: !Copy, we can't just copy it out and call it a day + // For example, if it's a Box, not forgetting it here would lead to a double free + // So we just format it and forget it afterwards + if let Some(extra) = unsafe { self.get_extra() } { f.debug_struct("StuffedPtr::Extra") .field("extra", &extra) .finish()?; - std::mem::forget(extra); + mem::forget(extra); Ok(()) } else { let ptr = map_ptr(self.0, S::extract_ptr); @@ -98,6 +157,63 @@ where } } +impl Clone for StuffedPtr +where + S: StuffingStrategy, + S::Extra: Clone, +{ + fn clone(&self) -> Self { + // SAFETY: We forget that `extra` ever existed after taking the reference and cloning it + if let Some(extra) = unsafe { self.get_extra() } { + let cloned_extra = extra.clone(); + mem::forget(extra); + Self::new_extra(cloned_extra) + } else { + // just copy the pointer + Self(self.0, PhantomData) + } + } +} + +impl PartialEq for StuffedPtr +where + S: StuffingStrategy, + S::Extra: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + // SAFETY: We forget them after + let extras = unsafe { (self.get_extra(), other.get_extra()) }; + + let eq = match &extras { + (Some(extra1), Some(extra2)) => extra1.eq(extra2), + (None, None) => { + // SAFETY: `get_extra` returned `None`, so it must be a ptr + unsafe { + let ptr1 = self.get_ptr_unchecked(); + let ptr2 = self.get_ptr_unchecked(); + std::ptr::eq(ptr1, ptr2) + } + } + _ => false, + }; + + mem::forget(extras); + + eq + } +} + +// pointers are `Eq` +fn _assert_eq() {} +const _: fn() = _assert_eq::<*mut ()>; + +impl Eq for StuffedPtr +where + S: StuffingStrategy, + S::Extra: PartialEq + Eq, +{ +} + impl From> for StuffedPtr where S: StuffingStrategy, @@ -107,16 +223,49 @@ where } } +/// A trait that describes how to stuff extras and pointers into the pointer sized object. +/// # Safety +/// +/// If [`StuffingStrategy::is_extra`] returns true for a value, then +/// [`StuffingStrategy::extract_extra`] *must* return a valid `Extra` for that same value. +/// +/// [`StuffingStrategy::stuff_extra`] *must* consume `inner` and make sure that it's not dropped +/// if it isn't `Copy`. +/// +/// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`], +/// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true. pub unsafe trait StuffingStrategy { + /// The type of the extra. type Extra; + /// Checks whether the `StufferPtr` data value contains an extra value. The result of this + /// function can be trusted. fn is_extra(data: usize) -> bool; - fn stuff_extra(inner: Self::Extra) -> usize; - fn extract_extra(data: usize) -> Self::Extra; + /// Stuff extra data into a usize that is then put into the pointer. This operation + /// must be infallible. + fn stuff_extra(inner: Self::Extra) -> usize; + + /// Extract extra data from the data. + /// # Safety + /// `data` must contain data created by [`StuffingStrategy::stuff_extra`]. + unsafe fn extract_extra(data: usize) -> Self::Extra; + + /// Stuff a pointer address into the pointer sized integer. + /// + /// This can be used to optimize away some of the unnecessary parts of the pointer or do other + /// cursed things with it. + /// + /// The default implementation just returns the address directly. fn stuff_ptr(inner: usize) -> usize { inner } + + /// Extract the pointer address from the data. + /// + /// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`]. + /// + /// The default implementation just returns the value directly. fn extract_ptr(inner: usize) -> usize { inner } @@ -130,8 +279,9 @@ fn map_ptr(ptr: *mut T, map: impl FnOnce(usize) -> usize) -> *mut T { #[cfg(test)] mod tests { - use crate::strategies::test_strategies::{HasDebug, PanicsInDrop}; + use crate::strategies::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop}; use crate::StuffedPtr; + use std::mem; #[test] fn set_get_ptr_no_extra() { @@ -144,6 +294,13 @@ mod tests { } } + #[test] + fn get_extra() { + let stuffed_ptr: StuffedPtr<(), EmptyInMax> = StuffedPtr::new_extra(EmptyInMax); + assert!(stuffed_ptr.is_extra()); + assert!(matches!(stuffed_ptr.copy_extra(), Some(EmptyInMax))); + } + #[test] fn debug() { let boxed = Box::new(1); @@ -154,7 +311,6 @@ mod tests { let extra = HasDebug; let stuffed_ptr: StuffedPtr = StuffedPtr::new_extra(extra); - println!("{:?} {:X}", stuffed_ptr.0, usize::MAX); assert_eq!( format!("{stuffed_ptr:?}"), "StuffedPtr::Extra { extra: hello! }" @@ -163,10 +319,33 @@ mod tests { #[test] #[should_panic] - fn needs_drop() { - let extra = PanicsInDrop; - let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(extra); + fn drop_extra_when_extra() { + let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop); // the panicking drop needs to be called here! drop(stuffed_ptr); } + + #[test] + fn dont_drop_extra_when_pointer() { + let mut unit = (); + let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit); + // the panicking drop needs not to be called here! + drop(stuffed_ptr); + } + + #[test] + fn some_traits_dont_drop() { + // make sure that extra is never dropped twice + + let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop); + let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop); + + // PartialEq + assert_eq!(stuffed_ptr1, stuffed_ptr2); + // Debug + let _ = format!("{stuffed_ptr1:?}"); + + mem::forget(stuffed_ptr1); + mem::forget(stuffed_ptr2); + } } diff --git a/src/strategies.rs b/src/strategies.rs index 1201dca..6b906ed 100644 --- a/src/strategies.rs +++ b/src/strategies.rs @@ -11,7 +11,7 @@ unsafe impl StuffingStrategy for () { 0 } - fn extract_extra(_data: usize) -> Self::Extra { + unsafe fn extract_extra(_data: usize) -> Self::Extra { () } } @@ -31,17 +31,23 @@ pub mod test_strategies { data == usize::MAX } - fn stuff_extra(_inner: Self::Extra) -> usize { + fn stuff_extra(inner: Self::Extra) -> usize { + std::mem::forget(inner); usize::MAX } - fn extract_extra(_data: usize) -> Self::Extra { + unsafe fn extract_extra(_data: usize) -> Self::Extra { $ty } } }; } + #[derive(Clone, Copy)] + pub struct EmptyInMax; + + impl_usize_max_zst!(EmptyInMax); + pub struct HasDebug; impl Debug for HasDebug { @@ -52,6 +58,7 @@ pub mod test_strategies { impl_usize_max_zst!(HasDebug); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct PanicsInDrop; impl Drop for PanicsInDrop {