summaryrefslogtreecommitdiff
path: root/rust/zerocopy/src/split_at.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/zerocopy/src/split_at.rs')
-rw-r--r--rust/zerocopy/src/split_at.rs718
1 files changed, 551 insertions, 167 deletions
diff --git a/rust/zerocopy/src/split_at.rs b/rust/zerocopy/src/split_at.rs
index d7778425a31d..4aeb602f69e0 100644
--- a/rust/zerocopy/src/split_at.rs
+++ b/rust/zerocopy/src/split_at.rs
@@ -10,13 +10,19 @@
// those terms.
use super::*;
-use crate::pointer::invariant::{Aligned, Exclusive, Invariants, Shared, Valid};
+use crate::pointer::invariant::{Aligned, Exclusive, Invariants, Safe, Shared};
/// Types that can be split in two.
///
/// This trait generalizes Rust's existing support for splitting slices to
/// support slices and slice-based dynamically-sized types ("slice DSTs").
///
+/// For a `repr(C, packed)` or `repr(C, packed(N))` struct, the derive wraps
+/// the trailing field's [`Elem`](Self::Elem) in [`Unalign`]. The returned
+/// slice can therefore be accessed even when packing misaligns the original
+/// elements. Each packed struct in the trailing-field chain adds one wrapper;
+/// unpacked structs and [`ManuallyDrop`] forward their trailing type's `Elem`.
+///
/// # Implementation
///
/// **Do not implement this trait yourself!** Instead, use
@@ -38,6 +44,28 @@ use crate::pointer::invariant::{Aligned, Exclusive, Invariants, Shared, Valid};
/// This derive performs a sophisticated, compile-time safety analysis to
/// determine whether a type is `SplitAt`.
///
+/// A packed struct exposes its trailing elements through `Unalign`:
+///
+/// ```
+/// use zerocopy::{FromBytes, SplitAt, Unalign};
+/// # use zerocopy_derive::*;
+///
+/// #[derive(FromBytes, IntoBytes, KnownLayout, SplitAt)]
+/// #[repr(C, packed)]
+/// struct Packet {
+/// tag: u8,
+/// words: [u16],
+/// }
+///
+/// let mut bytes = [0u8; 5];
+/// let packet = Packet::mut_from_bytes(&mut bytes[..]).unwrap();
+/// let (left, right): (&mut Packet, &mut [Unalign<u16>]) =
+/// packet.split_at_mut(1).unwrap().via_into_bytes();
+/// left.tag = 1;
+/// right[0] = Unalign::new(0x0202);
+/// assert_eq!(bytes, [1, 0, 0, 2, 2]);
+/// ```
+///
/// # Safety
///
/// This trait does not convey any safety guarantees to code outside this crate.
@@ -58,10 +86,20 @@ use crate::pointer::invariant::{Aligned, Exclusive, Invariants, Shared, Valid};
)]
// # Safety
//
-// The trailing slice is well-aligned for its element type. `Self` is `[T]`, or
-// a `repr(C)` or `repr(transparent)` slice DST.
+// `Self` is a slice, a `repr(C)` (possibly packed) or `repr(transparent)` slice
+// DST, or `ManuallyDrop<T>` for some `T: SplitAt`.
+//
+// `Self::Elem` has the same size, bit validity, and `UnsafeCell` coverage as
+// the actual trailing element type. Access through either representation
+// preserves the other's validity, including writes through mutable references
+// and interior mutation through shared references. For every aligned `Self`,
+// its trailing slice's address is aligned for `Self::Elem`.
pub unsafe trait SplitAt: KnownLayout<PointerMetadata = usize> {
- /// The element type of the trailing slice.
+ /// The element type exposed by the split's returned slice.
+ ///
+ /// For `[T]`, this is `T`. For a struct, this is the trailing field's `Elem`,
+ /// wrapped in [`Unalign`] if the struct is packed. Nested packed structs
+ /// produce nested `Unalign` wrappers.
type Elem;
#[doc(hidden)]
@@ -188,10 +226,7 @@ pub unsafe trait SplitAt: KnownLayout<PointerMetadata = usize> {
/// Attempts to split `self` in two.
///
/// Returns `None` if `l_len` is greater than the length of `self`'s
- /// trailing slice, or if the given `l_len` would result in [the trailing
- /// padding](KnownLayout#slice-dst-layout) of the left portion overlapping
- /// the right portion.
- ///
+ /// trailing slice.
///
/// # Examples
///
@@ -249,7 +284,8 @@ pub unsafe trait SplitAt: KnownLayout<PointerMetadata = usize> {
}
}
-// SAFETY: `[T]`'s trailing slice is `[T]`, which is trivially aligned.
+// SAFETY: `[T]` exposes its own element type, preserving size, validity, and
+// `UnsafeCell` coverage. An aligned slice is aligned for its elements.
unsafe impl<T> SplitAt for [T] {
type Elem = T;
@@ -262,6 +298,29 @@ unsafe impl<T> SplitAt for [T] {
}
}
+// SAFETY: `ManuallyDrop<T>` has the same layout and bit validity as `T` [1].
+// Its `KnownLayout` implementation preserves `T`'s metadata and trailing-slice
+// layout. Forwarding `T::Elem` therefore preserves its size, validity, and
+// alignment guarantees. Its `UnsafeCell` coverage also matches `T`'s, as
+// established for `ManuallyDrop` in `impls.rs`; accesses through the forwarded
+// element representation preserve validity in both directions.
+//
+// [1] Per https://doc.rust-lang.org/1.93.0/std/mem/struct.ManuallyDrop.html:
+//
+// `ManuallyDrop<T>` is guaranteed to have the same layout and bit validity as
+// `T`
+unsafe impl<T: ?Sized + SplitAt> SplitAt for ManuallyDrop<T> {
+ type Elem = T::Elem;
+
+ #[inline]
+ #[allow(dead_code)]
+ fn only_derive_is_allowed_to_implement_this_trait()
+ where
+ Self: Sized,
+ {
+ }
+}
+
/// A `T` that has been split into two possibly-overlapping parts.
///
/// For some dynamically sized types, the padding that appears after the
@@ -270,10 +329,11 @@ unsafe impl<T> SplitAt for [T] {
/// requires trailing padding, the trailing padding of the left part of the
/// split `T` will overlap the right part. If `T` is a mutable reference or
/// permits interior mutation, you must ensure that the left and right parts do
-/// not overlap. You can do this at zero-cost using using
-/// [`Self::via_immutable`], [`Self::via_into_bytes`], or
-/// [`Self::via_unaligned`], or with a dynamic check by using
-/// [`Self::via_runtime_check`].
+/// not overlap. Use [`Self::via_into_bytes`] or
+/// [`Self::via_no_dynamic_padding`] to establish this without a runtime check,
+/// or [`Self::via_runtime_check`] to check a particular split. Shared references
+/// to an [`Immutable`] type may overlap; use [`Self::via_immutable`] in that
+/// case.
#[derive(Debug)]
pub struct Split<T> {
/// A pointer to the source slice DST.
@@ -304,7 +364,7 @@ where
T: ?Sized + SplitAt,
{
#[inline(always)]
- fn into_ptr(self) -> Split<Ptr<'a, T, (Shared, Aligned, Valid)>> {
+ fn into_ptr(self) -> Split<Ptr<'a, T, (Shared, Aligned, Safe)>> {
let source = Ptr::from_ref(self.source);
// SAFETY: `Ptr::from_ref(self.source)` points to exactly `self.source`
// and thus maintains the invariants of `self` with respect to `l_len`.
@@ -423,59 +483,100 @@ where
(l.as_ref(), r.as_ref())
}
- /// Produces the split parts of `self`, using [`Unaligned`] to ensure that
- /// it is sound to have concurrent references to both parts.
+ /// Produces the split parts of `self` using [`Self::via_no_dynamic_padding`].
+ #[deprecated(note = "use `Split::via_no_dynamic_padding` instead")]
+ #[doc(hidden)]
+ #[must_use = "has no side effects"]
+ #[inline(always)]
+ pub fn via_unaligned(self) -> (&'a T, &'a [T::Elem])
+ where
+ T: Unaligned,
+ {
+ self.via_no_dynamic_padding()
+ }
+
+ /// Produces the split parts of `self` using a compile-time assertion to
+ /// ensure that it is unconditionally sound to have concurrent references to
+ /// both parts.
+ ///
+ /// When possible, you should prefer using [`Self::via_into_bytes`], which
+ /// ensures this safety property through regular trait bounds.
///
/// # Examples
///
+ /// In the below example, a single byte of padding exists (on most
+ /// platforms) between `Packet<[u16]>`'s `length` and `body` fields,
+ /// precluding the use of [`Self::via_into_bytes`].
+ ///
/// ```
- /// use zerocopy::{SplitAt, FromBytes};
+ /// use zerocopy::SplitAt;
/// # use zerocopy_derive::*;
///
- /// #[derive(SplitAt, FromBytes, KnownLayout, Immutable, Unaligned)]
+ /// #[derive(SplitAt, KnownLayout)]
/// #[repr(C)]
- /// struct Packet {
+ /// struct Packet<B: ?Sized> {
/// length: u8,
- /// body: [u8],
+ /// body: B,
/// }
///
- /// // These bytes encode a `Packet`.
- /// let bytes = &[4, 1, 2, 3, 4, 5, 6, 7, 8, 9][..];
- ///
- /// let packet = Packet::ref_from_bytes(bytes).unwrap();
+ /// let packet: &Packet<[u16]> = &Packet { length: 4, body: [1u16, 2, 3, 4, 5, 6, 7, 8, 9] };
///
/// assert_eq!(packet.length, 4);
/// assert_eq!(packet.body, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
///
- /// // Attempt to split `packet` at `length`.
- /// let split = packet.split_at(packet.length as usize).unwrap();
+ /// if let Some(split) = packet.split_at(packet.length as usize) {
+ /// // Every body length requires no trailing padding.
+ /// let (packet, rest) = split.via_no_dynamic_padding();
+ /// assert_eq!(packet.length, 4);
+ /// assert_eq!(packet.body, [1, 2, 3, 4]);
+ /// assert_eq!(rest, [5, 6, 7, 8, 9]);
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
+ /// }
+ /// ```
///
- /// // Use the `Unaligned` bound on `Packet` to prove that it's okay to
- /// // return concurrent references to `packet` and `rest`.
- /// let (packet, rest) = split.via_unaligned();
+ /// # Compile-Time Assertions
///
- /// assert_eq!(packet.length, 4);
- /// assert_eq!(packet.body, [1, 2, 3, 4]);
- /// assert_eq!(rest, [5, 6, 7, 8, 9]);
+ /// This method rejects at compile-time any type that admits dynamic
+ /// trailing padding. In the below example, some `split_at` indices would
+ /// produce left parts whose trailing padding overlaps with the right part:
+ ///
+ /// ```compile_fail,E0080
+ /// use zerocopy::SplitAt;
+ /// # use zerocopy_derive::*;
+ ///
+ /// #[derive(SplitAt, KnownLayout)]
+ /// #[repr(C, align(2))]
+ /// struct Packet<B: ?Sized> {
+ /// length: u16,
+ /// body: B,
+ /// }
+ ///
+ /// let packet: &Packet<[u8]> = &Packet { length: 4, body: [1u8, 2, 3, 4, 5, 6, 7, 8, 9] };
+ ///
+ /// if let Some(split) = packet.split_at(packet.length as usize) {
+ /// let _ = split.via_no_dynamic_padding(); // ⚠ Compile Error!
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
+ /// }
/// ```
///
- #[doc = codegen_header!("h5", "split_via_unaligned")]
+ /// If you need to split such types, use [`Self::via_runtime_check`].
+ ///
+ #[doc = codegen_header!("h5", "split_via_no_dynamic_padding")]
///
/// See [`Split::via_immutable`](#method.split_via_immutable.codegen).
#[must_use = "has no side effects"]
#[inline(always)]
- pub fn via_unaligned(self) -> (&'a T, &'a [T::Elem])
- where
- T: Unaligned,
- {
- let (l, r) = self.into_ptr().via_unaligned();
+ pub fn via_no_dynamic_padding(self) -> (&'a T, &'a [T::Elem]) {
+ let (l, r) = self.into_ptr().via_no_dynamic_padding();
(l.as_ref(), r.as_ref())
}
/// Produces the split parts of `self`, using a dynamic check to ensure that
/// it is sound to have concurrent references to both parts. You should
/// prefer using [`Self::via_immutable`], [`Self::via_into_bytes`], or
- /// [`Self::via_unaligned`], which have no runtime cost.
+ /// [`Self::via_no_dynamic_padding`], which perform no runtime check.
///
/// Note that this check is overly conservative if `T` is [`Immutable`]; for
/// some types, this check will reject some splits which
@@ -484,52 +585,40 @@ where
/// # Examples
///
/// ```
- /// use zerocopy::{SplitAt, FromBytes, IntoBytes, network_endian::U16};
+ /// use zerocopy::SplitAt;
/// # use zerocopy_derive::*;
///
- /// #[derive(SplitAt, FromBytes, KnownLayout, Immutable, Debug)]
+ /// #[derive(SplitAt, KnownLayout)]
/// #[repr(C, align(2))]
- /// struct Packet {
- /// length: U16,
- /// body: [u8],
+ /// struct Packet<B: ?Sized> {
+ /// length: u16,
+ /// body: B,
/// }
///
- /// // These bytes encode a `Packet`.
- /// let bytes = [
- /// 4u16.to_be(),
- /// 1u16.to_be(),
- /// 2u16.to_be(),
- /// 3u16.to_be(),
- /// 4u16.to_be()
- /// ];
- ///
- /// let packet = Packet::ref_from_bytes(bytes.as_bytes()).unwrap();
+ /// let packet: &Packet<[u8]> = &Packet { length: 4, body: [1u8, 2, 3, 4, 5, 6, 7, 8, 9] };
///
/// assert_eq!(packet.length, 4);
- /// assert_eq!(packet.body, [0, 1, 0, 2, 0, 3, 0, 4]);
- ///
- /// // Attempt to split `packet` at `length`.
- /// let split = packet.split_at(packet.length.into()).unwrap();
+ /// assert_eq!(packet.body, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
///
- /// // Use a dynamic check to prove that it's okay to return concurrent
- /// // references to `packet` and `rest`.
- /// let (packet, rest) = split.via_runtime_check().unwrap();
+ /// // The two-byte prefix and four-byte body need no trailing padding.
+ /// if let Some(split) = packet.split_at(packet.length as usize) {
+ /// if let Ok((packet, rest)) = split.via_runtime_check() {
+ /// assert_eq!(packet.length, 4);
+ /// assert_eq!(packet.body, [1, 2, 3, 4]);
+ /// assert_eq!(rest, [5, 6, 7, 8, 9]);
+ /// } else {
+ /// unreachable!("A four-byte body requires no trailing padding");
+ /// }
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
+ /// }
///
- /// assert_eq!(packet.length, 4);
- /// assert_eq!(packet.body, [0, 1, 0, 2]);
- /// assert_eq!(rest, [0, 3, 0, 4]);
- ///
- /// // Attempt to split `packet` at `length - 1`.
- /// let idx = packet.length.get() - 1;
- /// let split = packet.split_at(idx as usize).unwrap();
- ///
- /// // Attempt (and fail) to use a dynamic check to prove that it's okay
- /// // to return concurrent references to `packet` and `rest`. Note that
- /// // this is a case of `via_runtime_check` being overly conservative.
- /// // Although the left and right parts indeed overlap, the `Immutable`
- /// // bound ensures that concurrently referencing these overlapping
- /// // parts is sound.
- /// assert!(split.via_runtime_check().is_err());
+ /// // A three-byte body needs a padding byte that would overlap `rest`.
+ /// if let Some(split) = packet.split_at((packet.length - 1) as usize) {
+ /// assert!(split.via_runtime_check().is_err());
+ /// } else {
+ /// unreachable!("One less than the packet's length is within its body");
+ /// }
/// ```
///
#[doc = codegen_section!(
@@ -602,7 +691,7 @@ where
T: ?Sized + SplitAt,
{
#[inline(always)]
- fn into_ptr(self) -> Split<Ptr<'a, T, (Exclusive, Aligned, Valid)>> {
+ fn into_ptr(self) -> Split<Ptr<'a, T, (Exclusive, Aligned, Safe)>> {
let source = Ptr::from_mut(self.source);
// SAFETY: `Ptr::from_mut(self.source)` points to exactly `self.source`,
// and thus maintains the invariants of `self` with respect to `l_len`.
@@ -665,101 +754,135 @@ where
(l.as_mut(), r.as_mut())
}
- /// Produces the split parts of `self`, using [`Unaligned`] to ensure that
- /// it is sound to have concurrent references to both parts.
+ /// Produces the split parts of `self` using [`Self::via_no_dynamic_padding`].
+ #[deprecated(note = "use `Split::via_no_dynamic_padding` instead")]
+ #[doc(hidden)]
+ #[must_use = "has no side effects"]
+ #[inline(always)]
+ pub fn via_unaligned(self) -> (&'a mut T, &'a mut [T::Elem])
+ where
+ T: Unaligned,
+ {
+ self.via_no_dynamic_padding()
+ }
+
+ /// Produces the split parts of `self` using a compile-time assertion to
+ /// ensure that it is unconditionally sound to have concurrent references to
+ /// both parts.
+ ///
+ /// When possible, you should prefer using [`Self::via_into_bytes`], which
+ /// ensures this safety property through regular trait bounds.
///
/// # Examples
///
+ /// In the below example, a single byte of padding exists (on most
+ /// platforms) between `Packet<[u16]>`'s `length` and `body` fields,
+ /// precluding the use of [`Self::via_into_bytes`].
+ ///
/// ```
- /// use zerocopy::{SplitAt, FromBytes};
+ /// use zerocopy::SplitAt;
/// # use zerocopy_derive::*;
///
- /// #[derive(SplitAt, FromBytes, KnownLayout, IntoBytes, Unaligned)]
+ /// #[derive(SplitAt, KnownLayout)]
/// #[repr(C)]
/// struct Packet<B: ?Sized> {
/// length: u8,
/// body: B,
/// }
///
- /// // These bytes encode a `Packet`.
- /// let mut bytes = &mut [4, 1, 2, 3, 4, 5, 6, 7, 8, 9][..];
- ///
- /// let packet = Packet::<[u8]>::mut_from_bytes(bytes).unwrap();
+ /// let packet: &mut Packet<[u16]> = &mut Packet { length: 4, body: [1u16, 2, 3, 4, 5, 6, 7, 8, 9] };
///
/// assert_eq!(packet.length, 4);
/// assert_eq!(packet.body, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
///
- /// {
- /// // Attempt to split `packet` at `length`.
- /// let split = packet.split_at_mut(packet.length as usize).unwrap();
- ///
- /// // Use the `Unaligned` bound on `Packet` to prove that it's okay to
- /// // return concurrent references to `packet` and `rest`.
- /// let (packet, rest) = split.via_unaligned();
- ///
+ /// // Every body length requires no trailing padding.
+ /// if let Some(split) = packet.split_at_mut(packet.length as usize) {
+ /// let (packet, rest) = split.via_no_dynamic_padding();
/// assert_eq!(packet.length, 4);
/// assert_eq!(packet.body, [1, 2, 3, 4]);
/// assert_eq!(rest, [5, 6, 7, 8, 9]);
- ///
/// rest.fill(0);
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
/// }
///
/// assert_eq!(packet.length, 4);
/// assert_eq!(packet.body, [1, 2, 3, 4, 0, 0, 0, 0, 0]);
/// ```
///
+ /// # Compile-Time Assertions
+ ///
+ /// This method rejects at compile-time any type that admits dynamic
+ /// trailing padding. In the below example, some `split_at` indices would
+ /// produce left parts whose trailing padding overlaps with the right part:
+ ///
+ /// ```compile_fail,E0080
+ /// use zerocopy::SplitAt;
+ /// # use zerocopy_derive::*;
+ ///
+ /// #[derive(SplitAt, KnownLayout)]
+ /// #[repr(C, align(2))]
+ /// struct Packet<B: ?Sized> {
+ /// length: u16,
+ /// body: B,
+ /// }
+ ///
+ /// let packet: &mut Packet<[u8]> = &mut Packet { length: 4, body: [1u8, 2, 3, 4, 5, 6, 7, 8, 9] };
+ ///
+ /// if let Some(split) = packet.split_at_mut(packet.length as usize) {
+ /// let _ = split.via_no_dynamic_padding(); // ⚠ Compile Error!
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
+ /// }
+ /// ```
+ ///
+ /// If you need to split such types, use [`Self::via_runtime_check`].
+ ///
/// # Code Generation
///
/// See [`Split::via_immutable`](#method.split_via_immutable.codegen).
#[must_use = "has no side effects"]
#[inline(always)]
- pub fn via_unaligned(self) -> (&'a mut T, &'a mut [T::Elem])
- where
- T: Unaligned,
- {
- let (l, r) = self.into_ptr().via_unaligned();
+ pub fn via_no_dynamic_padding(self) -> (&'a mut T, &'a mut [T::Elem]) {
+ let (l, r) = self.into_ptr().via_no_dynamic_padding();
(l.as_mut(), r.as_mut())
}
/// Produces the split parts of `self`, using a dynamic check to ensure that
/// it is sound to have concurrent references to both parts. You should
- /// prefer using [`Self::via_into_bytes`] or [`Self::via_unaligned`], which
- /// have no runtime cost.
+ /// prefer using [`Self::via_into_bytes`] or
+ /// [`Self::via_no_dynamic_padding`], which perform no runtime check.
///
/// # Examples
///
/// ```
- /// use zerocopy::{SplitAt, FromBytes};
+ /// use zerocopy::SplitAt;
/// # use zerocopy_derive::*;
///
- /// #[derive(SplitAt, FromBytes, KnownLayout, IntoBytes, Debug)]
- /// #[repr(C)]
+ /// #[derive(SplitAt, KnownLayout)]
+ /// #[repr(C, align(2))]
/// struct Packet<B: ?Sized> {
- /// length: u8,
+ /// length: u16,
/// body: B,
/// }
///
- /// // These bytes encode a `Packet`.
- /// let mut bytes = &mut [4, 1, 2, 3, 4, 5, 6, 7, 8, 9][..];
- ///
- /// let packet = Packet::<[u8]>::mut_from_bytes(bytes).unwrap();
+ /// let packet: &mut Packet<[u8]> = &mut Packet { length: 4, body: [1u8, 2, 3, 4, 5, 6, 7, 8, 9] };
///
/// assert_eq!(packet.length, 4);
/// assert_eq!(packet.body, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
///
- /// {
- /// // Attempt to split `packet` at `length`.
- /// let split = packet.split_at_mut(packet.length as usize).unwrap();
- ///
- /// // Use a dynamic check to prove that it's okay to return concurrent
- /// // references to `packet` and `rest`.
- /// let (packet, rest) = split.via_runtime_check().unwrap();
- ///
- /// assert_eq!(packet.length, 4);
- /// assert_eq!(packet.body, [1, 2, 3, 4]);
- /// assert_eq!(rest, [5, 6, 7, 8, 9]);
- ///
- /// rest.fill(0);
+ /// // The two-byte prefix and four-byte body need no trailing padding.
+ /// if let Some(split) = packet.split_at_mut(packet.length as usize) {
+ /// if let Ok((packet, rest)) = split.via_runtime_check() {
+ /// assert_eq!(packet.length, 4);
+ /// assert_eq!(packet.body, [1, 2, 3, 4]);
+ /// assert_eq!(rest, [5, 6, 7, 8, 9]);
+ /// rest.fill(0);
+ /// } else {
+ /// unreachable!("A four-byte body requires no trailing padding");
+ /// }
+ /// } else {
+ /// unreachable!("The packet's length field is within its body");
/// }
///
/// assert_eq!(packet.length, 4);
@@ -805,7 +928,7 @@ where
impl<'a, T, I> Split<Ptr<'a, T, I>>
where
T: ?Sized + SplitAt,
- I: Invariants<Alignment = Aligned, Validity = Valid>,
+ I: Invariants<Alignment = Aligned, Validity = Safe>,
{
fn into_ref(self) -> Split<&'a T>
where
@@ -860,37 +983,57 @@ where
unsafe { self.via_unchecked() }
}
- /// Produces the split parts of `self`, using [`Unaligned`] to ensure that
- /// it is sound to have concurrent references to both parts.
+ /// Produces the split parts of `self` after the conservative layout
+ /// predicate establishes that `T` never requires dynamic trailing padding.
#[inline(always)]
- fn via_unaligned(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>)
- where
- T: Unaligned,
- {
- // SAFETY: By `T: SplitAt + Unaligned`, `T` is either a slice or a
- // `repr(C)` or `repr(transparent)` slice DST that is well-aligned at
- // any address and length. If `T` is a slice DST with alignment 1,
- // `repr(C)` or `repr(transparent)` ensures that no padding is placed
- // after the final element of the trailing slice. Consequently, `T` can
- // be split into strictly non-overlapping parts any any index.
+ fn via_no_dynamic_padding(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>) {
+ static_assert!(
+ T: ?Sized + KnownLayout => !T::LAYOUT.requires_dynamic_padding(),
+ "`Split::via_no_dynamic_padding` cannot be used with a type whose layout may require dynamic trailing padding; use `Split::via_runtime_check` instead"
+ );
+
+ // SAFETY: The assertion establishes that `requires_dynamic_padding()`
+ // is false. By that method's guarantee, every valid metadata—including
+ // `self.l_len()`, which is valid by `Split`'s invariant—describes an
+ // object with no trailing padding. By the postcondition of
+ // `PtrInner::split_at_unchecked`, the two returned referents are then
+ // contiguous and non-overlapping. Since the source already conforms to
+ // `I::Aliasing`, splitting it into disjoint byte ranges preserves that
+ // invariant: under `Exclusive`, neither result aliases or accesses the
+ // other's bytes; under `Shared`, interior mutation through one result
+ // cannot mutate bytes pointed to by the other. This satisfies Rust's
+ // reference aliasing rules [1].
+ //
+ // [1] Per https://doc.rust-lang.org/1.93.1/std/ptr/index.html#pointer-to-reference-conversion:
+ //
+ // When creating a mutable reference, then while this reference
+ // exists, the memory it points to must not get accessed (read or
+ // written) through any other pointer or reference not derived from
+ // this reference.
+ //
+ // When creating a shared reference, then while this reference
+ // exists, the memory it points to must not get mutated (except inside
+ // `UnsafeCell`).
unsafe { self.via_unchecked() }
}
/// Produces the split parts of `self`, using a dynamic check to ensure that
/// it is sound to have concurrent references to both parts. You should
/// prefer using [`Self::via_immutable`], [`Self::via_into_bytes`], or
- /// [`Self::via_unaligned`], which have no runtime cost.
+ /// [`Self::via_no_dynamic_padding`], which perform no runtime check.
#[inline(always)]
fn via_runtime_check(self) -> Result<(Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>), Self> {
let l_len = self.l_len();
+ // `l_len` witnesses an object size at most `isize::MAX`, and
+ // `KnownLayout::LAYOUT` guarantees that it contains the trailing
+ // slice. Thus `padding_for_elems` returns the exact trailing padding.
+ let trailing_padding = crate::trailing_slice_layout::<T>().padding_for_elems(l_len.get());
// FIXME(#1290): Once we require `KnownLayout` on all fields, add an
// `IS_IMMUTABLE` associated const, and add `T::IS_IMMUTABLE ||` to the
// below check.
- if l_len.padding_needed_for() == 0 {
- // SAFETY: By `T: SplitAt`, `T` is either `[T]`, or a `repr(C)` or
- // `repr(transparent)` slice DST, for which the trailing padding
- // needed to accommodate `l_len` trailing elements is
- // `l_len.padding_needed_for()`. If no trailing padding is required,
+ if trailing_padding == 0 {
+ // SAFETY: As established above, `trailing_padding` is the exact
+ // padding after the left part's trailing slice. If it is zero,
// the left and right parts are strictly non-overlapping.
Ok(unsafe { self.via_unchecked() })
} else {
@@ -903,7 +1046,7 @@ where
/// # Safety
///
/// The caller promises that if `I::Aliasing` is [`Exclusive`] or `T`
- /// permits interior mutation, then `l_len.padding_needed_for() == 0`.
+ /// permits interior mutation, then the left part has no trailing padding.
#[inline(always)]
unsafe fn via_unchecked(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>) {
let l_len = self.l_len();
@@ -915,11 +1058,11 @@ where
// Lemma 0: `left` and `right` conform to the aliasing invariant
// `I::Aliasing`. Proof: If `I::Aliasing` is `Exclusive` or `T` permits
- // interior mutation, the caller promises that `l_len.padding_needed_for()
- // == 0`. Consequently, by post-condition on `PtrInner::split_at_unchecked`,
- // there is no trailing padding after `left`'s final element that would
- // overlap into `right`. If `I::Aliasing` is shared and `T` forbids interior
- // mutation, then overlap between their referents is permissible.
+ // interior mutation, the caller promises that the left part has no
+ // trailing padding. Consequently, by the postcondition of
+ // `PtrInner::split_at_unchecked`, `left` and `right` do not overlap.
+ // If `I::Aliasing` is shared and `T` forbids interior mutation, then
+ // overlap between their referents is permissible.
// SAFETY:
// 0. `left` conforms to the aliasing invariant of `I::Aliasing`, by Lemma 0.
@@ -933,17 +1076,19 @@ where
// SAFETY:
// 0. `right` conforms to the aliasing invariant of `I::Aliasing`, by Lemma
// 0.
- // 1. `right` conforms to the alignment invariant of `I::Alignment, because
- // if `ptr` with `I::Alignment = Aligned`, then by invariant on `T:
- // SplitAt`, the trailing slice of `ptr` (from which `right` is derived)
- // will also be well-aligned.
+ // 1. `right` conforms to the alignment invariant of `I::Alignment`:
+ // if the source is aligned, `T: SplitAt` guarantees that its
+ // trailing slice's address is aligned for `T::Elem`. Advancing by
+ // whole elements preserves that alignment.
// 2. `right` conforms to the validity invariant of `I::Validity`,
- // because `right: [T::Elem]` is derived from the trailing slice of
- // `ptr`, which, by contract on `T: SplitAt::Elem`, has type
- // `[T::Elem]`. The `left` part cannot be used to invalidate `right`,
- // because the caller promises that if `I::Aliasing` is `Exclusive`
- // or `T` permits interior mutation, then `l_len.padding_needed_for()
- // == 0` and thus the parts will be non-overlapping.
+ // because `T: SplitAt` guarantees that `T::Elem` has the size and
+ // bit validity of the original trailing element. It also guarantees
+ // compatible shared and mutable access, so writes through `right`
+ // preserve the original elements' validity and interior mutation
+ // respects the original `UnsafeCell` coverage. The left part
+ // cannot invalidate `right`: when the references are exclusive or
+ // permit interior mutation, the caller guarantees no trailing
+ // padding and hence no overlap.
let right = unsafe { Ptr::from_inner(right) };
(left, right)
@@ -952,6 +1097,185 @@ where
#[cfg(test)]
mod tests {
+ use core::{cell::Cell, mem::ManuallyDrop};
+
+ use crate::{Immutable, KnownLayout, SplitAt, Unalign, Unaligned};
+
+ #[derive(KnownLayout, SplitAt, Immutable, Unaligned)]
+ #[repr(C, packed)]
+ struct Packed<T: ?Sized> {
+ prefix: u8,
+ tail: ManuallyDrop<T>,
+ }
+
+ #[derive(KnownLayout, SplitAt, Immutable)]
+ #[repr(C, packed(2))]
+ struct Packed2<T: ?Sized> {
+ prefix: u8,
+ tail: ManuallyDrop<T>,
+ }
+
+ #[derive(KnownLayout, SplitAt, Immutable)]
+ #[repr(C)]
+ struct Inner<T: ?Sized> {
+ prefix: u32,
+ tail: T,
+ }
+
+ #[test]
+ fn test_split_at_packed() {
+ use crate::{FromBytes, IntoBytes};
+
+ #[derive(FromBytes, IntoBytes, KnownLayout, SplitAt, Immutable, Unaligned)]
+ #[repr(C, packed)]
+ struct Packet {
+ prefix: u8,
+ tail: [u16],
+ }
+
+ // The tail starts at an odd address, exercising unaligned reads and
+ // writes on targets where `u16` has alignment 2.
+ #[repr(align(8))]
+ struct Bytes([u8; 9]);
+
+ for i in 0..=4 {
+ let mut bytes = Bytes([1; 9]);
+ let packet = Packet::ref_from_bytes(&bytes.0).unwrap();
+ let (left, right): (&Packet, &[Unalign<u16>]) =
+ packet.split_at(i).unwrap().via_immutable();
+ assert_eq!(core::mem::size_of_val(left), 1 + 2 * i);
+ assert_eq!(right.len(), 4 - i);
+ assert!(right.iter().all(|elem| elem.get() == 0x0101));
+ assert!(left.split_at(i).is_some());
+ assert!(left.split_at(i + 1).is_none());
+ assert!(packet.split_at(5).is_none());
+
+ let (_, right) = packet.split_at(i).unwrap().via_into_bytes();
+ assert_eq!(right.len(), 4 - i);
+ let (_, right) = packet.split_at(i).unwrap().via_no_dynamic_padding();
+ assert_eq!(right.len(), 4 - i);
+
+ let packet = Packet::mut_from_bytes(&mut bytes.0).unwrap();
+ let (left, right): (&mut Packet, &mut [Unalign<u16>]) =
+ packet.split_at_mut(i).unwrap().via_runtime_check().ok().unwrap();
+ left.prefix = 2;
+ for elem in right {
+ *elem = Unalign::new(0x0202);
+ }
+ assert_eq!(bytes.0[0], 2);
+ assert!(bytes.0[1..1 + 2 * i].iter().all(|&byte| byte == 1));
+ assert!(bytes.0[1 + 2 * i..].iter().all(|&byte| byte == 2));
+ }
+ }
+
+ #[test]
+ fn test_split_at_nested_packed() {
+ // A packing factor greater than 1 still exposes `Unalign` elements,
+ // including when the factor is below the original element alignment.
+ let mut words = Packed2 { prefix: 0, tail: ManuallyDrop::new([1u32, 2, 3]) };
+ let dst: &mut Packed2<[u32]> = &mut words;
+ let (left, right): (&mut _, &mut [Unalign<u32>]) =
+ dst.split_at_mut(1).unwrap().via_no_dynamic_padding();
+ left.prefix = 4;
+ right[0] = Unalign::new(5);
+ let dst: &Packed2<[u32]> = &words;
+ let (left, right) = dst.split_at(0).unwrap().via_immutable();
+ assert_eq!(left.prefix, 4);
+ assert_eq!(right[0].get(), 1);
+ assert_eq!(right[1].get(), 5);
+ assert_eq!(right[2].get(), 3);
+
+ let mut packet = Inner {
+ prefix: 0,
+ tail: Packed2 {
+ prefix: 1,
+ tail: ManuallyDrop::new(Packed {
+ prefix: 2,
+ tail: ManuallyDrop::new([3u16, 4, 5]),
+ }),
+ },
+ };
+ let dst: &Inner<Packed2<Packed<[u16]>>> = &packet;
+ let (left, right): (&_, &[Unalign<Unalign<u16>>]) =
+ dst.split_at(1).unwrap().via_immutable();
+ assert_eq!(left.prefix, 0);
+ assert_eq!(right.len(), 2);
+ assert_eq!(right[0].get().get(), 4);
+ assert_eq!(right[1].get().get(), 5);
+
+ // Split the packed field separately: the unpacked outer struct's
+ // padding does not constrain this split.
+ let dst: &mut Packed2<Packed<[u16]>> = &mut packet.tail;
+ let (left, right): (&mut _, &mut [Unalign<Unalign<u16>>]) =
+ dst.split_at_mut(1).unwrap().via_no_dynamic_padding();
+ left.prefix = 6;
+ right[0] = Unalign::new(Unalign::new(7));
+ let dst: &Inner<Packed2<Packed<[u16]>>> = &packet;
+ let (_, right) = dst.split_at(0).unwrap().via_immutable();
+ assert_eq!(right[0].get().get(), 3);
+ assert_eq!(right[1].get().get(), 7);
+ assert_eq!(right[2].get().get(), 5);
+ }
+
+ #[test]
+ fn test_split_at_packed_preserves_inner_padding() {
+ for i in 0..=4 {
+ let mut packet = Packed {
+ prefix: 1,
+ tail: ManuallyDrop::new(Inner { prefix: 0, tail: [2u8, 3, 4, 5] }),
+ };
+ let dst: &Packed<Inner<[u8]>> = &packet;
+ let (left, right): (&_, &[Unalign<u8>]) = dst.split_at(i).unwrap().via_immutable();
+ assert_eq!(right.len(), 4 - i);
+ assert_eq!(right.first().map(Unalign::get), [2, 3, 4, 5].get(i).copied());
+
+ // The inner `u32` prefix makes the tail start at byte 5 of the
+ // packed wrapper. Its size may still include inner padding.
+ let has_padding = core::mem::size_of_val(left) != 5 + i;
+ assert_eq!(dst.split_at(i).unwrap().via_runtime_check().is_err(), has_padding);
+
+ let dst: &mut Packed<Inner<[u8]>> = &mut packet;
+ let split = dst.split_at_mut(i).unwrap().via_runtime_check();
+ assert_eq!(split.is_err(), has_padding);
+ if let Ok((left, right)) = split {
+ left.prefix = 6;
+ for elem in right {
+ *elem = Unalign::new(7);
+ }
+ let dst: &Packed<Inner<[u8]>> = &packet;
+ let (left, right) = dst.split_at(i).unwrap().via_immutable();
+ assert_eq!(left.prefix, 6);
+ assert!(right.iter().all(|elem| elem.get() == 7));
+ }
+ }
+ }
+
+ #[test]
+ fn test_split_at_packed_interior_mutation_and_zsts() {
+ let packet = Packed {
+ prefix: 0,
+ tail: ManuallyDrop::new([Cell::new(1u8), Cell::new(2), Cell::new(3)]),
+ };
+ let dst: &Packed<[Cell<u8>]> = &packet;
+ let (left, right): (&_, &[Unalign<Cell<u8>>]) =
+ dst.split_at(1).unwrap().via_no_dynamic_padding();
+ left.tail[0].set(4);
+ right[0].try_deref().unwrap().set(5);
+ assert_eq!(dst.tail[0].get(), 4);
+ assert_eq!(dst.tail[1].get(), 5);
+ assert_eq!(dst.tail[2].get(), 3);
+
+ let mut packet = Packed { prefix: 0, tail: ManuallyDrop::new([(); 3]) };
+ for i in 0..=3 {
+ let dst: &mut Packed<[()]> = &mut packet;
+ let (left, right): (&mut _, &mut [Unalign<()>]) =
+ dst.split_at_mut(i).unwrap().via_no_dynamic_padding();
+ assert_eq!(left.tail.len(), i);
+ assert_eq!(right.len(), 3 - i);
+ assert_eq!(core::mem::size_of_val(left), 1);
+ }
+ }
+
#[cfg(feature = "derive")]
#[test]
fn test_split_at() {
@@ -1011,6 +1335,8 @@ mod tests {
trailing: [u8],
}
+ assert!(SliceDst::LAYOUT.requires_dynamic_padding());
+
const N: usize = 16;
let arr = [1u16; N];
@@ -1070,21 +1396,79 @@ mod tests {
assert_eq!(r, &[4]);
}
#[test]
+ #[allow(deprecated)]
fn test_split_at_via_unaligned() {
- use crate::{FromBytes, Immutable, IntoBytes, KnownLayout, SplitAt, Unaligned};
- #[derive(FromBytes, KnownLayout, SplitAt, IntoBytes, Immutable, Unaligned)]
+ use crate::{Immutable, KnownLayout, Split, SplitAt, Unaligned};
+
+ fn via_unaligned<'a, T>(split: Split<&'a T>) -> (&'a T, &'a [T::Elem])
+ where
+ T: ?Sized + SplitAt + Unaligned,
+ {
+ split.via_unaligned()
+ }
+
+ fn via_unaligned_mut<'a, T>(split: Split<&'a mut T>) -> (&'a mut T, &'a mut [T::Elem])
+ where
+ T: ?Sized + SplitAt + Unaligned,
+ {
+ split.via_unaligned()
+ }
+
+ #[derive(KnownLayout, SplitAt, Immutable, Unaligned)]
#[repr(C)]
- struct Packet {
- length: u8,
- body: [u8],
+ struct Packet<B: ?Sized> {
+ prefix: [u8; 2],
+ body: B,
}
- let arr = [1, 2, 3, 4];
- let packet = Packet::ref_from_bytes(&arr[..]).unwrap();
+ // Exercise generic callers using the original `T: Unaligned` bound.
+ assert!(!Packet::<[[u8; 2]]>::LAYOUT.requires_dynamic_padding());
+ let packet = Packet { prefix: [0, 1], body: [[2, 3], [4, 5], [6, 7]] };
+ let packet: &Packet<[[u8; 2]]> = &packet;
let split = packet.split_at(2).unwrap();
- let (l, r) = split.via_unaligned();
- assert_eq!(l.length, 1);
- assert_eq!(r, &[4]);
+ let (l, r) = via_unaligned(split);
+ assert_eq!(l.body, [[2, 3], [4, 5]]);
+ assert_eq!(r, &[[6, 7]]);
+
+ let mut packet = Packet { prefix: [0, 1], body: [[2, 3], [4, 5], [6, 7]] };
+ {
+ let packet: &mut Packet<[[u8; 2]]> = &mut packet;
+ let split = packet.split_at_mut(2).unwrap();
+ let (l, r) = via_unaligned_mut(split);
+ l.body[0] = [8, 9];
+ r[0] = [10, 11];
+ }
+ assert_eq!(packet.body, [[8, 9], [4, 5], [10, 11]]);
+ }
+
+ #[test]
+ fn test_split_at_via_no_dynamic_padding() {
+ use core::cell::Cell;
+
+ use crate::SplitAt;
+
+ // `u16` does not implement `Unaligned`, but `[u16]` never requires
+ // dynamic trailing padding.
+ let words = [1u16, 2, 3, 4];
+ let split = SplitAt::split_at(&words[..], 2).unwrap();
+ let (left, right) = split.via_no_dynamic_padding();
+ assert_eq!(left, [1, 2]);
+ assert_eq!(right, [3, 4]);
+
+ let mut words = [1u16, 2, 3, 4];
+ let split = SplitAt::split_at_mut(&mut words[..], 2).unwrap();
+ let (left, right) = split.via_no_dynamic_padding();
+ left[0] = 5;
+ right[0] = 6;
+ assert_eq!(words, [5, 2, 6, 4]);
+
+ // Exercise the safety-sensitive shared route with interior mutation.
+ let cells = [Cell::new(1u16), Cell::new(2), Cell::new(3)];
+ let split = SplitAt::split_at(&cells[..], 2).unwrap();
+ let (left, right) = split.via_no_dynamic_padding();
+ left[0].set(4);
+ right[0].set(5);
+ assert_eq!([cells[0].get(), cells[1].get(), cells[2].get()], [4, 2, 5]);
}
}