Skip to content

Commit 85b4437

Browse files
joshlfjswrenn
andcommitted
Teach transmute_{ref,mut}! to handle slice DSTs
This requires us to 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 rename `SizeEq` to `SizeCompat`, and permit its `cast_from_raw` method to perform a runtime metadata fix-up operation. We also relax the safety post-condition to specify that `cast_from_raw` may return a pointer with a smaller referent than that of its argument. This second relaxation prepares us to support size truncating casts, for example `&[u8] -> &[u16]` - given an odd number of `u8`s, the only way to return a `&[u16]` is to "lose" the trailing `u8`. We generalize the safety invariant on `TransmuteFrom` to support such casts. Makes progress on #1817 Co-authored-by: Jack Wrenn <[email protected]> gherrit-pr-id: Ib4bc62202e0b3b09d155333b525087f7aa8f02c2
1 parent 7fdec6f commit 85b4437

File tree

156 files changed

+1590
-5077
lines changed

Some content is hidden

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

156 files changed

+1590
-5077
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+
/// `transmute_ref!` does not support transmuting from a type of smaller
39+
/// alignment to one of larger alignment.
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: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ safety_comment! {
114114
});
115115
}
116116

117-
impl_size_eq!(bool, u8);
117+
impl_size_compat!(bool, u8);
118118

119119
safety_comment! {
120120
/// SAFETY:
@@ -145,7 +145,7 @@ safety_comment! {
145145
});
146146
}
147147

148-
impl_size_eq!(char, Unalign<u32>);
148+
impl_size_compat!(char, Unalign<u32>);
149149

150150
safety_comment! {
151151
/// SAFETY:
@@ -179,39 +179,13 @@ safety_comment! {
179179
});
180180
}
181181

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

200184
macro_rules! unsafe_impl_try_from_bytes_for_nonzero {
201185
($($nonzero:ident[$prim:ty]),*) => {
202186
$(
203187
unsafe_impl!(=> TryFromBytes for $nonzero; |n| {
204-
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {
205-
fn cast_from_raw(n: NonNull<$nonzero>) -> NonNull<Unalign<$prim>> {
206-
cast!(n)
207-
}
208-
}
209-
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {
210-
fn cast_from_raw(p: NonNull<Unalign<$prim>>) -> NonNull<$nonzero> {
211-
cast!(p)
212-
}
213-
}
214-
188+
impl_size_compat!($nonzero, Unalign<$prim>);
215189
let n = n.transmute::<Unalign<$prim>, invariant::Valid, _>();
216190
$nonzero::new(n.read_unaligned().into_inner()).is_some()
217191
});
@@ -429,58 +403,75 @@ mod atomics {
429403
macro_rules! unsafe_impl_transmute_from_for_atomic {
430404
($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {
431405
const _: () = {
432-
use core::{cell::UnsafeCell, ptr::NonNull};
433-
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
406+
use core::{cell::UnsafeCell};
407+
use crate::pointer::{TransmuteFrom, PtrInner, SizeCompat, invariant::Valid};
434408

435409
$(
436410
#[allow(unused_unsafe)] // Force the caller to call this macro inside `safety_comment!`.
437411
const _: () = unsafe {};
438412

439-
// SAFETY: The caller promised that `$atomic` and `$prim` have
440-
// the same size and bit validity.
413+
// SAFETY: The caller promised that `$atomic` and `$prim`
414+
// have the same size and bit validity. As a result of size
415+
// equality, both impls of `SizeCompat::cast_from_raw`
416+
// preserve referent size exactly.
441417
unsafe impl<$($tyvar)?> TransmuteFrom<$atomic, Valid, Valid> for $prim {}
442-
// SAFETY: The caller promised that `$atomic` and `$prim` have
443-
// the same size and bit validity.
418+
// SAFETY: The caller promised that `$atomic` and `$prim`
419+
// have the same size and bit validity. As a result of size
420+
// equality, both impls of `SizeCompat::cast_from_raw`
421+
// preserve referent size exactly.
444422
unsafe impl<$($tyvar)?> TransmuteFrom<$prim, Valid, Valid> for $atomic {}
445423

446-
// SAFETY: THe caller promised that `$atomic` and `$prim`
447-
// have the same size.
448-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
449-
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<$prim> {
450-
cast!(a)
424+
// SAFETY: See inline safety comment.
425+
unsafe impl<$($tyvar)?> SizeCompat<$atomic> for $prim {
426+
#[inline(always)]
427+
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, $prim> {
428+
// SAFETY: The caller promised that `$atomic` and `$prim`
429+
// have the same size. Thus, this cast preserves
430+
// address, referent size, and provenance.
431+
unsafe { cast!(a) }
451432
}
452433
}
453-
// SAFETY: THe caller promised that `$atomic` and `$prim`
454-
// have the same size.
455-
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
456-
fn cast_from_raw(p: NonNull<$prim>) -> NonNull<$atomic> {
457-
cast!(p)
434+
// SAFETY: See previous safety comment.
435+
unsafe impl<$($tyvar)?> SizeCompat<$prim> for $atomic {
436+
#[inline(always)]
437+
fn cast_from_raw(p: PtrInner<'_, $prim>) -> PtrInner<'_, $atomic> {
438+
// SAFETY: See previous safety comment.
439+
unsafe { cast!(p) }
458440
}
459441
}
442+
460443
// SAFETY: The caller promised that `$atomic` and `$prim`
461444
// have the same size. `UnsafeCell<T>` has the same size as
462-
// `T` [1].
445+
// `T` [1]. Thus, this cast preserves address, referent
446+
// size, and provenance.
463447
//
464448
// [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout:
465449
//
466450
// `UnsafeCell<T>` has the same in-memory representation as
467451
// its inner type `T`. A consequence of this guarantee is that
468452
// it is possible to convert between `T` and `UnsafeCell<T>`.
469-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
470-
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<UnsafeCell<$prim>> {
471-
cast!(a)
453+
unsafe impl<$($tyvar)?> SizeCompat<$atomic> for UnsafeCell<$prim> {
454+
#[inline(always)]
455+
fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, UnsafeCell<$prim>> {
456+
// SAFETY: See previous safety comment.
457+
unsafe { cast!(a) }
472458
}
473459
}
474460
// SAFETY: See previous safety comment.
475-
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
476-
fn cast_from_raw(p: NonNull<UnsafeCell<$prim>>) -> NonNull<$atomic> {
477-
cast!(p)
461+
unsafe impl<$($tyvar)?> SizeCompat<UnsafeCell<$prim>> for $atomic {
462+
#[inline(always)]
463+
fn cast_from_raw(p: PtrInner<'_, UnsafeCell<$prim>>) -> PtrInner<'_, $atomic> {
464+
// SAFETY: See previous safety comment.
465+
unsafe { cast!(p) }
478466
}
479467
}
480468

481469
// SAFETY: The caller promised that `$atomic` and `$prim`
482470
// have the same bit validity. `UnsafeCell<T>` has the same
483-
// bit validity as `T` [1].
471+
// bit validity as `T` [1]. `UnsafeCell<T>` also has the
472+
// same size as `T` [1], and so both impls of
473+
// `SizeCompat::cast_from_raw` preserve referent size
474+
// exactly.
484475
//
485476
// [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout:
486477
//

0 commit comments

Comments
 (0)