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 Jan 3, 2024
1 parent fcb1842 commit 2439de6
Show file tree
Hide file tree
Showing 7 changed files with 1,239 additions and 884 deletions.
300 changes: 76 additions & 224 deletions src/lib.rs

Large diffs are not rendered by default.

53 changes: 22 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,86 @@ 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>)? $(: Maybe<$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>)? $(: Maybe<$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>)? $(: Maybe<$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>)? $(: Maybe<$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>)? $(Maybe<$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: Maybe<'_, 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() };
$is_bit_valid
}
};
(@method TryFromBytes ; |$candidate:ident: Ptr<$repr:ty>| $is_bit_valid:expr) => {
(@method TryFromBytes ; |$candidate:ident: Maybe<$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: Maybe<'_, 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(_: Maybe<'_, Self>) -> bool { true }
};
(@method $trait:ident) => {
#[allow(clippy::missing_inline_in_public_items)]
Expand Down
92 changes: 92 additions & 0 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2023 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.

//! Abstractions over raw pointers.

mod ptr;

pub use ptr::{invariant, Ptr};

use crate::{TryFromBytes, Unaligned};

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

// These methods are defined on the type alias, `Maybe`, so as to bring them to
// the forefront of the rendered rustdoc.
impl<'a, T, Alignment> Maybe<'a, T, Alignment>
where
T: 'a + ?Sized + TryFromBytes,
Alignment: invariant::Alignment,
{
/// Check that `Ptr`'s referent is validly initialized for `T`.
///
/// # Panics
///
/// This method will panic if
/// [`T::is_bit_valid`][TryFromBytes::is_bit_valid] panics.
#[inline]
pub(crate) fn check_valid(self) -> Option<MaybeAligned<'a, T, Alignment>> {
let candidate = self;
// This call may panic. If that happens, it doesn't cause any soundness
// issues, as we have not generated any invalid state which we need to
// fix before returning.
if T::is_bit_valid(candidate.forget_aligned()) {
// SAFETY: If `Self::is_bit_valid`, code may assume that `candidate`
// contains a bit-valid instance of `Self`.
let candidate = unsafe { candidate.assume_valid() };
Some(candidate)
} else {
None
}
}
}

/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
/// use in [`TryFromBytes::is_bit_valid`].
pub type MaybeAligned<'a, T, Alignment = invariant::AnyAlignment> =
Ptr<'a, T, (invariant::Shared, Alignment, invariant::Valid)>;

// These methods are defined on the type alias, `MaybeAligned`, so as to bring
// them to the forefront of the rendered rustdoc for that type alias.
impl<'a, T, Alignment> MaybeAligned<'a, T, Alignment>
where
T: 'a + ?Sized,
Alignment: invariant::Alignment,
{
/// 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.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 unaligned_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.assume_aligned() };
ptr.as_ref()
}
}
Loading

0 comments on commit 2439de6

Please sign in to comment.