From 6042f4702d5e90e2bf9a6c7be1706f93e38cc4eb Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:16:30 +0200 Subject: [PATCH] start with pointer --- Cargo.toml | 1 + src/lib.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++-- src/strategies.rs | 47 +++++++++++++ 3 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 src/strategies.rs diff --git a/Cargo.toml b/Cargo.toml index 67b1141..2c31dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +sptr = "0.2.3" diff --git a/src/lib.rs b/src/lib.rs index 1b4a90c..aa45424 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,163 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); +mod strategies; + +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use std::ops::Not; + +use sptr::Strict; + +pub struct StuffedPtr(*mut T, PhantomData) +where + S: StuffingStrategy; + +impl StuffedPtr +where + S: StuffingStrategy, +{ + pub fn new_ptr(ptr: *mut T) -> Self { + Self(map_ptr(ptr, S::stuff_ptr), PhantomData) + } + + 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 + let ptr = std::ptr::null_mut(); + let ptr = Strict::with_addr(ptr, S::stuff_extra(extra)); + Self(ptr, PhantomData) + } + + pub unsafe fn get_ptr(&self) -> Option<*mut T> { + self.is_extra().not().then(|| self.get_ptr_unchecked()) + } + + 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) + } + + 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()) + } + + fn addr(&self) -> usize { + Strict::addr(self.0) + } + + fn is_extra(&self) -> bool { + S::is_extra(self.addr()) + } +} + +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() }; + f.debug_struct("StuffedPtr::Extra") + .field("extra", &extra) + .finish()?; + std::mem::forget(extra); + Ok(()) + } else { + let ptr = map_ptr(self.0, S::extract_ptr); + f.debug_struct("StuffedPtr::Ptr") + .field("ptr", &ptr) + .finish() + } + } +} + +impl Drop for StuffedPtr +where + S: StuffingStrategy, +{ + fn drop(&mut self) { + if self.is_extra() { + // SAFETY: We move it out here and it's never accessed again. + let extra = unsafe { self.get_extra_unchecked() }; + drop(extra); + } else { + // dropping a ptr is a no-op + } + } +} + +impl From> for StuffedPtr +where + S: StuffingStrategy, +{ + fn from(boxed: Box) -> Self { + Self::new_ptr(Box::into_raw(boxed)) + } +} + +pub unsafe trait StuffingStrategy { + type Extra; + + fn is_extra(data: usize) -> bool; + fn stuff_extra(inner: Self::Extra) -> usize; + fn extract_extra(data: usize) -> Self::Extra; + + fn stuff_ptr(inner: usize) -> usize { + inner + } + fn extract_ptr(inner: usize) -> usize { + inner + } +} + +fn map_ptr(ptr: *mut T, map: impl FnOnce(usize) -> usize) -> *mut T { + let int = Strict::addr(ptr); + let result = map(int); + Strict::with_addr(ptr, result) +} + +#[cfg(test)] +mod tests { + use crate::strategies::test_strategies::HasDebug; + use crate::StuffedPtr; + + #[test] + fn set_get_ptr_no_extra() { + unsafe { + let boxed = Box::new(1); + let stuffed_ptr: StuffedPtr = boxed.into(); + let ptr = stuffed_ptr.get_ptr_unchecked(); + let boxed = Box::from_raw(ptr); + assert_eq!(*boxed, 1); + } + } + + #[test] + fn debug() { + let boxed = Box::new(1); + let stuffed_ptr: StuffedPtr = boxed.into(); + assert!(format!("{stuffed_ptr:?}").starts_with("StuffedPtr::Ptr {")); + + drop(unsafe { Box::from_raw(stuffed_ptr.get_ptr().unwrap()) }); + + 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! }" + ); } } diff --git a/src/strategies.rs b/src/strategies.rs new file mode 100644 index 0000000..6d5142c --- /dev/null +++ b/src/strategies.rs @@ -0,0 +1,47 @@ +use crate::StuffingStrategy; + +unsafe impl StuffingStrategy for () { + type Extra = (); + + fn is_extra(_data: usize) -> bool { + false + } + + fn stuff_extra(_inner: Self::Extra) -> usize { + 0 + } + + fn extract_extra(_data: usize) -> Self::Extra { + () + } +} + +#[cfg(test)] +pub mod test_strategies { + use crate::StuffingStrategy; + use std::fmt::{Debug, Formatter}; + + pub struct HasDebug; + + impl Debug for HasDebug { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("hello!") + } + } + + unsafe impl StuffingStrategy for HasDebug { + type Extra = Self; + + fn is_extra(data: usize) -> bool { + data == usize::MAX + } + + fn stuff_extra(_inner: Self::Extra) -> usize { + usize::MAX + } + + fn extract_extra(_data: usize) -> Self::Extra { + Self + } + } +}