rootcause_internals/attachment/
raw.rs

1//! Type-erased attachment pointer types.
2//!
3//! This module encapsulates the `ptr` field of [`RawAttachment`] and
4//! [`RawAttachmentRef`], ensuring it is only visible within this module. This
5//! visibility restriction guarantees the safety invariant: **the pointer always
6//! comes from `Box<AttachmentData<A>>`**.
7//!
8//! # Safety Invariant
9//!
10//! Since the `ptr` field can only be set via [`RawAttachment::new`] (which
11//! creates it from `Box::into_raw`), and cannot be modified afterward (no `pub`
12//! or `pub(crate)` fields), the pointer provenance remains valid throughout the
13//! value's lifetime.
14//!
15//! The [`RawAttachment::drop`] implementation relies on this invariant to safely
16//! reconstruct the `Box` and deallocate the memory.
17//!
18//! # Type Erasure
19//!
20//! The concrete type parameter `A` is erased by casting to `AttachmentData<Erased>`.
21//! The vtable stored within the `AttachmentData` provides the runtime type
22//! information needed to safely downcast and format attachments.
23
24use alloc::boxed::Box;
25use core::{any::TypeId, ptr::NonNull};
26
27use crate::{
28    attachment::data::AttachmentData,
29    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
30    util::{CastTo, Erased},
31};
32
33/// A pointer to an [`AttachmentData`] that is guaranteed to point to an
34/// initialized instance of an [`AttachmentData<A>`] for some specific `A`,
35/// though we do not know which actual `A` it is.
36///
37/// However, the pointer is allowed to transition into a non-initialized state
38/// inside the [`RawAttachment::drop`] method.
39///
40/// The pointer is guaranteed to have been created using [`Box::into_raw`].
41///
42/// We cannot use a [`Box<AttachmentData<A>>`] directly, because that does not
43/// allow us to type-erase the `A`.
44#[repr(transparent)]
45pub struct RawAttachment {
46    /// Pointer to the inner attachment data
47    ptr: NonNull<AttachmentData<Erased>>,
48}
49
50impl RawAttachment {
51    /// Creates a new [`RawAttachment`] with the specified handler and
52    /// attachment.
53    #[inline]
54    pub fn new<A, H>(attachment: A) -> Self
55    where
56        A: 'static,
57        H: AttachmentHandler<A>,
58    {
59        let ptr = Box::new(AttachmentData::new::<H>(attachment));
60        let ptr: *const AttachmentData<A> = Box::into_raw(ptr);
61        let ptr: *mut AttachmentData<Erased> = ptr as _;
62
63        // SAFETY: `Box::into_raw` returns a non-null pointer
64        let ptr: NonNull<AttachmentData<Erased>> = unsafe { NonNull::new_unchecked(ptr) };
65
66        Self { ptr }
67    }
68
69    /// Returns a reference to the [`AttachmentData`] instance.
70    #[inline]
71    pub fn as_ref(&self) -> RawAttachmentRef<'_> {
72        RawAttachmentRef {
73            ptr: self.ptr,
74            _marker: core::marker::PhantomData,
75        }
76    }
77}
78
79impl core::ops::Drop for RawAttachment {
80    #[inline]
81    fn drop(&mut self) {
82        let vtable = self.as_ref().vtable();
83
84        // SAFETY: The vtable drop method has three safety requirements:
85        // - The pointer must come from `Box<AttachmentData<A>>` via `Box::into_raw`
86        // - The `A` type in `AttachmentData<A>` must match the vtable's `A` type
87        // - The pointer must not be used after this call
88        //
89        // These are satisfied because:
90        // - The only way to construct or alter a `RawAttachment` is through the
91        //   `RawAttachment::new` method
92        // - The only way to construct or alter an `AttachmentData` is through the
93        //   `AttachmentData::new` method
94        // - This is guaranteed by the fact that we are in the `drop()` function
95        unsafe {
96            vtable.drop(self.ptr);
97        }
98    }
99}
100
101/// A lifetime-bound pointer to an [`AttachmentData`] that is guaranteed to
102/// point to an initialized instance of an [`AttachmentData<A>`] for some
103/// specific `A`, though we do not know which actual `A` it is.
104///
105/// We cannot use a [`&'a AttachmentData<A>`] directly, because that would
106/// require us to know the actual type of the attachment, which we do not.
107///
108/// [`&'a AttachmentData<A>`]: AttachmentData
109#[derive(Clone, Copy)]
110#[repr(transparent)]
111pub struct RawAttachmentRef<'a> {
112    /// Pointer to the inner attachment data
113    ptr: NonNull<AttachmentData<Erased>>,
114    /// Marker to tell the compiler that we should
115    /// behave the same as a `&'a AttachmentData<Erased>`
116    _marker: core::marker::PhantomData<&'a AttachmentData<Erased>>,
117}
118
119impl<'a> RawAttachmentRef<'a> {
120    /// Casts the [`RawAttachmentRef`] to an [`AttachmentData<A>`] reference.
121    ///
122    /// # Safety
123    ///
124    /// The caller must ensure that the type `A` matches the actual attachment
125    /// type stored in the [`AttachmentData`].
126    #[inline]
127    pub(super) unsafe fn cast_inner<A: CastTo>(self) -> &'a AttachmentData<A::Target> {
128        // Debug assertion to catch type mismatches in case of bugs
129        debug_assert_eq!(self.vtable().type_id(), TypeId::of::<A>());
130
131        let this = self.ptr.cast::<AttachmentData<A::Target>>();
132        // SAFETY: Our caller guarantees that we point to an AttachmentData<A>.
133        // Converting NonNull to a reference is safe because:
134        // - The pointer is valid and aligned (from Box allocation in RawAttachment::new)
135        // - The data is initialized (Box allocation initializes)
136        // - The lifetime 'a is tied to the RawAttachmentRef<'a>, preventing use-after-free
137        // - Shared access through RawAttachmentRef prevents aliasing violations
138        unsafe { this.as_ref() }
139    }
140
141    /// Returns a [`NonNull`] pointer to the [`AttachmentData`] instance.
142    #[inline]
143    pub(super) fn as_ptr(self) -> *const AttachmentData<Erased> {
144        self.ptr.as_ptr()
145    }
146
147    /// Returns the [`TypeId`] of the attachment.
148    #[inline]
149    pub fn attachment_type_id(self) -> TypeId {
150        self.vtable().type_id()
151    }
152
153    /// Returns the [`TypeId`] of the attachment.
154    #[inline]
155    pub fn attachment_handler_type_id(self) -> TypeId {
156        self.vtable().handler_type_id()
157    }
158
159    /// Checks if the type of the attachment matches the specified type and
160    /// returns a reference to it if it does.
161    #[inline]
162    pub fn attachment_downcast<A: 'static>(self) -> Option<&'a A> {
163        if self.attachment_type_id() == core::any::TypeId::of::<A>() {
164            // SAFETY: We must ensure that the `A` in the AttachmentData matches the `A` we
165            // are using as an argument. However, we have just checked that the
166            // types match, so that is fine.
167            unsafe { Some(self.attachment_downcast_unchecked::<A>()) }
168        } else {
169            None
170        }
171    }
172
173    /// Formats the attachment by using the [`AttachmentHandler::display`]
174    /// method specified by the handler used to create the
175    /// [`AttachmentData`].
176    #[inline]
177    pub fn attachment_display(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
178        let vtable = self.vtable();
179        // SAFETY: We must ensure that the `A` of the `AttachmentData` matches the `A`
180        // of the `AttachmentVtable`. However, the only way to construct an
181        // `AttachmentData` is through the `AttachmentData::new` method,
182        // which ensures this fact.
183        unsafe { vtable.display(self, formatter) }
184    }
185
186    /// Formats the attachment by using the [`AttachmentHandler::debug`] method
187    /// specified by the handler used to create the [`AttachmentData`].
188    #[inline]
189    pub fn attachment_debug(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190        let vtable = self.vtable();
191        // SAFETY: We must ensure that the `A` of the `AttachmentData` matches the `A`
192        // of the `AttachmentVtable`. However, the only way to construct an
193        // `AttachmentData` is through the `AttachmentData::new` method,
194        // which ensures this fact.
195        unsafe { vtable.debug(self, formatter) }
196    }
197
198    /// The formatting style preferred by the attachment when formatted as part
199    /// of a report.
200    ///
201    /// # Arguments
202    ///
203    /// - `report_formatting_function`: Whether the report in which this
204    ///   attachment will be embedded is being formatted using [`Display`]
205    ///   formatting or [`Debug`]
206    ///
207    /// [`Display`]: core::fmt::Display
208    /// [`Debug`]: core::fmt::Debug
209    #[inline]
210    pub fn preferred_formatting_style(
211        self,
212        report_formatting_function: FormattingFunction,
213    ) -> AttachmentFormattingStyle {
214        let vtable = self.vtable();
215        // SAFETY: We must ensure that the `A` of the `AttachmentData` matches the `A`
216        // of the `AttachmentVtable`. However, the only way to construct an
217        // `AttachmentData` is through the `AttachmentData::new` method,
218        // which ensures this fact.
219        unsafe { vtable.preferred_formatting_style(self, report_formatting_function) }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use alloc::string::String;
226
227    use super::*;
228    use crate::handlers::AttachmentHandler;
229
230    struct HandlerI32;
231    impl AttachmentHandler<i32> for HandlerI32 {
232        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233            core::fmt::Display::fmt(value, formatter)
234        }
235
236        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237            core::fmt::Debug::fmt(value, formatter)
238        }
239    }
240
241    struct HandlerString;
242    impl AttachmentHandler<String> for HandlerString {
243        fn display(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244            core::fmt::Display::fmt(value, formatter)
245        }
246
247        fn debug(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248            core::fmt::Debug::fmt(value, formatter)
249        }
250    }
251
252    #[test]
253    fn test_raw_attachment_size() {
254        assert_eq!(
255            core::mem::size_of::<RawAttachment>(),
256            core::mem::size_of::<usize>()
257        );
258        assert_eq!(
259            core::mem::size_of::<Option<RawAttachment>>(),
260            core::mem::size_of::<usize>()
261        );
262        assert_eq!(
263            core::mem::size_of::<Result<(), RawAttachment>>(),
264            core::mem::size_of::<usize>()
265        );
266        assert_eq!(
267            core::mem::size_of::<Result<String, RawAttachment>>(),
268            core::mem::size_of::<String>()
269        );
270        assert_eq!(
271            core::mem::size_of::<Option<Option<RawAttachment>>>(),
272            core::mem::size_of::<Option<usize>>()
273        );
274
275        assert_eq!(
276            core::mem::size_of::<RawAttachmentRef<'_>>(),
277            core::mem::size_of::<usize>()
278        );
279        assert_eq!(
280            core::mem::size_of::<Option<RawAttachmentRef<'_>>>(),
281            core::mem::size_of::<usize>()
282        );
283        assert_eq!(
284            core::mem::size_of::<Result<(), RawAttachmentRef<'_>>>(),
285            core::mem::size_of::<usize>()
286        );
287        assert_eq!(
288            core::mem::size_of::<Result<String, RawAttachmentRef<'_>>>(),
289            core::mem::size_of::<String>()
290        );
291        assert_eq!(
292            core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
293            core::mem::size_of::<Option<usize>>()
294        );
295    }
296
297    #[test]
298    fn test_raw_attachment_get_refs() {
299        let attachment = RawAttachment::new::<i32, HandlerI32>(100);
300        let attachment_ref = attachment.as_ref();
301
302        // Accessing the pointer multiple times should be safe and consistent
303        let ptr1 = attachment_ref.as_ptr();
304        let ptr2 = attachment_ref.as_ptr();
305        assert_eq!(ptr1, ptr2);
306    }
307
308    #[test]
309    fn test_raw_attachment_downcast() {
310        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
311        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
312
313        let int_ref = int_attachment.as_ref();
314        let string_ref = string_attachment.as_ref();
315
316        // Are TypeIds what we expect?
317        assert_eq!(int_ref.attachment_type_id(), TypeId::of::<i32>());
318        assert_eq!(string_ref.attachment_type_id(), TypeId::of::<String>());
319
320        // The vtables should be different
321        assert!(!core::ptr::eq(int_ref.vtable(), string_ref.vtable()));
322
323        // Cross-type downcasting should fail safely
324        assert!(int_ref.attachment_downcast::<String>().is_none());
325        assert!(string_ref.attachment_downcast::<i32>().is_none());
326
327        // Correct downcasting should work
328        assert_eq!(int_ref.attachment_downcast::<i32>().unwrap(), &42);
329        assert_eq!(string_ref.attachment_downcast::<String>().unwrap(), "test");
330    }
331
332    #[test]
333    fn test_raw_attachment_display_debug() {
334        use alloc::format;
335
336        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
337        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
338
339        let int_ref = int_attachment.as_ref();
340        let string_ref = string_attachment.as_ref();
341
342        // Test display formatting
343        let display_int = format!(
344            "{}",
345            TestDisplayFormatter::new(|f| int_ref.attachment_display(f))
346        );
347        let display_string = format!(
348            "{}",
349            TestDisplayFormatter::new(|f| string_ref.attachment_display(f))
350        );
351
352        assert_eq!(display_int, "42");
353        assert_eq!(display_string, "test");
354
355        // Test debug formatting
356        let debug_int = format!(
357            "{}",
358            TestDisplayFormatter::new(|f| int_ref.attachment_debug(f))
359        );
360        let debug_string = format!(
361            "{}",
362            TestDisplayFormatter::new(|f| string_ref.attachment_debug(f))
363        );
364
365        assert_eq!(debug_int, "42");
366        assert_eq!(debug_string, "\"test\"");
367    }
368
369    // Helper struct for testing display/debug formatting
370    struct TestDisplayFormatter<F> {
371        formatter_fn: F,
372    }
373
374    impl<F> TestDisplayFormatter<F>
375    where
376        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
377    {
378        fn new(formatter_fn: F) -> Self {
379            Self { formatter_fn }
380        }
381    }
382
383    impl<F> core::fmt::Display for TestDisplayFormatter<F>
384    where
385        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
386    {
387        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388            (self.formatter_fn)(f)
389        }
390    }
391
392    #[test]
393    fn test_send_sync() {
394        static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync);
395        static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync);
396    }
397}