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<'a>(&'a self) -> RawAttachmentRef<'a> {
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    /// - `report_formatting_alternate`: Whether the report in which this attachment will be embedded is being formatted using the [`alternate`] mode
163    ///
164    /// [`Display`]: core::fmt::Display
165    /// [`Debug`]: core::fmt::Debug
166    /// [`alternate`]: core::fmt::Formatter::alternate
167    pub fn preferred_formatting_style(
168        self,
169        report_formatting_function: FormattingFunction,
170        report_formatting_alternate: bool,
171    ) -> AttachmentFormattingStyle {
172        let vtable = self.vtable();
173        // SAFETY: We must ensure that the `A` of the `AttachmentData` matches the `A` of the `AttachmentVtable`.
174        // However, the only way to construct an `AttachmentData` is through the `AttachmentData::new` method,
175        // which ensures this fact.
176        unsafe {
177            vtable.preferred_formatting_style(
178                self,
179                report_formatting_function,
180                report_formatting_alternate,
181            )
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use alloc::string::String;
189
190    use super::*;
191    use crate::handlers::AttachmentHandler;
192
193    struct HandlerI32;
194    impl AttachmentHandler<i32> for HandlerI32 {
195        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196            core::fmt::Display::fmt(value, formatter)
197        }
198
199        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200            core::fmt::Debug::fmt(value, formatter)
201        }
202    }
203
204    struct HandlerString;
205    impl AttachmentHandler<String> for HandlerString {
206        fn display(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207            core::fmt::Display::fmt(value, formatter)
208        }
209        fn debug(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210            core::fmt::Debug::fmt(value, formatter)
211        }
212    }
213
214    #[test]
215    fn test_raw_attachment_size() {
216        assert_eq!(
217            core::mem::size_of::<RawAttachment>(),
218            core::mem::size_of::<usize>()
219        );
220        assert_eq!(
221            core::mem::size_of::<Option<RawAttachment>>(),
222            core::mem::size_of::<usize>()
223        );
224        assert_eq!(
225            core::mem::size_of::<Result<(), RawAttachment>>(),
226            core::mem::size_of::<usize>()
227        );
228        assert_eq!(
229            core::mem::size_of::<Result<String, RawAttachment>>(),
230            core::mem::size_of::<String>()
231        );
232        assert_eq!(
233            core::mem::size_of::<Option<Option<RawAttachment>>>(),
234            core::mem::size_of::<Option<usize>>()
235        );
236
237        assert_eq!(
238            core::mem::size_of::<RawAttachmentRef<'_>>(),
239            core::mem::size_of::<usize>()
240        );
241        assert_eq!(
242            core::mem::size_of::<Option<RawAttachmentRef<'_>>>(),
243            core::mem::size_of::<usize>()
244        );
245        assert_eq!(
246            core::mem::size_of::<Result<(), RawAttachmentRef<'_>>>(),
247            core::mem::size_of::<usize>()
248        );
249        assert_eq!(
250            core::mem::size_of::<Result<String, RawAttachmentRef<'_>>>(),
251            core::mem::size_of::<String>()
252        );
253        assert_eq!(
254            core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
255            core::mem::size_of::<Option<usize>>()
256        );
257    }
258
259    #[test]
260    fn test_raw_attachment_get_refs() {
261        let attachment = RawAttachment::new::<i32, HandlerI32>(100);
262        let attachment_ref = attachment.as_ref();
263
264        // Accessing the pointer multiple times should be safe and consistent
265        let ptr1 = attachment_ref.as_ptr();
266        let ptr2 = attachment_ref.as_ptr();
267        assert_eq!(ptr1, ptr2);
268    }
269
270    #[test]
271    fn test_raw_attachment_downcast() {
272        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
273        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
274
275        let int_ref = int_attachment.as_ref();
276        let string_ref = string_attachment.as_ref();
277
278        // Are TypeIds what we expect?
279        assert_eq!(int_ref.attachment_type_id(), TypeId::of::<i32>());
280        assert_eq!(string_ref.attachment_type_id(), TypeId::of::<String>());
281
282        // The vtables should be different
283        assert!(!core::ptr::eq(int_ref.vtable(), string_ref.vtable()));
284
285        // Cross-type downcasting should fail safely
286        assert!(int_ref.attachment_downcast::<String>().is_none());
287        assert!(string_ref.attachment_downcast::<i32>().is_none());
288
289        // Correct downcasting should work
290        assert_eq!(int_ref.attachment_downcast::<i32>().unwrap(), &42);
291        assert_eq!(string_ref.attachment_downcast::<String>().unwrap(), "test");
292    }
293
294    #[test]
295    fn test_raw_attachment_display_debug() {
296        use alloc::format;
297
298        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
299        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
300
301        let int_ref = int_attachment.as_ref();
302        let string_ref = string_attachment.as_ref();
303
304        // Test display formatting
305        let display_int = format!(
306            "{}",
307            TestDisplayFormatter::new(|f| int_ref.attachment_display(f))
308        );
309        let display_string = format!(
310            "{}",
311            TestDisplayFormatter::new(|f| string_ref.attachment_display(f))
312        );
313
314        assert_eq!(display_int, "42");
315        assert_eq!(display_string, "test");
316
317        // Test debug formatting
318        let debug_int = format!(
319            "{}",
320            TestDisplayFormatter::new(|f| int_ref.attachment_debug(f))
321        );
322        let debug_string = format!(
323            "{}",
324            TestDisplayFormatter::new(|f| string_ref.attachment_debug(f))
325        );
326
327        assert_eq!(debug_int, "42");
328        assert_eq!(debug_string, "\"test\"");
329    }
330
331    // Helper struct for testing display/debug formatting
332    struct TestDisplayFormatter<F> {
333        formatter_fn: F,
334    }
335
336    impl<F> TestDisplayFormatter<F>
337    where
338        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
339    {
340        fn new(formatter_fn: F) -> Self {
341            Self { formatter_fn }
342        }
343    }
344
345    impl<F> core::fmt::Display for TestDisplayFormatter<F>
346    where
347        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
348    {
349        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
350            (self.formatter_fn)(f)
351        }
352    }
353}