rootcause_internals/report/
data.rs

1//! This module encapsulates the fields of the [`ReportData`]. Since this is the only place
2//! they are visible, this means that the type of the [`ReportVtable`] is guaranteed to always be in sync
3//! with the type of the actual context. This follows from the fact that they are in sync
4//! when created and that the API offers no way to change the [`ReportVtable`] or context type after
5//! creation.
6
7use core::ptr::NonNull;
8
9use alloc::vec::Vec;
10
11use crate::{
12    attachment::RawAttachment,
13    handlers::ContextHandler,
14    report::{
15        raw::{RawReport, RawReportMut, RawReportRef},
16        vtable::ReportVtable,
17    },
18    util::Erased,
19};
20
21/// Type-erased report data structure with vtable-based dispatch.
22///
23/// This struct uses `#[repr(C)]` to enable safe field access in type-erased contexts,
24/// allowing access to the vtable and other fields even when the concrete context type `C` is unknown.
25#[repr(C)]
26pub(super) struct ReportData<C: 'static> {
27    vtable: &'static ReportVtable,
28    children: Vec<RawReport>,
29    attachments: Vec<RawAttachment>,
30    context: C,
31}
32
33impl<C: 'static> ReportData<C> {
34    /// Creates a new [`ReportData`] with the specified handler, context, children and attachments.
35    ///
36    /// This method creates the vtable for type-erased dispatch and pairs it with the report data.
37    pub(super) fn new<H: ContextHandler<C>>(
38        context: C,
39        children: Vec<RawReport>,
40        attachments: Vec<RawAttachment>,
41    ) -> Self {
42        Self {
43            vtable: ReportVtable::new::<C, H>(),
44            children,
45            attachments,
46            context,
47        }
48    }
49}
50
51impl RawReport {
52    /// # Safety
53    ///
54    /// - The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
55    /// - The caller must ensure that this is the only existing reference pointing to
56    ///   the inner [`ReportData`].
57    pub unsafe fn into_parts<C: 'static>(self) -> (C, Vec<RawReport>, Vec<RawAttachment>) {
58        let ptr: NonNull<ReportData<Erased>> = self.into_non_null();
59        let ptr: NonNull<ReportData<C>> = ptr.cast::<ReportData<C>>();
60        let arc: triomphe::Arc<ReportData<C>> = unsafe { triomphe::Arc::from_raw(ptr.as_ptr()) };
61        match triomphe::Arc::try_unique(arc) {
62            Ok(unique) => {
63                let data = triomphe::UniqueArc::into_inner(unique);
64                (data.context, data.children, data.attachments)
65            }
66            Err(_) => {
67                if cfg!(debug_assertions) {
68                    unreachable!("Caller did not fulfill the guarantee that pointer is unique")
69                } else {
70                    unsafe { core::hint::unreachable_unchecked() }
71                }
72            }
73        }
74    }
75}
76
77impl<'a> RawReportRef<'a> {
78    /// Returns a reference to the [`ReportVtable`] of the [`ReportData`] instance.
79    pub(super) fn vtable(self) -> &'static ReportVtable {
80        let ptr = self.as_ptr();
81        // SAFETY: We don't know the actual inner context type, but we do know
82        // that it points to an instance of `ReportData<C>` for some specific `C`.
83        // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
84        // the fields before the actual context.
85        //
86        // We need to take care to avoid creating an actual reference to
87        // the `ReportData` itself though, as that would still be undefined behavior
88        // since we don't have the right type.
89        unsafe {
90            let vtable_ptr = &raw const (*ptr).vtable;
91            *vtable_ptr
92        }
93    }
94
95    /// Returns the child reports of this report.
96    pub fn children(self) -> &'a [RawReport] {
97        let ptr = self.as_ptr();
98        // SAFETY: We don't know the actual inner context type, but we do know
99        // that it points to an instance of `ReportData<C>` for some specific `C`.
100        // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
101        // the fields before the actual context.
102        //
103        // We need to take care to avoid creating an actual reference to
104        // the `ReportData` itself though, as that would still be undefined behavior
105        // since we don't have the right type.
106        //
107        // We return a pointer with lifetime `'a`, which we can do because the `RawReportRef<'a>`
108        // guarantees that the data is valid to read for at least that long.
109        unsafe {
110            let children_ptr = &raw const (*ptr).children;
111            &*children_ptr
112        }
113    }
114
115    /// Returns the attachments of this report.
116    pub fn attachments(self) -> &'a [RawAttachment] {
117        let ptr = self.as_ptr();
118        // SAFETY: We don't know the actual inner context type, but we do know
119        // that it points to an instance of `ReportData<C>` for some specific `C`.
120        // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
121        // the fields before the actual context.
122        //
123        // We need to take care to avoid creating an actual reference to
124        // the `ReportData` itself though, as that would still be undefined behavior
125        // since we don't have the right type.
126        //
127        // We return a pointer with lifetime `'a`, which we can do because the `RawReportRef<'a>`
128        // guarantees that the data is valid to read for at least that long.
129        unsafe {
130            let attachments_ptr = &raw const (*ptr).attachments;
131            &*attachments_ptr
132        }
133    }
134
135    /// Accesses the inner context of the [`ReportData`] instance as a reference to the specified type.
136    ///
137    /// # Safety
138    ///
139    /// The caller must ensure that the type `C` matches the actual context type stored in the [`ReportData`].
140    pub unsafe fn context_downcast_unchecked<C: 'static>(self) -> &'a C {
141        // SAFETY: The inner function requires that `C` matches the type stored, but that is guaranteed by our caller.
142        let this = unsafe { self.cast_inner::<C>() };
143        &this.context
144    }
145}
146
147impl<'a> RawReportMut<'a> {
148    /// Gets a mutable reference to the child reports.
149    pub fn children_mut(self) -> &'a mut Vec<RawReport> {
150        let ptr = self.into_mut_ptr();
151
152        // SAFETY: We don't know the actual inner context type, but we do know
153        // that it points to an instance of `ReportData<C>` for some specific `C`.
154        // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
155        // the fields before the actual context.
156        //
157        // We need to take care to avoid creating an actual reference to
158        // the `ReportData` itself though, as that would still be undefined behavior
159        // since we don't have the right type.
160        //
161        // We return a mutable pointer with lifetime `'a`, which we can do because the `&'a mut self`
162        // guarantees that the data is valid to mutate for at least that long.
163        unsafe {
164            let children_ptr = &raw mut (*ptr).children;
165            &mut *children_ptr
166        }
167    }
168
169    /// Gets a mutable reference to the attachments.
170    ///
171    /// # Safety
172    ///
173    /// The caller must ensure that this is the only existing reference pointing to
174    /// the inner [`ReportData`].
175    pub fn attachments_mut(self) -> &'a mut Vec<RawAttachment> {
176        let ptr = self.into_mut_ptr();
177
178        // SAFETY: We don't know the actual inner context type, but we do know
179        // that it points to an instance of `ReportData<C>` for some specific `C`.
180        // Since `ReportData<C>` is `#[repr(C)]`, that means we can access
181        // the fields before the actual context.
182        //
183        // We need to take care to avoid creating an actual reference to
184        // the `ReportData` itself though, as that would still be undefined behavior
185        // since we don't have the right type.
186        //
187        // We return a mutable pointer with lifetime `'a`, which we can do because the `&'a mut self`
188        // guarantees that the data is valid to mutate for at least that long.
189        unsafe {
190            let attachments_ptr = &raw mut (*ptr).attachments;
191            &mut *attachments_ptr
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_report_data_field_offsets() {
202        // Test that fields are accessible in the expected order for type-erased access
203        use core::mem::{offset_of, size_of};
204
205        fn check<T>() {
206            // Verify field order: vtable, children, attachments, context
207            assert_eq!(offset_of!(ReportData<T>, vtable), 0);
208            assert_eq!(
209                offset_of!(ReportData<T>, children),
210                size_of::<&'static ReportVtable>()
211            );
212            assert_eq!(
213                offset_of!(ReportData<T>, attachments),
214                size_of::<&'static ReportVtable>() + size_of::<Vec<RawAttachment>>()
215            );
216            assert!(
217                offset_of!(ReportData<T>, context)
218                    >= size_of::<&'static ReportVtable>()
219                        + size_of::<Vec<RawAttachment>>()
220                        + size_of::<Vec<RawReport>>()
221            );
222        }
223
224        #[repr(align(32))]
225        struct LargeAlignment {
226            _value: u8,
227        }
228
229        check::<u8>();
230        check::<i32>();
231        check::<[u64; 4]>();
232        check::<i32>();
233        check::<LargeAlignment>();
234    }
235}