#![no_std]
#![warn(rust_2018_idioms)]
#![warn(missing_docs)]
#[cfg(test)]
extern crate alloc;
mod backend;
mod strategy;
use core::{
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::Not,
};
use sptr::Strict;
pub use crate::{backend::Backend, strategy::StuffingStrategy};
#[repr(transparent)]
pub struct StuffedPtr<T, S, B = usize>(B::Stored, PhantomData<S>)
where
S: StuffingStrategy<B>,
B: Backend<T>;
impl<T, S, B> StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
B: Backend<T>,
{
pub fn new_ptr(ptr: *mut T) -> Self {
let addr = Strict::addr(ptr);
let stuffed = S::stuff_ptr(addr);
StuffedPtr(B::set_ptr(ptr, stuffed), PhantomData)
}
pub fn new_other(other: S::Other) -> Self {
let ptr = core::ptr::null_mut();
let other = S::stuff_other(other);
StuffedPtr(B::set_ptr(ptr, other), PhantomData)
}
pub fn get_ptr(&self) -> Option<*mut T> {
match self.is_other().not() {
true => {
unsafe { Some(self.get_ptr_unchecked()) }
}
false => None,
}
}
pub unsafe fn get_ptr_unchecked(&self) -> *mut T {
let (provenance, addr) = B::get_ptr(self.0);
let addr = S::extract_ptr(addr);
Strict::with_addr(provenance, addr)
}
pub fn into_other(self) -> Option<S::Other> {
match self.is_other() {
true => {
unsafe { Some(self.into_other_unchecked()) }
}
false => None,
}
}
pub unsafe fn into_other_unchecked(self) -> S::Other {
let other = self.get_other_unchecked();
mem::forget(self);
other
}
pub unsafe fn get_other(&self) -> Option<S::Other> {
match self.is_other() {
true => {
Some(self.get_other_unchecked())
}
false => None,
}
}
pub unsafe fn get_other_unchecked(&self) -> S::Other {
let data = self.addr();
S::extract_other(data)
}
fn addr(&self) -> B {
B::get_int(self.0)
}
fn is_other(&self) -> bool {
S::is_other(self.addr())
}
}
impl<T, S, B> StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: Copy,
B: Backend<T>,
{
pub fn copy_other(&self) -> Option<S::Other> {
unsafe { self.get_other() }
}
pub unsafe fn copy_other_unchecked(&self) -> S::Other {
self.get_other_unchecked()
}
}
impl<T, S, B> Debug for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: Debug,
B: Backend<T>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
if let Some(other) = unsafe { self.get_other() } {
f.debug_struct("StuffedPtr::Other")
.field("other", &other)
.finish()?;
mem::forget(other);
Ok(())
} else {
let ptr = unsafe { self.get_ptr_unchecked() };
f.debug_struct("StuffedPtr::Ptr")
.field("ptr", &ptr)
.finish()
}
}
}
impl<T, S, B> Clone for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: Clone,
B: Backend<T>,
{
fn clone(&self) -> Self {
if let Some(other) = unsafe { self.get_other() } {
let cloned_other = other.clone();
mem::forget(other);
Self::new_other(cloned_other)
} else {
StuffedPtr(self.0, PhantomData)
}
}
}
impl<T, S, B> Copy for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: Copy,
B: Backend<T>,
{
}
impl<T, S, B> PartialEq for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: PartialEq,
B: Backend<T>,
{
fn eq(&self, other: &Self) -> bool {
let others = unsafe { (self.get_other(), other.get_other()) };
let eq = match &others {
(Some(other1), Some(other2)) => other1.eq(other2),
(None, None) => {
unsafe {
let ptr1 = self.get_ptr_unchecked();
let ptr2 = self.get_ptr_unchecked();
core::ptr::eq(ptr1, ptr2)
}
}
_ => false,
};
mem::forget(others);
eq
}
}
impl<T, S, B> Eq for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: PartialEq + Eq,
B: Backend<T>,
{
}
impl<T, S, B> Hash for StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
S::Other: Hash,
B: Backend<T>,
{
fn hash<H: Hasher>(&self, state: &mut H) {
if let Some(other) = unsafe { self.get_other() } {
other.hash(state);
mem::forget(other);
} else {
let ptr = unsafe { self.get_ptr_unchecked() };
ptr.hash(state);
}
}
}
#[cfg(test)]
mod tests {
#![allow(non_snake_case)]
use alloc::{boxed::Box, format};
use core::mem;
use paste::paste;
use crate::{
strategy::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop},
Backend, StuffedPtr, StuffingStrategy,
};
fn from_box<T, S, B>(boxed: Box<T>) -> StuffedPtr<T, S, B>
where
S: StuffingStrategy<B>,
B: Backend<T>,
{
StuffedPtr::new_ptr(Box::into_raw(boxed))
}
macro_rules! make_tests {
($backend:ident) => {
paste! {
#[test]
fn [<set_get_ptr_no_other__ $backend>]() {
unsafe {
let boxed = Box::new(1);
let stuffed_ptr: StuffedPtr<i32, (), $backend> = from_box(boxed);
let ptr = stuffed_ptr.get_ptr_unchecked();
let boxed = Box::from_raw(ptr);
assert_eq!(*boxed, 1);
}
}
#[test]
fn [<get_other__ $backend>]() {
let stuffed_ptr: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax);
assert!(stuffed_ptr.is_other());
assert!(matches!(stuffed_ptr.copy_other(), Some(EmptyInMax)));
}
#[test]
fn [<debug__ $backend>]() {
let boxed = Box::new(1);
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = from_box(boxed);
assert!(format!("{stuffed_ptr:?}").starts_with("StuffedPtr::Ptr {"));
drop(unsafe { Box::from_raw(stuffed_ptr.get_ptr().unwrap()) });
let other = HasDebug;
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = StuffedPtr::new_other(other);
assert_eq!(
format!("{stuffed_ptr:?}"),
"StuffedPtr::Other { other: hello! }"
);
}
#[test]
#[allow(clippy::redundant_clone)]
fn [<clone__ $backend>]() {
let mut unit = ();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
let _ = stuffed_ptr1.clone();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop);
let stuffed_ptr2 = stuffed_ptr1.clone();
mem::forget((stuffed_ptr1, stuffed_ptr2));
}
#[test]
fn [<eq__ $backend>]() {
let mut unit = ();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
assert_eq!(stuffed_ptr1, stuffed_ptr2);
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop);
assert_ne!(stuffed_ptr1, stuffed_ptr2);
mem::forget(stuffed_ptr2);
}
#[test]
fn [<dont_drop_other_when_pointer__ $backend>]() {
let mut unit = ();
let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
drop(stuffed_ptr);
}
#[test]
fn [<some_traits_dont_drop__ $backend>]() {
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop);
assert_eq!(stuffed_ptr1, stuffed_ptr2);
let _ = format!("{stuffed_ptr1:?}");
mem::forget((stuffed_ptr1, stuffed_ptr2));
}
}
};
}
make_tests!(u128);
make_tests!(u64);
make_tests!(usize);
}