rootcause_internals/attachment/
raw.rs

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