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            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
130            // @add-unsafe-context: drop
131            (self.drop)(ptr);
132        }
133    }
134
135    /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
136    ///
137    /// # Safety
138    ///
139    /// The caller must ensure:
140    ///
141    /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
142    ///    a pointer via [`triomphe::Arc::into_raw`]
143    /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
144    ///    the [`ReportData`].
145    /// 3. All other references to this report are compatible with shared
146    ///    ownership. Specifically none of them assume that the strong_count is
147    ///    `1`.
148    #[inline]
149    pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
150        // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
151        // below. That function's safety requirements are upheld:
152        // 1. Guaranteed by the caller
153        // 2. Guaranteed by the caller
154        // 3. Guaranteed by the caller
155        unsafe {
156            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
157            // @add-unsafe-context: clone_arc
158            (self.clone_arc)(ptr)
159        }
160    }
161
162    /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
163    /// by this pointer.
164    ///
165    /// # Safety
166    ///
167    /// The caller must ensure:
168    ///
169    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
170    ///    the [`RawReportRef`].
171    #[inline]
172    pub(super) unsafe fn strong_count<'a>(&self, ptr: RawReportRef<'a>) -> usize {
173        // SAFETY: We know that `self.strong_count` points to the function
174        // `strong_count::<C>` below. That function's safety requirements are
175        // upheld:
176        // 1. Guaranteed by the caller
177        unsafe {
178            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
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            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
205            // @add-unsafe-context: source
206            (self.source)(ptr)
207        }
208    }
209
210    /// Formats the report using the [`H::display`] function
211    /// used when creating this [`ReportVtable`].
212    ///
213    /// [`H::display`]: ContextHandler::display
214    ///
215    /// # Safety
216    ///
217    /// The caller must ensure:
218    ///
219    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
220    ///    the [`RawReportRef`].
221    #[inline]
222    pub(super) unsafe fn display(
223        &self,
224        ptr: RawReportRef<'_>,
225        formatter: &mut core::fmt::Formatter<'_>,
226    ) -> core::fmt::Result {
227        // SAFETY: We know that `self.display` points to the function `display::<C, H>`
228        // below. That function's safety requirements are upheld:
229        // 1. Guaranteed by the caller
230        unsafe {
231            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
232            // @add-unsafe-context: display
233            (self.display)(ptr, formatter)
234        }
235    }
236
237    /// Formats the given `RawReportRef` using the [`H::debug`] function
238    /// used when creating this [`ReportVtable`].
239    ///
240    /// [`H::debug`]: ContextHandler::debug
241    ///
242    /// # Safety
243    ///
244    /// The caller must ensure:
245    ///
246    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
247    ///    the [`RawReportRef`].
248    #[inline]
249    pub(super) unsafe fn debug(
250        &self,
251        ptr: RawReportRef<'_>,
252        formatter: &mut core::fmt::Formatter<'_>,
253    ) -> core::fmt::Result {
254        // SAFETY: We know that `self.debug` points to the function `debug::<C, H>`
255        // below. That function's safety requirements are upheld:
256        // 1. Guaranteed by the caller
257        unsafe {
258            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
259            // @add-unsafe-context: debug
260            (self.debug)(ptr, formatter)
261        }
262    }
263
264    /// Calls the [`H::preferred_formatting_style`] function to get the
265    /// formatting style preferred by the context when formatted as part of
266    /// a report.
267    ///
268    /// [`H::preferred_formatting_style`]: ContextHandler::preferred_formatting_style
269    ///
270    /// # Safety
271    ///
272    /// The caller must ensure:
273    ///
274    /// 1. This [`ReportVtable`] must be a vtable for the context type stored in
275    ///    the [`RawReportRef`].
276    #[inline]
277    pub(super) unsafe fn preferred_context_formatting_style(
278        &self,
279        ptr: RawReportRef<'_>,
280        report_formatting_function: FormattingFunction,
281    ) -> ContextFormattingStyle {
282        // SAFETY: We know that `self.preferred_context_formatting_style` points to the
283        // function `preferred_context_formatting_style::<C, H>` below.
284        // That function's safety requirements are upheld:
285        // 1. Guaranteed by the caller
286        unsafe {
287            // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
288            // @add-unsafe-context: preferred_context_formatting_style
289            (self.preferred_context_formatting_style)(ptr, report_formatting_function)
290        }
291    }
292}
293
294/// Drops the [`triomphe::Arc<ReportData<C>>`] instance pointed to by this
295/// pointer.
296///
297/// # Safety
298///
299/// The caller must ensure:
300///
301/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
302///    [`triomphe::Arc::into_raw`]
303/// 2. The context type `C` matches the actual context type stored in the
304///    [`ReportData`]
305/// 3. This method drops the [`triomphe::Arc<ReportData<C>>`], so the caller
306///    must ensure that the pointer has not previously been dropped, that it is
307///    able to transfer ownership of the pointer, and that it will not use the
308///    pointer after calling this method.
309pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
310    let ptr: NonNull<ReportData<C>> = ptr.cast();
311    let ptr = ptr.as_ptr();
312    // SAFETY:
313    // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
314    //    by caller)
315    // 2. After `from_raw`, the pointer is consumed and not accessed again
316    let arc = unsafe {
317        // @add-unsafe-context: ReportData
318        triomphe::Arc::from_raw(ptr)
319    };
320    core::mem::drop(arc);
321}
322
323/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
324///
325/// # Safety
326///
327/// The caller must ensure:
328///
329/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
330///    pointer via [`triomphe::Arc::into_raw`]
331/// 2. The context type `C` matches the actual context type stored in the
332///    [`ReportData`]
333/// 3. All other references to this report are compatible with shared ownership.
334///    Specifically none of them assume that the strong_count is `1`.
335unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
336    let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
337
338    // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
339    // type (guaranteed by the caller), which fulfills the requirements for
340    // `ArcBorrow::from_ptr`.
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}