test all backends!

This commit is contained in:
nora 2022-04-04 12:49:06 +02:00
parent 5164f70545
commit 14e7443532
6 changed files with 374 additions and 234 deletions

3
.rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
imports_granularity = "Crate"
newline_style = "Unix"
group_imports = "StdExternalCrate"

View file

@ -7,3 +7,6 @@ edition = "2021"
[dependencies]
sptr = "0.2.3"
[dev-dependencies]
paste = "1.0.7"

View file

@ -1,17 +1,29 @@
use sptr::Strict;
use std::mem;
use sptr::Strict;
/// A backend where the stuffed pointer is stored. Must be bigger or equal to the pointer size.
pub trait Backend<T> {
/// The underlying type where the data is stored. Often a tuple of a pointer (for the provenance)
/// and some integers to fill up the bytes.
type Stored: Copy;
/// Get the pointer from the backed. Since the [`crate::StuffingStrategy`] is able to use the full
/// bytes to pack in the pointer address, the full address is returned in the second tuple field,
/// as the integer. The provenance of the pointer is returned as the first tuple field, but its
/// address should be ignored and may be invalid.
fn get_ptr(s: Self::Stored) -> (*mut T, Self);
/// Set a new pointer address. The provenance of the new pointer is transferred in the first argument,
/// and the address in the second. See [`Backend::get_ptr`] for more details on the separation.
fn set_ptr(provenance: *mut T, addr: Self) -> Self::Stored;
/// Get the integer value from the backend. Note that this *must not* be used to create a pointer,
/// for that use [`Backend::get_ptr`] to keep the provenance.
fn get_int(s: Self::Stored) -> Self;
}
#[allow(clippy::should_assert_eq, dead_code)] // :/
#[allow(dead_code)] // :/
const fn assert_size<B>()
where
B: Backend<()>,

View file

@ -3,18 +3,28 @@
#![warn(missing_docs)]
//! A crate for stuffing things into a pointer.
//!
//! This crate consists of three parts:
//! * The type [`StuffedPtr`]
//! * The trait [`StuffingStrategy`]
//! * The trait [`Backend`]
//!
//!
mod backend;
pub mod strategies;
mod strategy;
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use std::mem;
use std::ops::Not;
use std::{
fmt::{Debug, Formatter},
marker::PhantomData,
mem,
ops::Not,
};
use crate::backend::Backend;
use sptr::Strict;
pub use crate::{backend::Backend, strategy::StuffingStrategy};
/// A union of a pointer and some extra data.
pub struct StuffedPtr<T, S, I = usize>(I::Stored, PhantomData<S>)
where
@ -235,142 +245,115 @@ 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<I> {
/// 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: I) -> bool;
/// Stuff extra data into a usize that is then put into the pointer. This operation
/// must be infallible.
fn stuff_extra(inner: Self::Extra) -> I;
/// Extract extra data from the data.
/// # Safety
/// `data` must contain data created by [`StuffingStrategy::stuff_extra`].
unsafe fn extract_extra(data: I) -> 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(addr: usize) -> I;
/// Extract the pointer address from the data.
///
/// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`].
fn extract_ptr(inner: I) -> usize;
}
#[cfg(test)]
mod tests {
use crate::strategies::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop};
use crate::StuffedPtr;
#![allow(non_snake_case)]
use std::mem;
// note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no
use paste::paste;
use crate::{
strategy::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop},
StuffedPtr,
};
// extra is ever dropped accidentally.
// note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no
macro_rules! make_tests {
($backend:ident) => {
paste! {
#[test]
fn set_get_ptr_no_extra() {
fn [<set_get_ptr_no_extra__ $backend>]() {
unsafe {
let boxed = Box::new(1);
let stuffed_ptr: StuffedPtr<i32, ()> = boxed.into();
let stuffed_ptr: StuffedPtr<i32, (), $backend> = boxed.into();
let ptr = stuffed_ptr.get_ptr_unchecked();
let boxed = Box::from_raw(ptr);
assert_eq!(*boxed, 1);
}
}
#[test]
fn get_extra() {
let stuffed_ptr: StuffedPtr<(), EmptyInMax> = StuffedPtr::new_extra(EmptyInMax);
fn [<get_extra__ $backend>]() {
let stuffed_ptr: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_extra(EmptyInMax);
assert!(stuffed_ptr.is_extra());
assert!(matches!(stuffed_ptr.copy_extra(), Some(EmptyInMax)));
}
#[test]
fn debug() {
fn [<debug__ $backend>]() {
let boxed = Box::new(1);
let stuffed_ptr: StuffedPtr<i32, HasDebug> = boxed.into();
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = 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);
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = StuffedPtr::new_extra(extra);
assert_eq!(
format!("{stuffed_ptr:?}"),
"StuffedPtr::Extra { extra: hello! }"
);
}
#[test]
#[should_panic]
fn drop_extra_when_extra() {
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
fn [<drop_extra_when_extra__ $backend>]() {
let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
// the panicking drop needs to be called here!
drop(stuffed_ptr);
}
#[test]
#[allow(clippy::redundant_clone)]
fn clone() {
fn [<clone__ $backend>]() {
let mut unit = ();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
let _ = stuffed_ptr1.clone();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
let stuffed_ptr2 = stuffed_ptr1.clone();
mem::forget((stuffed_ptr1, stuffed_ptr2));
}
#[test]
fn eq() {
fn [<eq__ $backend>]() {
// two pointers
let mut unit = ();
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&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> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
assert_ne!(stuffed_ptr1, stuffed_ptr2);
mem::forget(stuffed_ptr2);
}
#[test]
fn dont_drop_extra_when_pointer() {
fn [<dont_drop_extra_when_pointer__ $backend>]() {
let mut unit = ();
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
// the panicking drop needs not to be called here!
drop(stuffed_ptr);
}
#[test]
fn some_traits_dont_drop() {
fn [<some_traits_dont_drop__ $backend>]() {
// 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);
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
// PartialEq
assert_eq!(stuffed_ptr1, stuffed_ptr2);
@ -380,3 +363,10 @@ mod tests {
mem::forget((stuffed_ptr1, stuffed_ptr2));
}
}
};
}
make_tests!(u128);
make_tests!(u64);
make_tests!(usize);
}

View file

@ -1,90 +0,0 @@
//! Several pre-defined strategies to use with `StuffedPtr`.
//!
//! * `()`: An empty strategy, is always the pointer
use crate::StuffingStrategy;
unsafe impl StuffingStrategy<usize> for () {
type Extra = ();
fn is_extra(_data: usize) -> bool {
false
}
fn stuff_extra(_inner: Self::Extra) -> usize {
0
}
unsafe fn extract_extra(_data: usize) -> Self::Extra {}
fn stuff_ptr(addr: usize) -> usize {
addr
}
fn extract_ptr(inner: usize) -> usize {
inner
}
}
#[cfg(test)]
pub(crate) mod test_strategies {
use crate::StuffingStrategy;
use std::fmt::{Debug, Formatter};
macro_rules! impl_usize_max_zst {
($ty:ident) => {
// this one lives in usize::MAX
unsafe impl StuffingStrategy<usize> for $ty {
type Extra = Self;
fn is_extra(data: usize) -> bool {
data == usize::MAX
}
#[allow(clippy::forget_copy)]
fn stuff_extra(inner: Self::Extra) -> usize {
std::mem::forget(inner);
usize::MAX
}
unsafe fn extract_extra(_data: usize) -> Self::Extra {
$ty
}
fn stuff_ptr(addr: usize) -> usize {
addr
}
fn extract_ptr(inner: usize) -> usize {
inner
}
}
};
}
#[derive(Clone, Copy)]
pub struct EmptyInMax;
impl_usize_max_zst!(EmptyInMax);
pub struct HasDebug;
impl Debug for HasDebug {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("hello!")
}
}
impl_usize_max_zst!(HasDebug);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PanicsInDrop;
impl Drop for PanicsInDrop {
fn drop(&mut self) {
panic!("oh no!!!");
}
}
impl_usize_max_zst!(PanicsInDrop);
}

222
src/strategy.rs Normal file
View file

@ -0,0 +1,222 @@
/// 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<I> {
/// 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: I) -> bool;
/// Stuff extra data into a usize that is then put into the pointer. This operation
/// must be infallible.
fn stuff_extra(inner: Self::Extra) -> I;
/// Extract extra data from the data.
/// # Safety
/// `data` must contain data created by [`StuffingStrategy::stuff_extra`].
unsafe fn extract_extra(data: I) -> 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(addr: usize) -> I;
/// Extract the pointer address from the data.
///
/// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`].
fn extract_ptr(inner: I) -> usize;
}
unsafe impl StuffingStrategy<usize> for () {
type Extra = ();
fn is_extra(_data: usize) -> bool {
false
}
fn stuff_extra(_inner: Self::Extra) -> usize {
0
}
unsafe fn extract_extra(_data: usize) -> Self::Extra {}
fn stuff_ptr(addr: usize) -> usize {
addr
}
fn extract_ptr(inner: usize) -> usize {
inner
}
}
unsafe impl StuffingStrategy<u64> for () {
type Extra = ();
fn is_extra(_data: u64) -> bool {
false
}
fn stuff_extra(_inner: Self::Extra) -> u64 {
0
}
unsafe fn extract_extra(_data: u64) -> Self::Extra {}
fn stuff_ptr(addr: usize) -> u64 {
addr as u64
}
fn extract_ptr(inner: u64) -> usize {
inner as usize
}
}
unsafe impl StuffingStrategy<u128> for () {
type Extra = ();
fn is_extra(_data: u128) -> bool {
false
}
fn stuff_extra(_inner: Self::Extra) -> u128 {
0
}
unsafe fn extract_extra(_data: u128) -> Self::Extra {}
fn stuff_ptr(addr: usize) -> u128 {
addr as u128
}
fn extract_ptr(inner: u128) -> usize {
inner as usize
}
}
#[cfg(test)]
pub(crate) mod test_strategies {
use std::fmt::{Debug, Formatter};
use super::StuffingStrategy;
macro_rules! impl_usize_max_zst {
($ty:ident) => {
// this one lives in usize::MAX
unsafe impl StuffingStrategy<usize> for $ty {
type Extra = Self;
fn is_extra(data: usize) -> bool {
data == usize::MAX
}
#[allow(clippy::forget_copy)]
fn stuff_extra(inner: Self::Extra) -> usize {
std::mem::forget(inner);
usize::MAX
}
unsafe fn extract_extra(_data: usize) -> Self::Extra {
$ty
}
fn stuff_ptr(addr: usize) -> usize {
addr
}
fn extract_ptr(inner: usize) -> usize {
inner
}
}
unsafe impl StuffingStrategy<u64> for $ty {
type Extra = Self;
fn is_extra(data: u64) -> bool {
data == u64::MAX
}
#[allow(clippy::forget_copy)]
fn stuff_extra(inner: Self::Extra) -> u64 {
std::mem::forget(inner);
u64::MAX
}
unsafe fn extract_extra(_data: u64) -> Self::Extra {
$ty
}
fn stuff_ptr(addr: usize) -> u64 {
addr as u64
}
fn extract_ptr(inner: u64) -> usize {
inner as usize
}
}
unsafe impl StuffingStrategy<u128> for $ty {
type Extra = Self;
fn is_extra(data: u128) -> bool {
data == u128::MAX
}
#[allow(clippy::forget_copy)]
fn stuff_extra(inner: Self::Extra) -> u128 {
std::mem::forget(inner);
u128::MAX
}
unsafe fn extract_extra(_data: u128) -> Self::Extra {
$ty
}
fn stuff_ptr(addr: usize) -> u128 {
addr as u128
}
fn extract_ptr(inner: u128) -> usize {
inner as usize
}
}
};
}
#[derive(Clone, Copy)]
pub struct EmptyInMax;
impl_usize_max_zst!(EmptyInMax);
pub struct HasDebug;
impl Debug for HasDebug {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("hello!")
}
}
impl_usize_max_zst!(HasDebug);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PanicsInDrop;
impl Drop for PanicsInDrop {
fn drop(&mut self) {
panic!("oh no!!!");
}
}
impl_usize_max_zst!(PanicsInDrop);
}