Skip to content

Commit bc20681

Browse files
joshlfjswrenn
andauthored
Teach transmute_{ref,mut}! to handle slice DSTs (#2428)
We generalize our existing support in `util::macro_util`, now relying on PMEs to detect when transmutes cannot be supported. In order to continue to support sized reference transmutation in a `const` context, we use the autoref specialization trick to fall back to our old (`const`-safe) implementation when both the source and destination type are `Sized`. The main challenge in generalizing this support is making a trait implementation available conditional on a PME. We do this using the `unsafe_with_size_eq!` helper macro, which introduces `#[repr(transparent)]` wrapper types, `Src` and `Dst`. Internal to this macro, we implement `SizeEq<Src<T>> for Dst<U>`, with the implementation of `SizeEq::cast_from_raw` containing the PME logic for size equality. The macro's caller must promise to only use the `Src` and `Dst` types to wrap the `T` and `U` types for which this PME logic has been applied (or else the `SizeEq` impl could "leak" to types for which it is unsound). In addition, we generalize our prior support for transmuting between unsized types. In particular, we previously used the `SizeEq` trait to denote that two types have equal sizes in the face of a cast operation (in particular, that `*const T as *const U` preserves referent size). In this commit, we add support for metadata fix-up, which means that we support casts for which `*const T as *const U` does *not* preserve referent size. Instead, we compute an affine function at compile time and apply it at runtime - computing the destination type's metadata as a function of the source metadata, `dst_meta = A + src_meta * B`. `A` and `B` are computed at compile time. We generalize `SizeEq` to permit its `cast_from_raw` method to perform a runtime metadata fix-up operation. Makes progress on #1817 gherrit-pr-id: Ib4bc62202e0b3b09d155333b525087f7aa8f02c2 Co-authored-by: Jack Wrenn <[email protected]>
1 parent f9c9703 commit bc20681

File tree

154 files changed

+1455
-4619
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

154 files changed

+1455
-4619
lines changed

src/doctests.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2025 The Fuchsia Authors
2+
//
3+
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
4+
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
5+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
6+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
7+
// This file may not be copied, modified, or distributed except according to
8+
// those terms.
9+
10+
#![cfg(feature = "derive")] // Required for derives on `SliceDst`
11+
#![allow(dead_code)]
12+
13+
//! Our UI test framework, built on the `trybuild` crate, does not support
14+
//! testing for post-monomorphization errors. Instead, we use doctests, which
15+
//! are able to test for post-monomorphization errors.
16+
17+
use crate::*;
18+
19+
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable)]
20+
#[repr(C)]
21+
#[allow(missing_debug_implementations, missing_copy_implementations)]
22+
pub struct SliceDst<T, U> {
23+
pub t: T,
24+
pub u: [U],
25+
}
26+
27+
#[allow(clippy::must_use_candidate, clippy::missing_inline_in_public_items, clippy::todo)]
28+
impl<T: FromBytes + IntoBytes, U: FromBytes + IntoBytes> SliceDst<T, U> {
29+
pub fn new() -> &'static SliceDst<T, U> {
30+
todo!()
31+
}
32+
33+
pub fn new_mut() -> &'static mut SliceDst<T, U> {
34+
todo!()
35+
}
36+
}
37+
38+
/// We require that the alignment of the destination type is not larger than the
39+
/// alignment of the source type.
40+
///
41+
/// ```compile_fail,E0080
42+
/// let increase_alignment: &u16 = zerocopy::transmute_ref!(&[0u8; 2]);
43+
/// ```
44+
///
45+
/// ```compile_fail,E0080
46+
/// let mut src = [0u8; 2];
47+
/// let increase_alignment: &mut u16 = zerocopy::transmute_mut!(&mut src);
48+
/// ```
49+
enum TransmuteRefMutAlignmentIncrease {}
50+
51+
/// We require that the size of the destination type is not larger than the size
52+
/// of the source type.
53+
///
54+
/// ```compile_fail,E0080
55+
/// let increase_size: &[u8; 2] = zerocopy::transmute_ref!(&0u8);
56+
/// ```
57+
///
58+
/// ```compile_fail,E0080
59+
/// let mut src = 0u8;
60+
/// let increase_size: &mut [u8; 2] = zerocopy::transmute_mut!(&mut src);
61+
/// ```
62+
enum TransmuteRefMutSizeIncrease {}
63+
64+
/// We require that the size of the destination type is not smaller than the
65+
/// size of the source type.
66+
///
67+
/// ```compile_fail,E0080
68+
/// let decrease_size: &u8 = zerocopy::transmute_ref!(&[0u8; 2]);
69+
/// ```
70+
///
71+
/// ```compile_fail,E0080
72+
/// let mut src = [0u8; 2];
73+
/// let decrease_size: &mut u8 = zerocopy::transmute_mut!(&mut src);
74+
/// ```
75+
enum TransmuteRefMutSizeDecrease {}
76+
77+
/// It's not possible in the general case to increase the trailing slice offset
78+
/// during a reference transmutation - some pointer metadata values would not be
79+
/// supportable, and so such a transmutation would be fallible.
80+
///
81+
/// ```compile_fail,E0080
82+
/// use zerocopy::doctests::SliceDst;
83+
/// let src: &SliceDst<u8, u8> = SliceDst::new();
84+
/// let increase_offset: &SliceDst<[u8; 2], u8> = zerocopy::transmute_ref!(src);
85+
/// ```
86+
///
87+
/// ```compile_fail,E0080
88+
/// use zerocopy::doctests::SliceDst;
89+
/// let src: &mut SliceDst<u8, u8> = SliceDst::new_mut();
90+
/// let increase_offset: &mut SliceDst<[u8; 2], u8> = zerocopy::transmute_mut!(src);
91+
/// ```
92+
enum TransmuteRefMutDstOffsetIncrease {}
93+
94+
/// Reference transmutes are not possible when the difference between the source
95+
/// and destination types' trailing slice offsets is not a multiple of the
96+
/// destination type's trailing slice element size.
97+
///
98+
/// ```compile_fail,E0080
99+
/// use zerocopy::doctests::SliceDst;
100+
/// let src: &SliceDst<[u8; 3], [u8; 2]> = SliceDst::new();
101+
/// let _: &SliceDst<[u8; 2], [u8; 2]> = zerocopy::transmute_ref!(src);
102+
/// ```
103+
///
104+
/// ```compile_fail,E0080
105+
/// use zerocopy::doctests::SliceDst;
106+
/// let src: &mut SliceDst<[u8; 3], [u8; 2]> = SliceDst::new_mut();
107+
/// let _: &mut SliceDst<[u8; 2], [u8; 2]> = zerocopy::transmute_mut!(src);
108+
/// ```
109+
enum TransmuteRefMutDstOffsetNotMultiple {}
110+
111+
/// Reference transmutes are not possible when the source's trailing slice
112+
/// element size is not a multiple of the destination's.
113+
///
114+
/// ```compile_fail,E0080
115+
/// use zerocopy::doctests::SliceDst;
116+
/// let src: &SliceDst<(), [u8; 3]> = SliceDst::new();
117+
/// let _: &SliceDst<(), [u8; 2]> = zerocopy::transmute_ref!(src);
118+
/// ```
119+
///
120+
/// ```compile_fail,E0080
121+
/// use zerocopy::doctests::SliceDst;
122+
/// let src: &mut SliceDst<(), [u8; 3]> = SliceDst::new_mut();
123+
/// let _: &mut SliceDst<(), [u8; 2]> = zerocopy::transmute_mut!(src);
124+
/// ```
125+
enum TransmuteRefMutDstElemSizeNotMultiple {}

src/impls.rs

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -173,40 +173,13 @@ const _: () = unsafe {
173173
})
174174
};
175175

176-
// SAFETY: `str` and `[u8]` have the same layout [1].
177-
//
178-
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#str-layout:
179-
//
180-
// String slices are a UTF-8 representation of characters that have the same
181-
// layout as slices of type `[u8]`.
182-
unsafe impl pointer::SizeEq<str> for [u8] {
183-
fn cast_from_raw(s: NonNull<str>) -> NonNull<[u8]> {
184-
cast!(s)
185-
}
186-
}
187-
// SAFETY: See previous safety comment.
188-
unsafe impl pointer::SizeEq<[u8]> for str {
189-
fn cast_from_raw(bytes: NonNull<[u8]>) -> NonNull<str> {
190-
cast!(bytes)
191-
}
192-
}
176+
impl_size_eq!(str, [u8]);
193177

194178
macro_rules! unsafe_impl_try_from_bytes_for_nonzero {
195179
($($nonzero:ident[$prim:ty]),*) => {
196180
$(
197181
unsafe_impl!(=> TryFromBytes for $nonzero; |n| {
198-
// SAFETY: The caller promises that this is sound.
199-
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {
200-
fn cast_from_raw(n: NonNull<$nonzero>) -> NonNull<Unalign<$prim>> {
201-
cast!(n)
202-
}
203-
}
204-
// SAFETY: The caller promises that this is sound.
205-
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {
206-
fn cast_from_raw(p: NonNull<Unalign<$prim>>) -> NonNull<$nonzero> {
207-
cast!(p)
208-
}
209-
}
182+
impl_size_eq!($nonzero, Unalign<$prim>);
210183

211184
let n = n.transmute::<Unalign<$prim>, invariant::Valid, _>();
212185
$nonzero::new(n.read_unaligned().into_inner()).is_some()
@@ -423,8 +396,8 @@ mod atomics {
423396
($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {{
424397
crate::util::macros::__unsafe();
425398

426-
use core::{cell::UnsafeCell, ptr::NonNull};
427-
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
399+
use core::cell::UnsafeCell;
400+
use crate::pointer::{PtrInner, SizeEq, TransmuteFrom, invariant::Valid};
428401

429402
$(
430403
// SAFETY: The caller promised that `$atomic` and `$prim` have
@@ -437,15 +410,20 @@ mod atomics {
437410
// SAFETY: The caller promised that `$atomic` and `$prim` have
438411
// the same size.
439412
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
440-
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<$prim> {
441-
cast!(a)
413+
#[inline]
414+
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, $prim> {
415+
// SAFETY: The caller promised that `$atomic` and
416+
// `$prim` have the same size. Thus, this cast preserves
417+
// address, referent size, and provenance.
418+
unsafe { cast!(a) }
442419
}
443420
}
444-
// SAFETY: The caller promised that `$atomic` and `$prim` have
445-
// the same size.
421+
// SAFETY: See previous safety comment.
446422
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
447-
fn cast_from_raw(p: NonNull<$prim>) -> NonNull<$atomic> {
448-
cast!(p)
423+
#[inline]
424+
fn cast_from_raw(p: PtrInner<'_, $prim>) -> PtrInner<'_, $atomic> {
425+
// SAFETY: See previous safety comment.
426+
unsafe { cast!(p) }
449427
}
450428
}
451429
// SAFETY: The caller promised that `$atomic` and `$prim` have
@@ -457,14 +435,18 @@ mod atomics {
457435
// its inner type `T`. A consequence of this guarantee is that
458436
// it is possible to convert between `T` and `UnsafeCell<T>`.
459437
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
460-
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<UnsafeCell<$prim>> {
461-
cast!(a)
438+
#[inline]
439+
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, UnsafeCell<$prim>> {
440+
// SAFETY: See previous safety comment.
441+
unsafe { cast!(a) }
462442
}
463443
}
464444
// SAFETY: See previous safety comment.
465445
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
466-
fn cast_from_raw(p: NonNull<UnsafeCell<$prim>>) -> NonNull<$atomic> {
467-
cast!(p)
446+
#[inline]
447+
fn cast_from_raw(p: PtrInner<'_, UnsafeCell<$prim>>) -> PtrInner<'_, $atomic> {
448+
// SAFETY: See previous safety comment.
449+
unsafe { cast!(p) }
468450
}
469451
}
470452

0 commit comments

Comments
 (0)