Skip to content

Commit

Permalink
Invariant-parameterize Ptr and make is_bit_valid safe
Browse files Browse the repository at this point in the history
Closes #715.
  • Loading branch information
jswrenn committed Dec 28, 2023
1 parent e56a42b commit c2f0b92
Show file tree
Hide file tree
Showing 7 changed files with 1,223 additions and 884 deletions.
304 changes: 80 additions & 224 deletions src/lib.rs

Large diffs are not rendered by default.

55 changes: 24 additions & 31 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,14 @@ macro_rules! safety_comment {
/// $ty>` which satisfies the preconditions of
/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that the
/// memory referenced by that `Ptr` always contains a valid `$repr`.
/// - The alignment of `$repr` is less than or equal to the alignment of
/// `$ty`.
/// - The impl of `is_bit_valid` must only return `true` for its argument
/// `Ptr<$repr>` if the original `Ptr<$ty>` refers to a valid `$ty`.
macro_rules! unsafe_impl {
// Implement `$trait` for `$ty` with no bounds.
($(#[$attr:meta])* $ty:ty: $trait:ident $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => {
($(#[$attr:meta])* $ty:ty: $trait:ident $(; |$candidate:ident: MaybeAligned<$repr:ty>| $is_bit_valid:expr)?) => {
$(#[$attr])*
unsafe impl $trait for $ty {
unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?);
unsafe_impl!(@method $trait $(; |$candidate: MaybeAligned<$repr>| $is_bit_valid)?);
}
};
// Implement all `$traits` for `$ty` with no bounds.
Expand Down Expand Up @@ -97,93 +95,88 @@ macro_rules! unsafe_impl {
$(#[$attr:meta])*
const $constname:ident : $constty:ident $(,)?
$($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),*
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: MaybeValid<$ptr_repr:ty>)?| $is_bit_valid:expr)?
) => {
unsafe_impl!(
@inner
$(#[$attr])*
@const $constname: $constty,
$($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)*
=> $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: Ptr<$ptr_repr>)?| $is_bit_valid)?
=> $trait for $ty $(; |$candidate $(: MaybeAligned<$ref_repr>)? $(: MaybeValid<$ptr_repr>)?| $is_bit_valid)?
);
};
(
$(#[$attr:meta])*
$($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),*
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: MaybeValid<$ptr_repr:ty>)?| $is_bit_valid:expr)?
) => {
unsafe_impl!(
@inner
$(#[$attr])*
$($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)*
=> $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: Ptr<$ptr_repr>)?| $is_bit_valid)?
=> $trait for $ty $(; |$candidate $(: MaybeAligned<$ref_repr>)? $(: MaybeValid<$ptr_repr>)?| $is_bit_valid)?
);
};
(
@inner
$(#[$attr:meta])*
$(@const $constname:ident : $constty:ident,)*
$($tyvar:ident $(: $(? $optbound:ident +)* + $($bound:ident +)* )?,)*
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: MaybeValid<$ptr_repr:ty>)?| $is_bit_valid:expr)?
) => {
$(#[$attr])*
unsafe impl<$(const $constname: $constty,)* $($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> $trait for $ty {
unsafe_impl!(@method $trait $(; |$candidate: $(&$ref_repr)? $(Ptr<$ptr_repr>)?| $is_bit_valid)?);
unsafe_impl!(@method $trait $(; |$candidate: $(MaybeAligned<$ref_repr>)? $(MaybeValid<$ptr_repr>)?| $is_bit_valid)?);
}
};

(@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => {
(@method TryFromBytes ; |$candidate:ident: MaybeAligned<$repr:ty>| $is_bit_valid:expr) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline]
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
fn is_bit_valid(candidate: MaybeValid<'_, Self>) -> bool {
// SAFETY:
// - The argument to `cast_unsized` is `|p| p as *mut _` as required
// by that method's safety precondition.
// - The caller has promised that the cast results in an object of
// equal or lesser size.
// - The caller has promised that `$repr`'s alignment is less than
// or equal to `Self`'s alignment.
#[allow(clippy::as_conversions)]
let candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) };
// SAFETY:
// - The caller has promised that the referenced memory region will
// contain a valid `$repr` for `'a`.
// - The memory may not be referenced by any mutable references.
// This is a precondition of `is_bit_valid`.
// - The memory may not be mutated even via `UnsafeCell`s. This is a
// precondition of `is_bit_valid`.
// - There must not exist any references to the same memory region
// which contain `UnsafeCell`s at byte ranges which are not
// identical to the byte ranges at which `T` contains
// `UnsafeCell`s. This is a precondition of `is_bit_valid`.
let $candidate: &$repr = unsafe { candidate.as_ref() };

// SAFETY: The caller has promised that the referenced memory region
// will contain a valid `$repr`.
let candidate = unsafe { candidate.assume_valid() };

let $candidate = MaybeAligned::from_ptr(candidate);
$is_bit_valid
}
};
(@method TryFromBytes ; |$candidate:ident: Ptr<$repr:ty>| $is_bit_valid:expr) => {
(@method TryFromBytes ; |$candidate:ident: MaybeValid<$repr:ty>| $is_bit_valid:expr) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline]
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
fn is_bit_valid(candidate: MaybeValid<'_, Self>) -> bool {
// SAFETY:
// - The argument to `cast_unsized` is `|p| p as *mut _` as required
// by that method's safety precondition.
// - The caller has promised that the cast results in an object of
// equal or lesser size.
// - The caller has promised that `$repr`'s alignment is less than
// or equal to `Self`'s alignment.
#[allow(clippy::as_conversions)]
let $candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) };

// SAFETY: The caller has promised that `$repr` is as-initialized as
// `Self`.
let $candidate = unsafe { $candidate.assume_as_initialized() };

$is_bit_valid
}
};
(@method TryFromBytes) => {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}
#[inline(always)] unsafe fn is_bit_valid(_: Ptr<'_, Self>) -> bool { true }
#[inline(always)] fn is_bit_valid(_: MaybeValid<'_, Self>) -> bool { true }
};
(@method $trait:ident) => {
#[allow(clippy::missing_inline_in_public_items)]
Expand Down
78 changes: 78 additions & 0 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Abstractions over raw pointers.

mod ptr;

pub use maybe_aligned::MaybeAligned;
pub use ptr::{invariant, Ptr};

/// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument
/// to [`TryFromBytes::is_bit_valid`][crate::TryFromBytes::is_bit_valid].
pub type MaybeValid<'a, T> =
Ptr<'a, T, (invariant::Shared, invariant::AnyAlignment, invariant::AsInitialized)>;

/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
/// use in [`TryFromBytes::is_bit_valid`][crate::TryFromBytes::is_bit_valid].
mod maybe_aligned {
use super::{ptr::invariant, Ptr};
use crate::Unaligned;
use core::fmt::{Debug, Formatter};

/// A reference to a validly-initialized `T` of lifetime `'a` that might
/// *not* be validly aligned for `T`.
#[doc(hidden)]
pub struct MaybeAligned<'a, T: ?Sized> {
ptr: Ptr<'a, T, (invariant::Shared, invariant::AnyAlignment, invariant::Valid)>,
}

impl<'a, T> MaybeAligned<'a, T>
where
T: 'a + ?Sized,
{
/// Constructs a `MaybeAligned` from a [`Ptr`].
///
/// The `Ptr`'s referent must be validly-initialized for `T`.
#[doc(hidden)]
#[inline]
pub const fn from_ptr(
ptr: Ptr<'a, T, (invariant::Shared, invariant::AnyAlignment, invariant::Valid)>,
) -> Self {
Self { ptr }
}

/// Reads the value from `MaybeAligned`.
///
/// This is only available if `T` is [`Copy`].
#[inline]
pub fn read_unaligned(self) -> T
where
T: Copy,
{
let raw = self.ptr.as_non_null().as_ptr();
// SAFETY: By invariant on `MaybeAligned`, `raw` contains
// validly-initialized data for `T`. The value is safe to read and
// return, because `T` is copy.
unsafe { core::ptr::read_unaligned(raw) }
}

/// Views the value as an aligned reference.
///
/// This is only available if `T` is [`Unaligned`].
#[inline]
pub fn as_ref(self) -> &'a T
where
T: Unaligned,
{
// SAFETY: The alignment of `T` is 1 and thus is always aligned
// because `T: Unaligned`.
let ptr = unsafe { self.ptr.assume_aligned() };
ptr.as_ref()
}
}

impl<'a, T: 'a + ?Sized> Debug for MaybeAligned<'a, T> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.ptr.fmt(f)
}
}
}
Loading

0 comments on commit c2f0b92

Please sign in to comment.