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