1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
//! Module for [`BoxedDst`].
use crate::{Tag, TagTrait, TagTypeId};
use alloc::alloc::alloc;
use core::alloc::Layout;
use core::marker::PhantomData;
use core::mem::size_of;
use core::ops::Deref;
use core::ptr::NonNull;
/// A helper type to create boxed DST, i.e., tags with a dynamic size for the
/// builder. This is tricky in Rust. This type behaves similar to the regular
/// `Box` type except that it ensure the same layout is used for the (explicit)
/// allocation and the (implicit) deallocation of memory. Otherwise, I didn't
/// find any way to figure out the right layout for a DST. Miri always reported
/// issues that the deallocation used a wrong layout.
///
/// Technically, I'm certain this code is memory safe. But with this type, I
/// also can convince miri that it is.
#[derive(Debug, Eq)]
pub struct BoxedDst<T: ?Sized> {
ptr: core::ptr::NonNull<T>,
layout: Layout,
// marker: I used this only as the regular Box impl also does it.
_marker: PhantomData<T>,
}
impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
/// Create a boxed tag with the given content.
///
/// # Parameters
/// - `content` - All payload bytes of the DST tag without the tag type or
/// the size. The memory is only read and can be discarded
/// afterwards.
pub(crate) fn new(content: &[u8]) -> Self {
// Currently, I do not find a nice way of making this dynamic so that
// also miri is guaranteed to be happy. But it seems that 4 is fine
// here. I do have control over allocation and deallocation.
const ALIGN: usize = 4;
let tag_size = size_of::<TagTypeId>() + size_of::<u32>() + content.len();
// By using miri, I could figure out that there often are problems where
// miri thinks an allocation is smaller then necessary. Most probably
// due to not packed structs. Using packed structs however
// (especially with DSTs), is a crazy ass pain and unusable :/ Therefore,
// the best solution I can think of is to allocate a few byte more than
// necessary. I think that during runtime, everything works fine and
// that no memory issues are present.
let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary
let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap();
let ptr = unsafe { alloc(layout) };
assert!(!ptr.is_null());
// write tag content to memory
unsafe {
// write tag type
let ptrx = ptr.cast::<TagTypeId>();
ptrx.write(T::ID.into());
// write tag size
let ptrx = ptrx.add(1).cast::<u32>();
ptrx.write(tag_size as u32);
// write rest of content
let ptrx = ptrx.add(1).cast::<u8>();
let tag_content_slice = core::slice::from_raw_parts_mut(ptrx, content.len());
for (i, &byte) in content.iter().enumerate() {
tag_content_slice[i] = byte;
}
}
let base_tag = unsafe { &*ptr.cast::<Tag>() };
let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag));
Self {
ptr: NonNull::new(raw).unwrap(),
layout,
_marker: PhantomData,
}
}
}
impl<T: ?Sized> Drop for BoxedDst<T> {
fn drop(&mut self) {
unsafe { alloc::alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) }
}
}
impl<T: ?Sized> Deref for BoxedDst<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
fn eq(&self, other: &Self) -> bool {
self.deref().eq(other.deref())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tag::StringError;
use crate::TagType;
const METADATA_SIZE: usize = 8;
#[derive(ptr_meta::Pointee)]
#[repr(C)]
struct CustomTag {
typ: TagTypeId,
size: u32,
string: [u8],
}
impl CustomTag {
fn string(&self) -> Result<&str, StringError> {
Tag::parse_slice_as_string(&self.string)
}
}
impl TagTrait for CustomTag {
const ID: TagType = TagType::Custom(0x1337);
fn dst_size(base_tag: &Tag) -> usize {
assert!(base_tag.size as usize >= METADATA_SIZE);
base_tag.size as usize - METADATA_SIZE
}
}
#[test]
fn test_boxed_dst_tag() {
let content = b"hallo\0";
let content_rust_str = "hallo";
let tag = BoxedDst::<CustomTag>::new(content);
assert_eq!(tag.typ, CustomTag::ID);
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
assert_eq!(tag.string(), Ok(content_rust_str));
}
#[test]
fn can_hold_tag_trait() {
fn consume<T: TagTrait + ?Sized>(_: &T) {}
let content = b"hallo\0";
let tag = BoxedDst::<CustomTag>::new(content);
consume(tag.deref());
consume(&*tag);
// Compiler not smart enough?
// consume(&tag);
}
}