Skip to main content

rootcause_internals/attachment/
vtable.rs

1//! Vtable for type-erased attachment operations.
2//!
3//! This module contains the [`AttachmentVtable`] which enables calling handler
4//! methods on attachments when their concrete attachment type `A` and handler
5//! type `H` have been erased. The vtable stores function pointers that dispatch
6//! to the correct typed implementations.
7//!
8//! This module encapsulates the fields of [`AttachmentVtable`] so they cannot
9//! be accessed directly. This visibility restriction guarantees the safety
10//! invariant: **the vtable's type parameters must match the actual attachment
11//! type and handler stored in the [`AttachmentData`]**.
12//!
13//! # Safety Invariant
14//!
15//! This invariant is maintained because vtables are created as `&'static`
16//! references via [`AttachmentVtable::new`], which pairs the function pointers
17//! with specific types `A` and `H` at compile time.
18
19use alloc::boxed::Box;
20use core::{
21    any::{self, TypeId},
22    ptr::NonNull,
23};
24
25use crate::{
26    attachment::{data::AttachmentData, raw::RawAttachmentRef},
27    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
28    util::Erased,
29};
30
31/// Vtable for type-erased attachment operations.
32///
33/// Contains function pointers for performing operations on attachments without
34/// knowing their concrete type at compile time.
35///
36/// # Safety Invariant
37///
38/// The fields `drop`, `display`, `debug`, and `preferred_formatting_style` are
39/// guaranteed to point to the functions defined below instantiated with the
40/// attachment type `A` and handler type `H` that were used to create this
41/// [`AttachmentVtable`].
42pub(crate) struct AttachmentVtable {
43    /// Gets the [`TypeId`] of the attachment type that was used to create this
44    /// [`AttachmentVtable`].
45    type_id: fn() -> TypeId,
46    /// Gets the [`any::type_name`] of the attachment type that was used to
47    /// create this [`AttachmentVtable`].
48    type_name: fn() -> &'static str,
49    /// Gets the [`TypeId`] of the handler that was used to create this
50    /// [`AttachmentVtable`].
51    handler_type_id: fn() -> TypeId,
52    /// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this
53    /// pointer.
54    drop: unsafe fn(NonNull<AttachmentData<Erased>>),
55    /// Formats the attachment using the `display` method on the handler.
56    display: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
57    /// Formats the attachment using the `debug` method on the handler.
58    debug: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
59    /// Get the formatting style preferred by the attachment when formatted as
60    /// part of a report.
61    preferred_formatting_style:
62        unsafe fn(RawAttachmentRef<'_>, FormattingFunction) -> AttachmentFormattingStyle,
63}
64
65impl AttachmentVtable {
66    /// Creates a new [`AttachmentVtable`] for the attachment type `A` and the
67    /// handler type `H`.
68    pub(super) const fn new<A: 'static, H: AttachmentHandler<A>>() -> &'static Self {
69        const {
70            &Self {
71                type_id: TypeId::of::<A>,
72                type_name: any::type_name::<A>,
73                handler_type_id: TypeId::of::<H>,
74                drop: drop::<A>,
75                display: display::<A, H>,
76                debug: debug::<A, H>,
77                preferred_formatting_style: preferred_formatting_style::<A, H>,
78            }
79        }
80    }
81
82    /// Gets the [`TypeId`] of the attachment type that was used to create this
83    /// [`AttachmentVtable`].
84    #[inline]
85    pub(super) fn type_id(&self) -> TypeId {
86        (self.type_id)()
87    }
88
89    /// Gets the [`any::type_name`] of the attachment type that was used to
90    /// create this [`AttachmentVtable`].
91    #[inline]
92    pub(super) fn type_name(&self) -> &'static str {
93        (self.type_name)()
94    }
95
96    /// Gets the [`TypeId`] of the handler that was used to create this
97    /// [`AttachmentVtable`].
98    #[inline]
99    pub(super) fn handler_type_id(&self) -> TypeId {
100        (self.handler_type_id)()
101    }
102
103    /// Drops the `Box<AttachmentData<A>>` instance pointed to by this pointer.
104    ///
105    /// # Safety
106    ///
107    /// The caller must ensure:
108    ///
109    /// 1. The pointer comes from [`Box<AttachmentData<A>>`] via
110    ///    [`Box::into_raw`]
111    /// 2. This [`AttachmentVtable`] must be a vtable for the attachment type
112    ///    stored in the [`AttachmentData`].
113    /// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
114    ///    ensure that the pointer has not previously been dropped, that it is
115    ///    able to transfer ownership of the pointer, and that it will not use
116    ///    the pointer after calling this method.
117    #[inline]
118    pub(super) unsafe fn drop(&self, ptr: NonNull<AttachmentData<Erased>>) {
119        // SAFETY: We know that `self.drop` points to the function `drop::<A>` below.
120        // That function's safety requirements are upheld:
121        // 1. Guaranteed by the caller
122        // 2. Guaranteed by the caller
123        // 3. Guaranteed by the caller
124        unsafe {
125            // @add-unsafe-context: drop
126            (self.drop)(ptr);
127        }
128    }
129
130    /// Formats the attachment using the [`H::display`] function
131    /// used when creating this [`AttachmentVtable`].
132    ///
133    /// [`H::display`]: AttachmentHandler::display
134    ///
135    /// # Safety
136    ///
137    /// The caller must ensure:
138    ///
139    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
140    ///    stored in the [`RawAttachmentRef`].
141    #[inline]
142    pub(super) unsafe fn display(
143        &self,
144        ptr: RawAttachmentRef<'_>,
145        formatter: &mut core::fmt::Formatter<'_>,
146    ) -> core::fmt::Result {
147        // SAFETY: We know that the `self.display` field points to the function
148        // `display::<A, H>` below. That function's safety requirements are upheld:
149        // 1. Guaranteed by the caller
150        unsafe {
151            // @add-unsafe-context: display
152            (self.display)(ptr, formatter)
153        }
154    }
155
156    /// Formats the attachment using the [`H::debug`] function
157    /// used when creating this [`AttachmentVtable`].
158    ///
159    /// [`H::debug`]: AttachmentHandler::debug
160    ///
161    /// # Safety
162    ///
163    /// The caller must ensure:
164    ///
165    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
166    ///    stored in the [`RawAttachmentRef`].
167    #[inline]
168    pub(super) unsafe fn debug(
169        &self,
170        ptr: RawAttachmentRef<'_>,
171        formatter: &mut core::fmt::Formatter<'_>,
172    ) -> core::fmt::Result {
173        // SAFETY: We know that the `self.debug` field points to the function
174        // `debug::<A, H>` below. That function's safety requirements are upheld:
175        // 1. Guaranteed by the caller
176        unsafe {
177            // @add-unsafe-context: debug
178            // @add-unsafe-context: RawAttachmentRef
179            // @add-unsafe-context: AttachmentData
180            (self.debug)(ptr, formatter)
181        }
182    }
183
184    /// Gets the preferred formatting style using the
185    /// [`H::preferred_formatting_style`] function used when creating this
186    /// [`AttachmentVtable`].
187    ///
188    /// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
189    ///
190    /// # Safety
191    ///
192    /// The caller must ensure:
193    ///
194    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
195    ///    stored in the [`RawAttachmentRef`].
196    #[inline]
197    pub(super) unsafe fn preferred_formatting_style(
198        &self,
199        ptr: RawAttachmentRef<'_>,
200        report_formatting_function: FormattingFunction,
201    ) -> AttachmentFormattingStyle {
202        // SAFETY: We know that the `self.preferred_formatting_style` field points to
203        // the function `preferred_formatting_style::<A, H>` below. That
204        // function's safety requirements are upheld:
205        // 1. Guaranteed by the caller
206        unsafe {
207            // @add-unsafe-context: preferred_formatting_style
208            // @add-unsafe-context: RawAttachmentRef
209            // @add-unsafe-context: AttachmentData
210            (self.preferred_formatting_style)(ptr, report_formatting_function)
211        }
212    }
213}
214
215/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
216///
217/// # Safety
218///
219/// The caller must ensure:
220///
221/// 1. The pointer comes from [`Box<AttachmentData<A>>`] via [`Box::into_raw`]
222/// 2. The attachment type `A` matches the actual attachment type stored in the
223///    [`AttachmentData`]
224/// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
225///    ensure that the pointer has not previously been dropped, that it is able
226///    to transfer ownership of the pointer, and that it will not use the
227///    pointer after calling this method.
228unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
229    let ptr: NonNull<AttachmentData<A>> = ptr.cast();
230    let ptr = ptr.as_ptr();
231    // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it
232    // came from a call to `Box::into_raw` as also guaranteed by our caller.
233    let boxed = unsafe {
234        // @add-unsafe-context: AttachmentData
235        Box::from_raw(ptr)
236    };
237    core::mem::drop(boxed);
238}
239
240/// Formats an attachment using its handler's display implementation.
241///
242/// # Safety
243///
244/// The caller must ensure:
245///
246/// 1. The type `A` matches the actual attachment type stored in the
247///    [`AttachmentData`]
248unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
249    ptr: RawAttachmentRef<'_>,
250    formatter: &mut core::fmt::Formatter<'_>,
251) -> core::fmt::Result {
252    // SAFETY:
253    // 1. Guaranteed by the caller
254    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
255    H::display(attachment, formatter)
256}
257
258/// Formats an attachment using its handler's debug implementation.
259///
260/// # Safety
261///
262/// The caller must ensure:
263///
264/// 1. The type `A` matches the actual attachment type stored in the
265///    [`AttachmentData`]
266unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
267    ptr: RawAttachmentRef<'_>,
268    formatter: &mut core::fmt::Formatter<'_>,
269) -> core::fmt::Result {
270    // SAFETY:
271    // 1. Guaranteed by the caller
272    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
273    H::debug(attachment, formatter)
274}
275
276/// Gets the preferred formatting style using the
277/// [`H::preferred_formatting_style`] function.
278///
279/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
280///
281/// # Safety
282///
283/// The caller must ensure:
284///
285/// 1. The type `A` matches the actual attachment type stored in the
286///    [`AttachmentData`]
287unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
288    ptr: RawAttachmentRef<'_>,
289    report_formatting_function: FormattingFunction,
290) -> AttachmentFormattingStyle {
291    // SAFETY:
292    // 1. Guaranteed by the caller
293    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
294    H::preferred_formatting_style(attachment, report_formatting_function)
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::handlers::AttachmentHandler;
301
302    struct HandlerI32;
303    impl AttachmentHandler<i32> for HandlerI32 {
304        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
305            core::fmt::Display::fmt(value, formatter)
306        }
307
308        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309            core::fmt::Debug::fmt(value, formatter)
310        }
311    }
312
313    #[test]
314    fn test_attachment_vtable_eq() {
315        // Test that vtables have proper static lifetime and can be safely shared
316        let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
317        let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
318
319        // Both should be the exact same static instance
320        assert!(core::ptr::eq(vtable1, vtable2));
321    }
322
323    #[test]
324    fn test_attachment_type_id() {
325        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
326        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
327    }
328
329    #[test]
330    fn test_attachment_type_name() {
331        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
332        assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
333    }
334}