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(NonNull<ReportData<Erased>>) -> 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 [`RawReportRef`].
117 /// 3. After calling this method, the pointer is no longer used
118 #[inline]
119 pub(super) unsafe fn drop(&self, ptr: NonNull<ReportData<Erased>>) {
120 // SAFETY: We know that `self.drop` points to the function `drop::<C>` below.
121 // That function's safety requirements are upheld:
122 // 1. Guaranteed by the caller
123 // 2. Guaranteed by the caller
124 // 3. Guaranteed by the caller
125 unsafe {
126 // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
127 // @add-unsafe-context: drop
128 (self.drop)(ptr);
129 }
130 }
131
132 /// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
133 ///
134 /// # Safety
135 ///
136 /// The caller must ensure:
137 ///
138 /// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into
139 /// a pointer via [`triomphe::Arc::into_raw`]
140 /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
141 /// the [`RawReportRef`].
142 /// 3. All other references to this report are compatible with shared
143 /// ownership. Specifically none of them assume that the strong_count is
144 /// `1`.
145 #[inline]
146 pub(super) unsafe fn clone_arc(&self, ptr: NonNull<ReportData<Erased>>) -> RawReport {
147 // SAFETY: We know that `self.clone_arc` points to the function `clone_arc::<C>`
148 // below. That function's safety requirements are upheld:
149 // 1. Guaranteed by the caller
150 // 2. Guaranteed by the caller
151 // 3. Guaranteed by the caller
152 unsafe {
153 // See https://github.com/rootcause-rs/rootcause-unsafe-analysis for details
154 // @add-unsafe-context: clone_arc
155 (self.clone_arc)(ptr)
156 }
157 }
158
159 /// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to
160 /// by this pointer.
161 ///
162 /// # Safety
163 ///
164 /// The caller must ensure:
165 ///
166 /// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
167 /// [`triomphe::Arc::into_raw`]
168 /// 2. This [`ReportVtable`] must be a vtable for the context type stored in
169 /// the [`RawReportRef`].
170 #[inline]
171 pub(super) unsafe fn strong_count(&self, ptr: NonNull<ReportData<Erased>>) -> usize {
172 // SAFETY: We know that `self.strong_count` points to the function
173 // `strong_count::<C>` below. That function's safety requirements are
174 // upheld:
175 // 1. Guaranteed by the caller
176 // 2. 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. The pointer is not used after calling this method
306pub(super) unsafe fn drop<C: 'static>(ptr: NonNull<ReportData<Erased>>) {
307 let ptr: NonNull<ReportData<C>> = ptr.cast();
308 let ptr = ptr.as_ptr();
309 // SAFETY:
310 // 1. The pointer has the correct type and came from `Arc::into_raw` (guaranteed
311 // by caller)
312 // 2. After `from_raw`, the pointer is consumed and not accessed again
313 let arc = unsafe {
314 // @add-unsafe-context: ReportData
315 triomphe::Arc::from_raw(ptr)
316 };
317 core::mem::drop(arc);
318}
319
320/// Clones the [`triomphe::Arc<ReportData<C>>`] pointed to by this pointer.
321///
322/// # Safety
323///
324/// The caller must ensure:
325///
326/// 1. The pointer comes from a [`triomphe::Arc<ReportData<C>>`] turned into a
327/// pointer via [`triomphe::Arc::into_raw`]
328/// 2. The context type `C` matches the actual context type stored in the
329/// [`ReportData`]
330/// 3. All other references to this report are compatible with shared ownership.
331/// Specifically none of them assume that the strong_count is `1`.
332unsafe fn clone_arc<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> RawReport {
333 let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
334
335 // SAFETY: The pointer is valid and came from `Arc::into_raw` with the correct
336 // type (guaranteed by the caller), which fulfills the requirements for
337 // `ArcBorrow::from_ptr`.
338 let arc_borrow = unsafe {
339 // @add-unsafe-context: ReportData
340 triomphe::ArcBorrow::from_ptr(ptr)
341 };
342
343 let arc = arc_borrow.clone_arc();
344 RawReport::from_arc(arc)
345}
346
347/// Gets the strong count of the [`triomphe::Arc<ReportData<C>>`] pointed to by
348/// this pointer.
349///
350/// # Safety
351///
352/// The caller must ensure:
353///
354/// 1. The pointer comes from [`triomphe::Arc<ReportData<C>>`] via
355/// [`triomphe::Arc::into_raw`]
356/// 2. The context type `C` matches the actual context type stored in the
357/// [`ReportData`]
358unsafe fn strong_count<C: 'static>(ptr: NonNull<ReportData<Erased>>) -> usize {
359 let ptr: *const ReportData<C> = ptr.cast::<ReportData<C>>().as_ptr();
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}