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.
34///
35/// # Safety
36///
37/// The following safety invariants are guaranteed to be upheld as long as this
38/// struct exists:
39///
40/// * The fields `drop`, `clone_arc`, `strong_count`, `source`, `display`,
41///   `debug`, and `preferred_context_formatting_style` all point to the
42///   functions defined below
43/// * The concrete pointers are all instantiated with the same context type `C`
44///   and handler type `H` that were used to create this `ReportVtable`.
45pub(crate) struct ReportVtable {
46    /// Gets the [`TypeId`] of the context type that was used to create this
47    /// [`ReportVtable`].
48    type_id: fn() -> TypeId,
49    /// Gets the [`TypeId`] of the handler that was used to create this
50    /// [`ReportVtable`].
51    handler_type_id: fn() -> TypeId,
52    /// Method to drop the [`triomphe::Arc<ReportData<C>>`] instance pointed to
53    /// by this pointer.
54    drop: unsafe fn(NonNull<ReportData<Erased>>),
55    /// Clones the `triomphe::Arc<ReportData<C>>` pointed to by this pointer.
56    clone_arc: unsafe fn(NonNull<ReportData<Erased>>) -> RawReport,
57    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
58    /// by this pointer.
59    strong_count: unsafe fn(RawReportRef<'_>) -> usize,
60    /// Returns a reference to the source of the error using the `source` method
61    /// on the handler.
62    source: unsafe fn(RawReportRef<'_>) -> Option<&(dyn core::error::Error + 'static)>,
63    /// Formats the report using the `display` method on the handler.
64    display: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
65    /// Formats the report using the `debug` method on the handler.
66    debug: unsafe fn(RawReportRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
67    /// Get the formatting style preferred by the context when formatted as part
68    /// of a report.
69    preferred_context_formatting_style:
70        unsafe fn(RawReportRef<'_>, FormattingFunction) -> ContextFormattingStyle,
71}
72
73impl ReportVtable {
74    /// Creates a new [`ReportVtable`] for the context type `C` and the handler
75    /// type `H`.
76    pub(super) const fn new<C: 'static, H: ContextHandler<C>>() -> &'static Self {
77        const {
78            &Self {
79                type_id: TypeId::of::<C>,
80                handler_type_id: TypeId::of::<H>,
81                drop: drop::<C>,
82                clone_arc: clone_arc::<C>,
83                strong_count: strong_count::<C>,
84                source: source::<C, H>,
85                display: display::<C, H>,
86                debug: debug::<C, H>,
87                preferred_context_formatting_style: preferred_context_formatting_style::<C, H>,
88            }
89        }
90    }
91
92    /// Gets the [`TypeId`] of the context type that was used to create this
93    /// [`ReportVtable`].
94    #[inline]
95    pub(super) fn type_id(&self) -> TypeId {
96        (self.type_id)()
97    }
98
99    /// Gets the [`TypeId`] of the handler that was used to create this
100    /// [`ReportVtable`].
101    #[inline]
102    pub(super) fn handler_type_id(&self) -> TypeId {
103        (self.handler_type_id)()
104    }
105
106    /// Drops the `triomphe::Arc<ReportData<C>>` instance pointed to by this
107    /// pointer.
108    ///
109    /// # Safety
110    ///
111    /// The caller must ensure:
112    ///
113    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
114    ///    a pointer via [`triomphe::Arc::into_raw`]
115    /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
116    ///    the [`ReportData`].
117    /// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
118    ///    must ensure that the pointer has not previously been dropped, that it
119    ///    is able to transfer ownership of the pointer, and that it will not
120    ///    use the pointer after calling this method.
121    #[inline]
122    pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
123        // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
124        // That function's safety requirements are upheld:
125        // 1. Guaranteed by the caller
126        // 2. Guaranteed by the caller
127        // 3. Guaranteed by the caller
128        unsafe {
129            // @add-unsafe-context: drop
130            (self.drop)(ptr);
131        }
132    }
133
134    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
135    ///
136    /// # Safety
137    ///
138    /// The caller must ensure:
139    ///
140    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
141    ///    a pointer via [`triomphe::Arc::into_raw`]
142    /// 2. The pointer has full provenance over the `Arc` (i.e., it was not
143    ///    derived from a `&T` reference)
144    /// 3. This [`ReportVtable`] must be a vtable for the context type stored in
145    ///    the [`ReportData`].
146    /// 4. All other references to this report are compatible with shared
147    ///    ownership. Specifically none of them assume that the strong_count is
148    ///    `1`.
149    #[inline]
150    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
151        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
152        // below. That function's safety requirements are upheld:
153        // 1. Guaranteed by the caller
154        // 2. Guaranteed by the caller
155        // 3. Guaranteed by the caller
156        // 4. Guaranteed by the caller
157        unsafe {
158            // @add-unsafe-context: clone_arc
159            (self.clone_arc)(ptr)
160        }
161    }
162
163    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
164    /// by this pointer.
165    ///
166    /// # Safety
167    ///
168    /// The caller must ensure:
169    ///
170    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
171    ///    the [`RawReportRef`].
172    #[inline]
173    pub(super) unsafe fn strong_count<'a>(&self, ptr: RawReportRef<'a>) -> usize {
174        // SAFETY: We know that `self.strong_count` points to the function
175        // `strong_count::<C>` below. That function's safety requirements are
176        // upheld:
177        // 1. Guaranteed by the caller
178        unsafe {
179            // @add-unsafe-context: strong_count
180            (self.strong_count)(ptr)
181        }
182    }
183
184    /// Returns a reference to the source of the error using the [`H::source`]
185    /// function used when creating this [`ReportVtable`].
186    ///
187    /// # Safety
188    ///
189    /// The caller must ensure:
190    ///
191    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
192    ///    the [`RawReportRef`].
193    ///
194    /// [`H::source`]: ContextHandler::source
195    #[inline]
196    pub(super) unsafe fn source<'a>(
197        &self,
198        ptr: RawReportRef<'a>,
199    ) -> Option<&'a (dyn core::error::Error + 'static)> {
200        // SAFETY: We know that `self.source` points to the function `source::<C, H>`
201        // below. That function's safety requirements are upheld:
202        // 1. Guaranteed by the caller
203        unsafe {
204            // @add-unsafe-context: source
205            (self.source)(ptr)
206        }
207    }
208
209    /// Formats the report using the [`H::display`] function
210    /// used when creating this [`ReportVtable`].
211    ///
212    /// [`H::display`]: ContextHandler::display
213    ///
214    /// # Safety
215    ///
216    /// The caller must ensure:
217    ///
218    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
219    ///    the [`RawReportRef`].
220    #[inline]
221    pub(super) unsafe fn display(
222        &self,
223        ptr: RawReportRef<'_>,
224        formatter: &mut core::fmt::Formatter<'_>,
225    ) -> core::fmt::Result {
226        // SAFETY: We know that `self.display` points to the function `display::<C, H>`
227        // below. That function's safety requirements are upheld:
228        // 1. Guaranteed by the caller
229        unsafe {
230            // @add-unsafe-context: display
231            (self.display)(ptr, formatter)
232        }
233    }
234
235    /// Formats the given `RawReportRef` using the [`H::debug`] function
236    /// used when creating this [`ReportVtable`].
237    ///
238    /// [`H::debug`]: ContextHandler::debug
239    ///
240    /// # Safety
241    ///
242    /// The caller must ensure:
243    ///
244    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
245    ///    the [`RawReportRef`].
246    #[inline]
247    pub(super) unsafe fn debug(
248        &self,
249        ptr: RawReportRef<'_>,
250        formatter: &mut core::fmt::Formatter<'_>,
251    ) -> core::fmt::Result {
252        // SAFETY: We know that `self.debug` points to the function `debug::<C, H>`
253        // below. That function's safety requirements are upheld:
254        // 1. Guaranteed by the caller
255        unsafe {
256            // @add-unsafe-context: debug
257            (self.debug)(ptr, formatter)
258        }
259    }
260
261    /// Calls the [`H::preferred_formatting_style`] function to get the
262    /// formatting style preferred by the context when formatted as part of
263    /// a report.
264    ///
265    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
266    ///
267    /// # Safety
268    ///
269    /// The caller must ensure:
270    ///
271    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
272    ///    the [`RawReportRef`].
273    #[inline]
274    pub(super) unsafe fn preferred_context_formatting_style(
275        &self,
276        ptr: RawReportRef<'_>,
277        report_formatting_function: FormattingFunction,
278    ) -> ContextFormattingStyle {
279        // SAFETY: We know that `self.preferred_context_formatting_style` points to the
280        // function `preferred_context_formatting_style::<C, H>` below.
281        // That function's safety requirements are upheld:
282        // 1. Guaranteed by the caller
283        unsafe {
284            // @add-unsafe-context: preferred_context_formatting_style
285            (self.preferred_context_formatting_style)(ptr, report_formatting_function)
286        }
287    }
288}
289
290/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
291/// pointer.
292///
293/// # Safety
294///
295/// The caller must ensure:
296///
297/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
298///    [`triomphe::Arc::into_raw`]
299/// 2. The context type `C` matches the actual context type stored in the
300///    [`ReportData`]
301/// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
302///    must ensure that the pointer has not previously been dropped, that it is
303///    able to transfer ownership of the pointer, and that it will not use the
304///    pointer after calling this method.
305pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
306    let ptr: NonNull<ReportData<C>> = ptr.cast();
307    let ptr = ptr.as_ptr();
308    // SAFETY:
309    // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
310    //    by caller)
311    // 2. After `from_raw`, the pointer is consumed and not accessed again
312    let arc = unsafe {
313        // @add-unsafe-context: ReportData
314        triomphe::Arc::from_raw(ptr)
315    };
316    core::mem::drop(arc);
317}
318
319/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
320///
321/// # Safety
322///
323/// The caller must ensure:
324///
325/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
326///    pointer via [`triomphe::Arc::into_raw`]
327/// 2. The pointer has full provenance over the `Arc` (i.e., it was not derived
328///    from a `&T` reference)
329/// 3. The context type `C` matches the actual context type stored in the
330///    [`ReportData`]
331/// 4. All other references to this report are compatible with shared ownership.
332///    Specifically none of them assume that the strong_count is `1`.
333unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
334    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
335
336    // SAFETY:
337    // - The pointer is valid and came from `Arc::into_raw` with the correct type
338    //   (guaranteed by the caller)
339    // - The pointer has full provenance over the `Arc` (i.e., it was not derived
340    //   from a `&T` reference) (guaranteed by the caller)
341    let arc_borrow = unsafe {
342        // @add-unsafe-context: ReportData
343        triomphe::ArcBorrow::from_ptr(ptr)
344    };
345
346    let arc = arc_borrow.clone_arc();
347    RawReport::from_arc(arc)
348}
349
350/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
351/// this pointer.
352///
353/// # Safety
354///
355/// The caller must ensure:
356///
357/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
358unsafe fn strong_count<'a, C: 'static>(ptr: RawReportRef<'a>) -> usize {
359    let ptr: *const ReportData<C> = ptr.as_ptr().cast::<ReportData<C>>();
360
361    // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
362    // type (guaranteed by the caller), which fulfills the requirements for
363    // `ArcBorrow::from_ptr`.
364    let arc_borrow = unsafe {
365        // @add-unsafe-context: ReportData
366        triomphe::ArcBorrow::from_ptr(ptr)
367    };
368
369    triomphe::ArcBorrow::strong_count(&arc_borrow)
370}
371
372/// Gets the source error from a report using its handler's source
373/// implementation.
374///
375/// # Safety
376///
377/// The caller must ensure:
378///
379/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
380unsafe fn source<'a, C: 'static, H: ContextHandler<C>>(
381    ptr: RawReportRef<'a>,
382) -> Option<&'a (dyn core::error::Error + 'static)> {
383    // SAFETY:
384    // 1. Guaranteed by the caller
385    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
386    H::source(context)
387}
388
389/// Formats a report using its handler's display implementation.
390///
391/// # Safety
392///
393/// The caller must ensure:
394///
395/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
396unsafe fn display<C: 'static, H: ContextHandler<C>>(
397    ptr: RawReportRef<'_>,
398    formatter: &mut core::fmt::Formatter<'_>,
399) -> core::fmt::Result {
400    // SAFETY:
401    // 1. Guaranteed by the caller
402    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
403    H::display(context, formatter)
404}
405
406/// Formats a report using its handler's debug implementation.
407///
408/// # Safety
409///
410/// The caller must ensure:
411///
412/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
413unsafe fn debug<C: 'static, H: ContextHandler<C>>(
414    ptr: RawReportRef<'_>,
415    formatter: &mut core::fmt::Formatter<'_>,
416) -> core::fmt::Result {
417    // SAFETY:
418    // 1. Guaranteed by the caller
419    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
420    H::debug(context, formatter)
421}
422
423/// Gets the preferred formatting style using the
424/// [`H::preferred_formatting_style`] function.
425///
426/// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
427///
428/// # Safety
429///
430/// The caller must ensure:
431///
432/// 1. The type `C` matches the actual context type stored in the [`ReportData`]
433unsafe fn preferred_context_formatting_style<C: 'static, H: ContextHandler<C>>(
434    ptr: RawReportRef<'_>,
435    report_formatting_function: FormattingFunction,
436) -> ContextFormattingStyle {
437    // SAFETY:
438    // 1. Guaranteed by the caller
439    let context: &C = unsafe { ptr.context_downcast_unchecked::<C>() };
440    H::preferred_formatting_style(context, report_formatting_function)
441}
442
443#[cfg(test)]
444mod tests {
445    use alloc::vec;
446    use core::{error::Error, fmt};
447
448    use super::*;
449    use crate::{handlers::ContextHandler, report::RawReport};
450
451    struct HandlerI32;
452    impl ContextHandler<i32> for HandlerI32 {
453        fn source(_value: &i32) -> Option<&(dyn Error + 'static)> {
454            None
455        }
456
457        fn display(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
458            fmt::Display::fmt(value, formatter)
459        }
460
461        fn debug(value: &i32, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
462            fmt::Debug::fmt(value, formatter)
463        }
464    }
465
466    #[test]
467    fn test_report_vtable_eq() {
468        // Test that vtables have proper static lifetime and can be safely shared
469        let vtable1 = ReportVtable::new::<i32, HandlerI32>();
470        let vtable2 = ReportVtable::new::<i32, HandlerI32>();
471
472        // Both should be the exact same static instance
473        assert!(core::ptr::eq(vtable1, vtable2));
474    }
475
476    #[test]
477    fn test_report_type_id() {
478        let vtable = ReportVtable::new::<i32, HandlerI32>();
479        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
480    }
481
482    #[test]
483    fn test_report_clone_eq() {
484        let report = RawReport::new::<_, HandlerI32>(42, vec![], vec![]);
485
486        // SAFETY: There are no assumptions about single ownership
487        let cloned_report = unsafe { report.as_ref().clone_arc() };
488
489        // Both reports should point to the same underlying data
490        assert!(core::ptr::eq(
491            report.as_ref().as_ptr(),
492            cloned_report.as_ref().as_ptr()
493        ));
494    }
495}