rootcause_internals/attachment/
raw.rs

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