mirror of
https://github.com/Noratrieb/stuff.git
synced 2026-01-14 16:35:08 +01:00
add docs
This commit is contained in:
parent
a453f210b3
commit
b5290e0a44
2 changed files with 213 additions and 27 deletions
227
src/lib.rs
227
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;
|
mod strategies;
|
||||||
|
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
|
|
||||||
use sptr::Strict;
|
use sptr::Strict;
|
||||||
|
|
||||||
|
/// A union of a pointer and some extra data.
|
||||||
pub struct StuffedPtr<T, S>(*mut T, PhantomData<S>)
|
pub struct StuffedPtr<T, S>(*mut T, PhantomData<S>)
|
||||||
where
|
where
|
||||||
S: StuffingStrategy;
|
S: StuffingStrategy;
|
||||||
|
|
@ -14,10 +22,12 @@ impl<T, S> StuffedPtr<T, S>
|
||||||
where
|
where
|
||||||
S: StuffingStrategy,
|
S: StuffingStrategy,
|
||||||
{
|
{
|
||||||
|
/// Create a new `StuffedPtr` from a pointer
|
||||||
pub fn new_ptr(ptr: *mut T) -> Self {
|
pub fn new_ptr(ptr: *mut T) -> Self {
|
||||||
Self(map_ptr(ptr, S::stuff_ptr), PhantomData)
|
Self(map_ptr(ptr, S::stuff_ptr), PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new `StuffPtr` from extra
|
||||||
pub fn new_extra(extra: S::Extra) -> Self {
|
pub fn new_extra(extra: S::Extra) -> Self {
|
||||||
// this doesn't have any provenance, which is ok, since it's never a pointer anyways.
|
// 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
|
// if the user calls `set_ptr` it will use the new provenance from that ptr
|
||||||
|
|
@ -26,26 +36,56 @@ where
|
||||||
Self(ptr, PhantomData)
|
Self(ptr, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn get_ptr(&self) -> Option<*mut T> {
|
/// Get the pointer data, or `None` if it contains extra
|
||||||
self.is_extra().not().then(|| self.get_ptr_unchecked())
|
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 {
|
pub unsafe fn get_ptr_unchecked(&self) -> *mut T {
|
||||||
map_ptr(self.0, S::extract_ptr)
|
map_ptr(self.0, S::extract_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn into_extra_unchecked(self) -> S::Extra {
|
/// Get owned extra data from this, or `None` if it contains pointer data
|
||||||
let data = self.addr();
|
pub fn into_extra(self) -> Option<S::Extra> {
|
||||||
S::extract_extra(data)
|
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<S::Extra> {
|
||||||
|
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 {
|
pub unsafe fn get_extra_unchecked(&self) -> S::Extra {
|
||||||
let data = self.addr();
|
let data = self.addr();
|
||||||
S::extract_extra(data)
|
unsafe { 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 {
|
fn addr(&self) -> usize {
|
||||||
|
|
@ -57,22 +97,41 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, S> StuffedPtr<T, S>
|
||||||
|
where
|
||||||
|
S: StuffingStrategy,
|
||||||
|
S::Extra: Copy,
|
||||||
|
{
|
||||||
|
/// Get extra data from this, or `None` if it's pointer data
|
||||||
|
pub fn copy_extra(&self) -> Option<S::Extra> {
|
||||||
|
// 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<T, S> Debug for StuffedPtr<T, S>
|
impl<T, S> Debug for StuffedPtr<T, S>
|
||||||
where
|
where
|
||||||
S: StuffingStrategy,
|
S: StuffingStrategy,
|
||||||
S::Extra: Debug,
|
S::Extra: Debug,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
if self.is_extra() {
|
// SAFETY:
|
||||||
// SAFETY: We checked that self contains the extra
|
// If S::Extra: !Copy, we can't just copy it out and call it a day
|
||||||
// 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
|
||||||
// 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
|
||||||
// So we just format it and forget it afterwards
|
if let Some(extra) = unsafe { self.get_extra() } {
|
||||||
let extra = unsafe { self.get_extra_unchecked() };
|
|
||||||
f.debug_struct("StuffedPtr::Extra")
|
f.debug_struct("StuffedPtr::Extra")
|
||||||
.field("extra", &extra)
|
.field("extra", &extra)
|
||||||
.finish()?;
|
.finish()?;
|
||||||
std::mem::forget(extra);
|
mem::forget(extra);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let ptr = map_ptr(self.0, S::extract_ptr);
|
let ptr = map_ptr(self.0, S::extract_ptr);
|
||||||
|
|
@ -98,6 +157,63 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, S> Clone for StuffedPtr<T, S>
|
||||||
|
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<T, S> PartialEq for StuffedPtr<T, S>
|
||||||
|
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<T: Eq>() {}
|
||||||
|
const _: fn() = _assert_eq::<*mut ()>;
|
||||||
|
|
||||||
|
impl<T, S> Eq for StuffedPtr<T, S>
|
||||||
|
where
|
||||||
|
S: StuffingStrategy,
|
||||||
|
S::Extra: PartialEq + Eq,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, S> From<Box<T>> for StuffedPtr<T, S>
|
impl<T, S> From<Box<T>> for StuffedPtr<T, S>
|
||||||
where
|
where
|
||||||
S: StuffingStrategy,
|
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 {
|
pub unsafe trait StuffingStrategy {
|
||||||
|
/// The type of the extra.
|
||||||
type 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 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 {
|
fn stuff_ptr(inner: usize) -> usize {
|
||||||
inner
|
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 {
|
fn extract_ptr(inner: usize) -> usize {
|
||||||
inner
|
inner
|
||||||
}
|
}
|
||||||
|
|
@ -130,8 +279,9 @@ fn map_ptr<T>(ptr: *mut T, map: impl FnOnce(usize) -> usize) -> *mut T {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::strategies::test_strategies::{HasDebug, PanicsInDrop};
|
use crate::strategies::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop};
|
||||||
use crate::StuffedPtr;
|
use crate::StuffedPtr;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_get_ptr_no_extra() {
|
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]
|
#[test]
|
||||||
fn debug() {
|
fn debug() {
|
||||||
let boxed = Box::new(1);
|
let boxed = Box::new(1);
|
||||||
|
|
@ -154,7 +311,6 @@ mod tests {
|
||||||
|
|
||||||
let extra = HasDebug;
|
let extra = HasDebug;
|
||||||
let stuffed_ptr: StuffedPtr<i32, HasDebug> = StuffedPtr::new_extra(extra);
|
let stuffed_ptr: StuffedPtr<i32, HasDebug> = StuffedPtr::new_extra(extra);
|
||||||
println!("{:?} {:X}", stuffed_ptr.0, usize::MAX);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{stuffed_ptr:?}"),
|
format!("{stuffed_ptr:?}"),
|
||||||
"StuffedPtr::Extra { extra: hello! }"
|
"StuffedPtr::Extra { extra: hello! }"
|
||||||
|
|
@ -163,10 +319,33 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn needs_drop() {
|
fn drop_extra_when_extra() {
|
||||||
let extra = PanicsInDrop;
|
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||||
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(extra);
|
|
||||||
// the panicking drop needs to be called here!
|
// the panicking drop needs to be called here!
|
||||||
drop(stuffed_ptr);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ unsafe impl StuffingStrategy for () {
|
||||||
0
|
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
|
data == usize::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stuff_extra(_inner: Self::Extra) -> usize {
|
fn stuff_extra(inner: Self::Extra) -> usize {
|
||||||
|
std::mem::forget(inner);
|
||||||
usize::MAX
|
usize::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_extra(_data: usize) -> Self::Extra {
|
unsafe fn extract_extra(_data: usize) -> Self::Extra {
|
||||||
$ty
|
$ty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct EmptyInMax;
|
||||||
|
|
||||||
|
impl_usize_max_zst!(EmptyInMax);
|
||||||
|
|
||||||
pub struct HasDebug;
|
pub struct HasDebug;
|
||||||
|
|
||||||
impl Debug for HasDebug {
|
impl Debug for HasDebug {
|
||||||
|
|
@ -52,6 +58,7 @@ pub mod test_strategies {
|
||||||
|
|
||||||
impl_usize_max_zst!(HasDebug);
|
impl_usize_max_zst!(HasDebug);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct PanicsInDrop;
|
pub struct PanicsInDrop;
|
||||||
|
|
||||||
impl Drop for PanicsInDrop {
|
impl Drop for PanicsInDrop {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue