rootcause_internals/report/
vtable.rs

1//! Vtable for type-erased report operations.
2//!
3//! This module contains the [`ReportVtable`] which enables calling handler
4//! methods on reports when their concrete context type `C` and handler type `H`
5//! have been erased. The vtable stores function pointers that dispatch to the
6//! correct typed implementations.
7//!
8//! This module encapsulates the fields of the [`ReportVtable`] so that they
9//! cannot be accessed directly without going through the proper methods which
10//! specifies which safety invariants are required to call them safely.
11
12use core::{any::TypeId, ptr::NonNull};
13
14use crate::{
15    handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction},
16    report::{
17        data::ReportData,
18        raw::{RawReport, RawReportRef},
19    },
20    util::Erased,
21};
22
23/// Vtable for type-erased report operations.
24///
25/// Contains function pointers for performing operations on reports without
26/// knowing their concrete type at compile time.
27pub(super) struct ReportVtable {
28    /// Gets the [`TypeId`] of the context type that was used to create this
29    /// [`ReportVtable`].
30    type_id: fn() -> TypeId,
31    /// Gets the [`TypeId`] of the handler that was used to create this
32    /// [`ReportVtable`].
33    handler_type_id: fn() -> TypeId,
34    /// Method to drop the [`triomphe::Arc<ReportData<C>>`] instance pointed to
35    /// by this pointer.
36    drop: unsafe fn(NonNull<ReportData<Erased>>),
37    /// Clones the `triomphe::Arc<ReportData<C>>` pointed to by this pointer.
38    clone_arc: unsafe fn(NonNull<ReportData<Erased>>) -> RawReport,
39    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
40    /// by this pointer.
41    strong_count: unsafe fn(NonNull<ReportData<Erased>>) -> usize,
42    /// Returns a reference to the source of the error using the `source` method
43    /// on the handler.
44    source: unsafe fn(RawReportRef<'_>) -> Option<&(dyn core::error::Error + 'static)>,
45    /// Formats the report using the `display` method on the handler.
46    display: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
47    /// Formats the report using the `debug method on the handler.
48    debug: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
49    /// Get the formatting style preferred by the context when formatted as part
50    /// of a report.
51    preferred_context_formatting_style:
52        unsafe fn(RawReportRef<'_>, FormattingFunction) -> ContextFormattingStyle,
53}
54
55impl ReportVtable {
56    /// Creates a new [`ReportVtable`] for the context type `C` and the handler
57    /// type `H`.
58    pub(super) const fn new<C: 'static, H: ContextHandler<C>>() -> &'static Self {
59        const {
60            &Self {
61                type_id: TypeId::of::<C>,
62                handler_type_id: TypeId::of::<H>,
63                drop: drop::<C>,
64                clone_arc: clone_arc::<C>,
65                strong_count: strong_count::<C>,
66                source: source::<C, H>,
67                display: display::<C, H>,
68                debug: debug::<C, H>,
69                preferred_context_formatting_style: preferred_context_formatting_style::<C, H>,
70            }
71        }
72    }
73
74    /// Gets the [`TypeId`] of the context type that was used to create this
75    /// [`ReportVtable`].
76    #[inline]
77    pub(super) fn type_id(&self) -> TypeId {
78        (self.type_id)()
79    }
80
81    /// Gets the [`TypeId`] of the handler that was used to create this
82    /// [`ReportVtable`].
83    #[inline]
84    pub(super) fn handler_type_id(&self) -> TypeId {
85        (self.handler_type_id)()
86    }
87
88    /// Drops the `triomphe::Arc<ReportData<C>>` instance pointed to by this
89    /// pointer.
90    ///
91    /// # Safety
92    ///
93    /// - The caller must ensure that the pointer comes from an
94    ///   [`triomphe::Arc<ReportData<C>>`], which was turned into a pointer
95    ///   using [`triomphe::Arc::into_raw`].
96    /// - The context type `C` stored in the [`ReportData`] must match the `C`
97    ///   used when creating this [`ReportVtable`].
98    /// - After calling this method, the pointer must no longer be used.
99    #[inline]
100    pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
101        // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
102        // That function has three requirements, all of which are guaranteed by our
103        // caller:
104        // - The pointer must come from `triomphe::Arc::into_raw`
105        // - The context type `C` must match the stored type
106        // - The pointer must not be used after calling
107        unsafe {
108            (self.drop)(ptr);
109        }
110    }
111
112    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
113    ///
114    /// # Safety
115    ///
116    /// - The caller must ensure that the pointer comes from an
117    ///   [`triomphe::Arc<ReportData<C>>`], which was turned into a pointer
118    ///   using [`triomphe::Arc::into_raw`].
119    /// - The context type `C` stored in the [`ReportData`] must match the `C`
120    ///   used when creating this [`ReportVtable`].
121    /// - There must be no external assumptions that there is a unique ownership
122    ///   of the [`triomphe::Arc`].
123    #[inline]
124    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
125        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
126        // below. That function has three requirements, all of which are
127        // guaranteed by our caller:
128        // - The pointer must come from `triomphe::Arc::into_raw`
129        // - The context type `C` must match the stored type
130        // - There must be no external assumptions about pointer uniqueness
131        unsafe { (self.clone_arc)(ptr) }
132    }
133
134    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
135    /// by this pointer.
136    ///
137    /// # Safety
138    ///
139    /// - The caller must ensure that the pointer comes from an
140    ///   [`triomphe::Arc<ReportData<C>>`], which was turned into a pointer
141    ///   using [`triomphe::Arc::into_raw`].
142    /// - The context type `C` stored in the [`ReportData`] must match the `C`
143    ///   used when creating this [`ReportVtable`].
144    #[inline]
145    pub(super) unsafe fn strong_count(&self, ptr: NonNull<ReportData<Erased>>) -> usize {
146        // SAFETY: We know that `self.strong_count` points to the function
147        // `strong_count::<C>` below. That function has three requirements, all
148        // of which are guaranteed by our caller:
149        // - The pointer must come from `triomphe::Arc::into_raw`
150        // - The context type `C` must match the stored type
151        unsafe { (self.strong_count)(ptr) }
152    }
153
154    /// Returns a reference to the source of the error using the [`H::source`]
155    /// function used when creating this [`ReportVtable`].
156    ///
157    /// # Safety
158    ///
159    /// The context type `C` used when creating this [`ReportVtable`] must match
160    /// the type of the `C` stored in the [`ReportData`] pointed to by the
161    /// [`RawReportRef`].
162    ///
163    /// [`H::source`]: ContextHandler::source
164    #[inline]
165    pub(super) unsafe fn source<'a>(
166        &self,
167        ptr: RawReportRef<'a>,
168    ) -> Option<&'a (dyn core::error::Error + 'static)> {
169        // SAFETY: We know that the `self.source` field points to the function
170        // `source::<C>` below. The safety requirement of that function is that
171        // the `C` matches the one stored in the `ReportData` pointed to by
172        // `ptr`. This is guaranteed by the caller of this method.
173        unsafe { (self.source)(ptr) }
174    }
175
176    /// Formats the report using the [`H::display`] function
177    /// used when creating this [`ReportVtable`].
178    ///
179    /// [`H::display`]: ContextHandler::display
180    ///
181    /// # Safety
182    ///
183    /// The context type `C` used when creating this [`ReportVtable`] must match
184    /// the type stored in the [`ReportData`].
185    #[inline]
186    pub(super) unsafe fn display(
187        &self,
188        ptr: RawReportRef<'_>,
189        formatter: &mut core::fmt::Formatter<'_>,
190    ) -> core::fmt::Result {
191        // SAFETY: We know that the `self.display` field points to the function
192        // `display::<C, H>` below. That function requires that the context type
193        // `C` matches the actual context type stored in the `ReportData`, which
194        // is guaranteed by our caller.
195        unsafe { (self.display)(ptr, formatter) }
196    }
197
198    /// Formats the given `RawReportRef` using the [`H::debug`] function
199    /// used when creating this [`ReportVtable`].
200    ///
201    /// [`H::debug`]: ContextHandler::debug
202    ///
203    /// # Safety
204    ///
205    /// The context type `C` used when creating this [`ReportVtable`] must match
206    /// the type stored in the [`ReportData`].
207    #[inline]
208    pub(super) unsafe fn debug(
209        &self,
210        ptr: RawReportRef<'_>,
211        formatter: &mut core::fmt::Formatter<'_>,
212    ) -> core::fmt::Result {
213        // SAFETY: We know that the `self.debug` field points to the function
214        // `debug::<C, H>` below. That function requires that the context type
215        // `C` matches the actual context type stored in the `ReportData`, which
216        // is guaranteed by our caller.
217        unsafe { (self.debug)(ptr, formatter) }
218    }
219
220    /// Calls the [`H::preferred_formatting_style`] function to get the
221    /// formatting style preferred by the context when formatted as part of
222    /// a report.
223    ///
224    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
225    ///
226    /// # Safety
227    ///
228    /// The context type `C` used when creating this [`ReportVtable`] must match
229    /// the type stored in the [`ReportData`].
230    #[inline]
231    pub(super) unsafe fn preferred_context_formatting_style(
232        &self,
233        ptr: RawReportRef<'_>,
234        report_formatting_function: FormattingFunction,
235    ) -> ContextFormattingStyle {
236        // SAFETY: We know that the `self.preferred_context_formatting_style` field
237        // points to the function `preferred_context_formatting_style::<C, H>` below.
238        // That function requires that the context type `C` matches the actual context
239        // type stored in the `ReportData`, which is guaranteed by our caller.
240        unsafe { (self.preferred_context_formatting_style)(ptr, report_formatting_function) }
241    }
242}
243
244/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
245/// pointer.
246///
247/// # Safety
248///
249/// - The caller must ensure that the pointer comes from an
250///   [`triomphe::Arc<ReportData<C>>`], which was turned into a pointer using
251///   [`triomphe::Arc::into_raw`].
252/// - The context type `C` must match the actual context type stored in the
253///   [`ReportData`].
254/// - After calling this method, the pointer must no longer be used.
255pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
256    let ptr: NonNull<ReportData<C>> = ptr.cast();
257    let ptr = ptr.as_ptr();
258    // SAFETY: Triomphe has two requirements:
259    // - The given pointer must be of the correct type and have come from a call to
260    //   `Arc::into_raw`.
261    // - After `from_raw`, the pointer must not be accessed.
262    //
263    // The first requirement is guaranteed by the fact that we created the pointer
264    // using `Arc::into_raw` and the caller guarantees that the type `C` matches
265    // the context type stored in the `ReportData`.
266    //
267    // The second requirement is guaranteed by the fact that there are no existing
268    // references to the same `ReportData` instance, as this method consumes the
269    // pointer.
270    let arc = unsafe { triomphe::Arc::from_raw(ptr) };
271    core::mem::drop(arc);
272}
273
274/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
275/// this pointer.
276///
277/// # Safety
278///
279/// - The caller must ensure that the pointer comes from an
280///   [`triomphe::Arc<ReportData<C>>`], which was turned into a pointer using
281///   [`triomphe::Arc::into_raw`].
282/// - The context type `C` stored in the [`ReportData`] must match the `C` used
283///   when creating this [`ReportVtable`].
284unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
285    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
286
287    // SAFETY: Our caller guarantees that we point to a `ReportData<C>` and that
288    // this pointer came from a `triomphe::Arc::into_raw`.
289    //
290    // This fulfills the safety docs for `ArcBorrow::from_ptr`, which explicitly
291    // mentions the `as_ptr` (which is called from `into_raw`) is safe.
292    let arc_borrow = unsafe { triomphe::ArcBorrow::from_ptr(ptr) };
293
294    let arc = arc_borrow.clone_arc();
295    RawReport::from_arc(arc)
296}
297
298/// Gets the source error from a report using its handler's source
299/// implementation.
300///
301/// # Safety
302///
303/// The caller must ensure that the type `C` matches the actual context type
304/// stored in the [`ReportData`].
305unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
306    ptr: RawReportRef<'a>,
307) -> Option<&'a (dyn core::error::Error + 'static)> {
308    // SAFETY: Our caller guarantees that the type `C` matches the actual context
309    // type stored in the `ReportData`.
310    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
311    H::source(context)
312}
313
314/// Formats a report using its handler's display implementation.
315///
316/// # Safety
317///
318/// The caller must ensure that the type `C` matches the actual context type
319/// stored in the [`ReportData`].
320unsafe fn display<C: 'static, H: ContextHandler<C>>(
321    ptr: RawReportRef<'_>,
322    formatter: &mut core::fmt::Formatter<'_>,
323) -> core::fmt::Result {
324    // SAFETY: Our caller guarantees that the type `C` matches the actual context
325    // type stored in the `ReportData`.
326    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
327    H::display(context, formatter)
328}
329
330/// Formats a report using its handler's debug implementation.
331///
332/// # Safety
333///
334/// The caller must ensure that the type `C` matches the actual context type
335/// stored in the [`ReportData`].
336unsafe fn debug<C: 'static, H: ContextHandler<C>>(
337    ptr: RawReportRef<'_>,
338    formatter: &mut core::fmt::Formatter<'_>,
339) -> core::fmt::Result {
340    // SAFETY: Our caller guarantees that the type `C` matches the actual context
341    // type stored in the `ReportData`.
342    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
343    H::debug(context, formatter)
344}
345
346/// Gets the preferred formatting style using the
347/// [`H::preferred_formatting_style`] function.
348///
349/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
350///
351/// # Safety
352///
353/// The caller must ensure that the type `A` matches the actual attachment type
354/// stored in the [`AttachmentData`].
355unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
356    ptr: RawReportRef<'_>,
357    report_formatting_function: FormattingFunction,
358) -> ContextFormattingStyle {
359    // SAFETY: Our caller guarantees that the type `C` matches the actual attachment
360    // type stored in the `AttachmentData`.
361    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
362    H::preferred_formatting_style(context, report_formatting_function)
363}
364
365/// Gets the preferred formatting style using the
366/// [`H::preferred_formatting_style`] function.
367///
368/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
369///
370/// # Safety
371///
372/// The caller must ensure that the type `A` matches the actual attachment type
373/// stored in the [`AttachmentData`].
374unsafe fn strong_count<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> usize {
375    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
376
377    // SAFETY: Our caller guarantees that we point to a `ReportData<C>` and that
378    // this pointer came from a `triomphe::Arc::into_raw`.
379    //
380    // This fulfills the safety docs for `ArcBorrow::from_ptr`, which explicitly
381    // mentions the `as_ptr` (which is called from `into_raw`) is safe.
382    let arc_borrow = unsafe { triomphe::ArcBorrow::from_ptr(ptr) };
383
384    triomphe::ArcBorrow::strong_count(&arc_borrow)
385}
386
387#[cfg(test)]
388mod tests {
389    use alloc::vec;
390    use core::{error::Error, fmt};
391
392    use super::*;
393    use crate::{handlers::ContextHandler, report::RawReport};
394
395    struct HandlerI32;
396    impl ContextHandler<i32> for HandlerI32 {
397        fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
398            None
399        }
400
401        fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
402            fmt::Display::fmt(value, formatter)
403        }
404
405        fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
406            fmt::Debug::fmt(value, formatter)
407        }
408    }
409
410    #[test]
411    fn test_report_vtable_eq() {
412        // Test that vtables have proper static lifetime and can be safely shared
413        let vtable1 = ReportVtable::new::<i32, HandlerI32>();
414        let vtable2 = ReportVtable::new::<i32, HandlerI32>();
415
416        // Both should be the exact same static instance
417        assert!(core::ptr::eq(vtable1, vtable2));
418    }
419
420    #[test]
421    fn test_report_type_id() {
422        let vtable = ReportVtable::new::<i32, HandlerI32>();
423        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
424    }
425
426    #[test]
427    fn test_report_clone_eq() {
428        let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
429
430        // SAFETY: There are no assumptions about single ownership
431        let cloned_report = unsafe { report.as_ref().clone_arc() };
432
433        // Both reports should be the same after
434        assert!(core::ptr::eq(
435            report.as_ref().as_ptr(),
436            cloned_report.as_ref().as_ptr()
437        ));
438    }
439}