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}