start with pointer

This commit is contained in:
nora 2022-04-03 13:16:30 +02:00
parent 062e2f7ee2
commit 6042f4702d
3 changed files with 209 additions and 6 deletions

View file

@ -6,3 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
sptr = "0.2.3"

View file

@ -1,8 +1,163 @@
#[cfg(test)] mod strategies;
mod tests {
#[test] use std::fmt::{Debug, Formatter};
fn it_works() { use std::marker::PhantomData;
let result = 2 + 2; use std::ops::Not;
assert_eq!(result, 4);
use sptr::Strict;
pub struct StuffedPtr<T, S>(*mut T, PhantomData<S>)
where
S: StuffingStrategy;
impl<T, S> StuffedPtr<T, S>
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<S::Extra> {
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<T, S> Debug for StuffedPtr<T, S>
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<T, S> Drop for StuffedPtr<T, S>
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<T, S> From<Box<T>> for StuffedPtr<T, S>
where
S: StuffingStrategy,
{
fn from(boxed: Box<T>) -> 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<T>(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<i32, ()> = 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<i32, HasDebug> = 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<i32, HasDebug> = StuffedPtr::new_extra(extra);
println!("{:?} {:X}", stuffed_ptr.0, usize::MAX);
assert_eq!(
format!("{stuffed_ptr:?}"),
"StuffedPtr::Extra { extra: hello! }"
);
} }
} }

47
src/strategies.rs Normal file
View file

@ -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
}
}
}