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