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