Skip to content
27 changes: 26 additions & 1 deletion rootcause-internals/src/attachment/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
//! reference to the full struct.

use crate::{
attachment::{raw::RawAttachmentRef, vtable::AttachmentVtable},
attachment::{
raw::{RawAttachmentMut, RawAttachmentRef},
vtable::AttachmentVtable,
},
handlers::AttachmentHandler,
};

Expand Down Expand Up @@ -112,6 +115,28 @@ impl<'a> RawAttachmentRef<'a> {
}
}

impl<'a> RawAttachmentMut<'a> {
/// Accesses the inner attachment of the [`AttachmentData`] instance as a
/// reference to the specified type.
///
/// # Safety
///
/// The caller must ensure:
///
/// 1. The type `A` matches the actual attachment type stored in the
/// [`AttachmentData`].
#[inline]
pub unsafe fn into_attachment_downcast_unchecked<A: 'static>(self) -> &'a mut A {
// SAFETY:
// 1. Guaranteed by the caller
let this = unsafe {
// @add-unsafe-context: AttachmentData
self.cast_inner::<A>()
};
&mut this.attachment
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion rootcause-internals/src/attachment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ pub(crate) mod data;
pub(crate) mod raw;
pub(crate) mod vtable;

pub use self::raw::{RawAttachment, RawAttachmentRef};
pub use self::raw::{RawAttachment, RawAttachmentMut, RawAttachmentRef};
174 changes: 168 additions & 6 deletions rootcause-internals/src/attachment/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,30 @@ impl RawAttachment {
NonNull::new_unchecked(ptr)
};

Self { ptr }
Self {
// SAFETY:
// 1. See above
// 2. N/A
// 3. N/A
ptr,
}
}

/// Returns a reference to the [`AttachmentData`] instance.
#[inline]
pub fn as_ref(&self) -> RawAttachmentRef<'_> {
RawAttachmentRef {
ptr: self.ptr,
_marker: core::marker::PhantomData,
}
// SAFETY:
//
// 1. Upheld by invariant `self`
unsafe { RawAttachmentRef::new(self.ptr) }
}

/// Returns a mutable reference to the [`AttachmentData`] instance.
#[inline]
pub fn as_mut(&mut self) -> RawAttachmentMut<'_> {
// SAFETY:
// 1. Upheld by invariant on `self`
unsafe { RawAttachmentMut::new(self.ptr) }
}
}

Expand Down Expand Up @@ -146,6 +160,22 @@ pub struct RawAttachmentRef<'a> {
}

impl<'a> RawAttachmentRef<'a> {
/// Creates a new [`RawAttachmentRef`] from a pointer
///
/// # Safety
///
/// The caller must ensure:
///
/// 1. That `ptr` has been created from a `Box<AttacnmentData<A>>`.
pub(super) unsafe fn new(ptr: NonNull<AttachmentData<Erased>>) -> Self {
RawAttachmentRef {
// SAFETY:
// 1. Guaranteed by caller
ptr,
_marker: core::marker::PhantomData,
}
}

/// Casts the [`RawAttachmentRef`] to an [`AttachmentData<A>`] reference.
///
/// # Safety
Expand Down Expand Up @@ -190,7 +220,7 @@ impl<'a> RawAttachmentRef<'a> {
self.vtable().type_name()
}

/// Returns the [`TypeId`] of the attachment.
/// Returns the [`TypeId`] of the attachment handler.
#[inline]
pub fn attachment_handler_type_id(self) -> TypeId {
self.vtable().handler_type_id()
Expand Down Expand Up @@ -254,6 +284,116 @@ impl<'a> RawAttachmentRef<'a> {
}
}

/// A mutable lifetime-bound pointer to an [`AttachmentData`] that is guaranteed
/// to be the sole mutable(?) pointer to an initialized instance of an
/// [`AttachmentData<A>`] for some specific `A`, though we do not know which
/// actual `A` it is.
///
/// We cannot use a [`&'a mut AttachmentData<A>`] directly, because that would
/// require us to know the actual type of the attachment, which we do not.
///
/// [`&'a mut AttachmentData<A>`]: AttachmentData
#[repr(transparent)]
pub struct RawAttachmentMut<'a> {
/// Pointer to the inner attachment data
///
/// # Safety
///
/// The following safety invariants are guaranteed to be upheld as long as
/// this struct exists:
///
/// 1. The pointer must have been created from a `Box<AttachmentData<A>>`
/// for some `A` using `Box::into_raw`.
/// 2. The pointer will point to the same `AttachmentData<A>` for the entire
/// lifetime of this object.
/// 3. This pointer represents exclusive mutable access to the
/// `AttachmentData`.
ptr: NonNull<AttachmentData<Erased>>,

/// Marker to tell the compiler that we should
/// behave the same as a `&'a mut AttachmentData<Erased>`
_marker: core::marker::PhantomData<&'a mut AttachmentData<Erased>>,
}

impl<'a> RawAttachmentMut<'a> {
/// Create a new [`RawAttachmentMut`] directly from a pointer
///
/// # Safety
///
/// The caller must ensure:
///
/// 1. `ptr` must have been created from a `Box<AttachmentData<A>>` for some
/// `A` using `Box::into_raw`.
/// 2. This pointer represents exclusive mutable access to the
/// `AttachmentData`.
pub(super) unsafe fn new(ptr: NonNull<AttachmentData<Erased>>) -> Self {
RawAttachmentMut {
// SAFETY:
// 1. Guaranteed by caller
// 2. N/A
// 3. Guaranteed by caller
ptr,
_marker: core::marker::PhantomData,
}
}

/// Casts the [`RawAttachmentMut`] to an [`AttachmentData<A>`] mutable
/// reference.
///
/// # Safety
///
/// The caller must ensure:
///
/// 1. The type `A` matches the actual attachment type stored in the
/// [`AttachmentData`].
#[inline]
pub(super) unsafe fn cast_inner<A>(self) -> &'a mut AttachmentData<A> {
// Debug assertion to catch type mismatches in case of bugs
debug_assert_eq!(self.as_ref().vtable().type_id(), TypeId::of::<A>());

let mut this = self.ptr.cast::<AttachmentData<A>>();
// SAFETY: Converting the NonNull pointer to a mutable reference is sound
// because:
// - The pointer is non-null, properly aligned, and dereferenceable (guaranteed
// by RawAttachmentMut's type invariants)
// - The pointee is properly initialized (RawAttachmentMut's doc comment
// guarantees it is the exclusive pointer to an initialized AttachmentData<A>
// for some A)
// - The type `A` matches the actual attachment type (guaranteed by caller)
// - Shared access is NOT allowed
// - The reference lifetime 'a is valid (tied to RawAttachmentMut<'a>'s
// lifetime)
unsafe { this.as_mut() }
}

/// Reborrows the mutable reference to the [`AttachmentData`] with a shorter
/// lifetime.
#[inline]
pub fn reborrow<'b>(&'b mut self) -> RawAttachmentMut<'b> {
// SAFETY:
// 1. Guaranteed by `self`
// 2. Guaranteed by mutable borrow of `self`
unsafe { RawAttachmentMut::new(self.ptr) }
}

/// Returns a reference to the [`AttachmentData`] instance.
#[inline]
pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> {
// SAFETY:
// 1. Guaranteed by `self`
unsafe { RawAttachmentRef::new(self.ptr) }
}

/// Consumes the mutable reference and returns an immutable one with the
/// same lifetime.
#[inline]
pub fn into_ref(self) -> RawAttachmentRef<'a> {
// SAFETY:
// 1. Guaranteed by `self`
unsafe { RawAttachmentRef::new(self.ptr) }
}
}

#[cfg(test)]
mod tests {
use alloc::string::String;
Expand Down Expand Up @@ -326,6 +466,27 @@ mod tests {
core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
core::mem::size_of::<Option<usize>>()
);

assert_eq!(
core::mem::size_of::<RawAttachmentMut<'_>>(),
core::mem::size_of::<usize>()
);
assert_eq!(
core::mem::size_of::<Option<RawAttachmentMut<'_>>>(),
core::mem::size_of::<usize>()
);
assert_eq!(
core::mem::size_of::<Result<(), RawAttachmentMut<'_>>>(),
core::mem::size_of::<usize>()
);
assert_eq!(
core::mem::size_of::<Result<String, RawAttachmentMut<'_>>>(),
core::mem::size_of::<String>()
);
assert_eq!(
core::mem::size_of::<Option<Option<RawAttachmentMut<'_>>>>(),
core::mem::size_of::<Option<usize>>()
);
}

#[test]
Expand Down Expand Up @@ -419,5 +580,6 @@ mod tests {
fn test_send_sync() {
static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync);
static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync);
static_assertions::assert_not_impl_any!(RawAttachmentMut<'_>: Send, Sync);
}
}
5 changes: 3 additions & 2 deletions rootcause-internals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
//!
//! - **[`attachment`]**: Type-erased attachment storage
//! - [`RawAttachment`]: Owned attachment with [`Box`]-based allocation
//! - [`RawAttachmentRef`]: Borrowed reference to an attachment
//! - [`RawAttachmentRef`]/[`RawAttachmentMut`]: Borrowed reference to an
//! attachment (shared/mutable)
//! - [`AttachmentData`]: `#[repr(C)]` wrapper enabling field access on erased
//! types
//! - [`AttachmentVtable`]: Function pointers for type-erased dispatch
Expand Down Expand Up @@ -87,5 +88,5 @@ pub mod handlers;
mod report;
mod util;

pub use attachment::{RawAttachment, RawAttachmentRef};
pub use attachment::{RawAttachment, RawAttachmentMut, RawAttachmentRef};
pub use report::{RawReport, RawReportMut, RawReportRef};
Loading
Loading