rootcause_internals/report/
data.rs

1//! `ReportData<C>` wrapper and field access.
2//!
3//! This module encapsulates the fields of [`ReportData`], ensuring they are
4//! only visible within this module. This visibility restriction guarantees the
5//! safety invariant: **the vtable type must always match the actual context
6//! type**.
7//!
8//! # Safety Invariant
9//!
10//! Since [`ReportData`] can only be constructed via [`ReportData::new`] (which
11//! creates matching vtable and context), and fields cannot be modified after
12//! construction (no `pub` or `pub(crate)` fields), the types remain in sync
13//! throughout the value's lifetime.
14//!
15//! # `#[repr(C)]` Layout
16//!
17//! The `#[repr(C)]` attribute enables safe field projection even when the type
18//! parameter `C` is erased. This allows accessing the vtable, children, and
19//! attachments fields from a pointer to `ReportData<Erased>` without
20//! constructing an invalid reference to the full struct.
21
22use alloc::vec::Vec;
23use core::ptr::NonNull;
24
25use crate::{
26    attachment::RawAttachment,
27    handlers::ContextHandler,
28    report::{
29        raw::{RawReport, RawReportMut, RawReportRef},
30        vtable::ReportVtable,
31    },
32    util::Erased,
33};
34
35/// Type-erased report data structure with vtable-based dispatch.
36///
37/// This struct uses `#[repr(C)]` to enable safe field access in type-erased
38/// contexts, allowing access to the vtable and other fields even when the
39/// concrete context type `C` is unknown.
40#[repr(C)]
41pub(crate) struct ReportData<C: 'static> {
42    /// Reference to the vtable of this report
43    ///
44    /// # Safety
45    ///
46    /// The following safety invariants are guaranteed to be upheld as long as
47    /// this struct exists:
48    ///
49    /// 1. The vtable must always point to a `ReportVtable` created for the
50    ///    actual context type `C` stored below. This is true even when accessed
51    ///    via type-erased pointers.
52    vtable: &'static ReportVtable,
53    /// The children of this report
54    children: Vec<RawReport>,
55    /// The attachments of this report
56    attachments: Vec<RawAttachment>,
57    /// The context data of this report
58    context: C,
59}
60
61impl<C: 'static> ReportData<C> {
62    /// Creates a new [`ReportData`] with the specified handler, context,
63    /// children and attachments.
64    ///
65    /// This method creates the vtable for type-erased dispatch and pairs it
66    /// with the report data.
67    #[inline]
68    pub(super) fn new<H: ContextHandler<C>>(
69        context: C,
70        children: Vec<RawReport>,
71        attachments: Vec<RawAttachment>,
72    ) -> Self {
73        Self {
74            vtable: ReportVtable::new::<C, H>(),
75            children,
76            attachments,
77            context,
78        }
79    }
80}
81
82impl RawReport {
83    /// Deconstructs this report into its context, children, and attachments.
84    ///
85    /// # Safety
86    ///
87    /// The caller must ensure:
88    ///
89    /// 1. The type `C` matches the actual context type stored in the
90    ///    [`ReportData`]
91    /// 2. This is the only existing reference pointing to the inner
92    ///    [`ReportData`]
93    pub unsafe fn into_parts<C: 'static>(self) -> (C, Vec<RawReport>, Vec<RawAttachment>) {
94        let ptr: NonNull<ReportData<Erased>> = self.into_non_null();
95        let ptr: NonNull<ReportData<C>> = ptr.cast::<ReportData<C>>();
96        let ptr: *const ReportData<C> = ptr.as_ptr();
97
98        // SAFETY:
99        // 1. The pointer is valid and came from `Arc::into_raw` (guaranteed by
100        //    RawReport construction)
101        // 2. After `from_raw` the `ptr` is not accessed.
102        let arc = unsafe { triomphe::Arc::<ReportData<C>>::from_raw(ptr) };
103
104        match triomphe::Arc::try_unique(arc) {
105            Ok(unique) => {
106                let data = triomphe::UniqueArc::into_inner(unique);
107                (data.context, data.children, data.attachments)
108            }
109            Err(_) => {
110                // Note: We could use `unreachable_unchecked` here in release builds for
111                // performance, but `into_parts` is not expected to be used in
112                // performance-critical paths, so a normal panic is preferable for
113                // better debugging.
114                unreachable!("Caller did not fulfill the guarantee that pointer is unique")
115            }
116        }
117    }
118}
119
120impl<'a> RawReportRef<'a> {
121    /// Returns a reference to the [`ReportVtable`] of this report.
122    ///
123    /// The returned vtable is guaranteed to match the context type stored in
124    /// the [`ReportData`].
125    #[inline]
126    pub(super) fn vtable(self) -> &'static ReportVtable {
127        let ptr = self.as_ptr();
128        // SAFETY: The safety requirements for `&raw const (*ptr).vtable` are upheld:
129        // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
130        //    guaranteed by `RawReportRef`'s invariants
131        // 2. `ReportData<C>` is `#[repr(C)]`, so the `vtable` field is at a consistent
132        //    offset regardless of the type parameter `C`
133        // 3. We avoid creating a reference to the full `ReportData` struct, which would
134        //    be UB since we don't know the correct type parameter
135        let vtable_ptr: *const &'static ReportVtable = unsafe {
136            // @add-unsafe-context: ReportData
137            // @add-unsafe-context: crate::util::Erased
138            &raw const (*ptr).vtable
139        };
140
141        // SAFETY: The safety requirements for dereferencing `vtable_ptr` are upheld:
142        // 1. The pointer is valid and properly aligned because it points to the first
143        //    field of a valid `ReportData<C>` instance
144        // 2. The `vtable` field is initialized in `ReportData::new` and never modified,
145        //    so it contains a valid `&'static ReportVtable` value
146        unsafe { *vtable_ptr }
147    }
148
149    /// Returns the child reports of this report.
150    #[inline]
151    pub fn children(self) -> &'a Vec<RawReport> {
152        let ptr: *const ReportData<Erased> = self.as_ptr();
153
154        // SAFETY: The safety requirements for `&raw const (*ptr).children` are upheld:
155        // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
156        //    guaranteed by `RawReportRef`'s invariants
157        // 2. `ReportData<C>` is `#[repr(C)]`, so the `children` field is at a
158        //    consistent offset regardless of the type parameter `C`
159        // 3. We avoid creating a reference to the full `ReportData` struct, which would
160        //    be UB since we don't know the correct type parameter
161        let children_ptr: *const Vec<RawReport> = unsafe {
162            // @add-unsafe-context: ReportData
163            // @add-unsafe-context: crate::util::Erased
164            &raw const (*ptr).children
165        };
166
167        // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
168        // because the existence of the `RawReportRef<'a>` already implies that
169        // we have readable access to the report for the 'a lifetime.
170        unsafe { &*children_ptr }
171    }
172
173    /// Returns the attachments of this report.
174    #[inline]
175    pub fn attachments(self) -> &'a Vec<RawAttachment> {
176        let ptr = self.as_ptr();
177
178        // SAFETY: The safety requirements for `&raw const (*ptr).attachments` are
179        // upheld:
180        // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
181        //    guaranteed by `RawReportRef`'s invariants
182        // 2. `ReportData<C>` is `#[repr(C)]`, so the `attachments` field is at a
183        //    consistent offset regardless of the type parameter `C`
184        // 3. We avoid creating a reference to the full `ReportData` struct, which would
185        //    be UB since we don't know the correct type parameter
186        let attachments_ptr: *const Vec<RawAttachment> = unsafe {
187            // @add-unsafe-context: ReportData
188            // @add-unsafe-context: crate::util::Erased
189            &raw const (*ptr).attachments
190        };
191
192        // SAFETY: We turn the `*const` pointer into a `&'a` reference. This is valid
193        // because the existence of the `RawReportRef<'a>` already implies that
194        // we have readable access to the report for the 'a lifetime.
195        unsafe { &*attachments_ptr }
196    }
197
198    /// Downcasts the context to the specified type and returns a reference.
199    ///
200    /// # Safety
201    ///
202    /// The caller must ensure:
203    ///
204    /// 1. The type `C` matches the actual context type stored in the
205    ///    [`ReportData`]
206    #[inline]
207    pub unsafe fn context_downcast_unchecked<C: 'static>(self) -> &'a C {
208        // SAFETY:
209        // 1. Guaranteed by the caller
210        let this = unsafe { self.cast_inner::<C>() };
211        &this.context
212    }
213}
214
215impl<'a> RawReportMut<'a> {
216    /// Gets a mutable reference to the child reports.
217    ///
218    /// # Safety
219    ///
220    /// The caller must ensure:
221    ///
222    /// 1. In case there are other references to the same report and they make
223    ///    assumptions about the report children being `Send+Sync`, then those
224    ///    assumptions must be upheld when modifying the children.
225    #[inline]
226    pub unsafe fn into_children_mut(self) -> &'a mut Vec<RawReport> {
227        let ptr = self.into_mut_ptr();
228
229        // SAFETY: The safety requirements for `&raw mut (*ptr).children` are upheld:
230        // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
231        //    guaranteed by `RawReportMut`'s invariants
232        // 2. `ReportData<C>` is `#[repr(C)]`, so the `children` field is at a
233        //    consistent offset regardless of the type parameter `C`
234        // 3. We avoid creating a reference to the full `ReportData` struct, which would
235        //    be UB since we don't know the correct type parameter
236        let children_ptr: *mut Vec<RawReport> = unsafe {
237            // @add-unsafe-context: ReportData
238            // @add-unsafe-context: crate::util::Erased
239            &raw mut (*ptr).children
240        };
241
242        // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
243        // because the existence of the `RawReportMut<'a>` already implied that
244        // nobody else has mutable access to the report for the 'a lifetime.
245        unsafe { &mut *children_ptr }
246    }
247
248    /// Deconstructs the `RawReportMut` and returns a mutable reference to the
249    /// attachments vector.
250    ///
251    /// # Safety
252    ///
253    /// The caller must ensure:
254    ///
255    /// 1. In case there are other references to the same report and they make
256    ///    assumptions about the report attachments being `Send+Sync`, then
257    ///    those assumptions must be upheld when modifying the attachments.
258    #[inline]
259    pub unsafe fn into_attachments_mut(self) -> &'a mut Vec<RawAttachment> {
260        let ptr = self.into_mut_ptr();
261
262        // SAFETY: The safety requirements for `&raw mut (*ptr).attachments` are upheld:
263        // 1. `ptr` is a valid pointer to a live `ReportData<C>` (for some `C`) as
264        //    guaranteed by `RawReportMut`'s invariants
265        // 2. `ReportData<C>` is `#[repr(C)]`, so the `attachments` field is at a
266        //    consistent offset regardless of the type parameter `C`
267        // 3. We avoid creating a reference to the full `ReportData` struct, which would
268        //    be UB since we don't know the correct type parameter
269        let attachments_ptr: *mut Vec<RawAttachment> = unsafe {
270            // @add-unsafe-context: ReportData
271            // @add-unsafe-context: crate::util::Erased
272            &raw mut (*ptr).attachments
273        };
274
275        // SAFETY: We turn the `*mut` pointer into a `&'a mut` reference. This is valid
276        // because the existence of the `RawReportMut<'a>` already implied that
277        // nobody else has mutable access to the report for the 'a lifetime.
278        unsafe { &mut *attachments_ptr }
279    }
280
281    /// Downcasts the context to the specified type and returns a mutable
282    /// reference.
283    ///
284    /// # Safety
285    ///
286    /// The caller must ensure:
287    ///
288    /// 1. The type `C` matches the actual context type stored in the
289    ///    [`ReportData`]
290    #[inline]
291    pub unsafe fn into_context_downcast_unchecked<C: 'static>(self) -> &'a mut C {
292        // SAFETY:
293        // 1. Guaranteed by the caller
294        let this = unsafe { self.cast_inner::<C>() };
295        &mut this.context
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_report_data_field_offsets() {
305        // Test that fields are accessible in the expected order for type-erased access
306        use core::mem::{offset_of, size_of};
307
308        fn check<T>() {
309            // Verify field order: vtable, children, attachments, context
310            assert_eq!(offset_of!(ReportData<T>, vtable), 0);
311            assert_eq!(
312                offset_of!(ReportData<T>, children),
313                size_of::<&'static ReportVtable>()
314            );
315            assert_eq!(
316                offset_of!(ReportData<T>, attachments),
317                size_of::<&'static ReportVtable>() + size_of::<Vec<RawAttachment>>()
318            );
319            assert!(
320                offset_of!(ReportData<T>, context)
321                    >= size_of::<&'static ReportVtable>()
322                        + size_of::<Vec<RawAttachment>>()
323                        + size_of::<Vec<RawReport>>()
324            );
325        }
326
327        #[repr(align(32))]
328        struct LargeAlignment {
329            _value: u8,
330        }
331
332        check::<u8>();
333        check::<i32>();
334        check::<[u64; 4]>();
335        check::<i32>();
336        check::<LargeAlignment>();
337    }
338}