Skip to content

Commit

Permalink
Lower MSRV to 1.60
Browse files Browse the repository at this point in the history
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
  • Loading branch information
joshlf committed Oct 28, 2023
1 parent aa91421 commit 4e14a22
Show file tree
Hide file tree
Showing 79 changed files with 1,592 additions and 427 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
[workspace]

[package]
edition = "2021"
edition = "2018"
name = "zerocopy"
version = "0.7.18"
version = "0.7.19"
authors = ["Joshua Liebow-Feeser <[email protected]>"]
description = "Utilities for zero-copy parsing and serialization"
license = "BSD-2-Clause OR Apache-2.0 OR MIT"
repository = "https://github.com/google/zerocopy"
rust-version = "1.61.0"
rust-version = "1.60.0"

exclude = [".*"]

Expand All @@ -45,7 +45,7 @@ simd-nightly = ["simd"]
__internal_use_only_features_that_work_on_stable = ["alloc", "derive", "simd"]

[dependencies]
zerocopy-derive = { version = "=0.7.18", path = "zerocopy-derive", optional = true }
zerocopy-derive = { version = "=0.7.19", path = "zerocopy-derive", optional = true }

[dependencies.byteorder]
version = "1.3"
Expand All @@ -56,7 +56,7 @@ optional = true
# zerocopy-derive remain equal, even if the 'derive' feature isn't used.
# See: https://github.com/matklad/macro-dep-test
[target.'cfg(any())'.dependencies]
zerocopy-derive = { version = "=0.7.18", path = "zerocopy-derive" }
zerocopy-derive = { version = "=0.7.19", path = "zerocopy-derive" }

[dev-dependencies]
assert_matches = "1.5"
Expand All @@ -71,4 +71,4 @@ testutil = { path = "testutil" }
# CI test failures.
trybuild = { version = "=1.0.85", features = ["diff"] }
# In tests, unlike in production, zerocopy-derive is not optional
zerocopy-derive = { version = "=0.7.18", path = "zerocopy-derive" }
zerocopy-derive = { version = "=0.7.19", path = "zerocopy-derive" }
138 changes: 66 additions & 72 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2007,10 +2007,14 @@ macro_rules! transmute {
// This branch, though never taken, ensures that the type of `e` is
// `AsBytes` and that the type of this macro invocation expression
// is `FromBytes`.
const fn transmute<T: $crate::AsBytes, U: $crate::FromBytes>(_t: T) -> U {
loop {}
}
transmute(e)

struct AssertIsAsBytes<T: $crate::AsBytes>(T);
let _ = AssertIsAsBytes(e);

struct AssertIsFromBytes<U: $crate::FromBytes>(U);
#[allow(unused, unreachable_code)]
let u = AssertIsFromBytes(loop {});
u.0
} else {
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
// the type of this macro invocation expression have the same size.
Expand Down Expand Up @@ -2092,10 +2096,14 @@ macro_rules! transmute_ref {
// `&T` where `T: 't + Sized + AsBytes`, that the type of this macro
// expression is `&U` where `U: 'u + Sized + FromBytes`, and that
// `'t` outlives `'u`.
const fn transmute<'u, 't: 'u, T: 't + Sized + $crate::AsBytes, U: 'u + Sized + $crate::FromBytes>(_t: &'t T) -> &'u U {
loop {}
}
transmute(e)

struct AssertIsAsBytes<'a, T: ::core::marker::Sized + $crate::AsBytes>(&'a T);
let _ = AssertIsAsBytes(e);

struct AssertIsFromBytes<'a, U: ::core::marker::Sized + $crate::FromBytes>(&'a U);
#[allow(unused, unreachable_code)]
let u = AssertIsFromBytes(loop {});
u.0
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
Expand All @@ -2115,32 +2123,14 @@ macro_rules! transmute_ref {

&u
} else {
// SAFETY:
// - We know that the input and output types are both `Sized` (ie,
// thin) references thanks to the trait bounds on `transmute`
// above, and thanks to the fact that transmute takes and returns
// references.
// - We know that it is sound to view the target type of the input
// reference (`T`) as the target type of the output reference
// (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by
// trait bounds on `transmute`) and because `size_of::<T>() ==
// size_of::<U>()` (guaranteed by the `assert_size_eq` above).
// - We know that alignment is not increased thanks to the
// `assert_align_gt_eq` above.
//
// We use this reexport of `core::mem::transmute` because we know it
// will always be available for crates which are using the 2015
// edition of Rust. By contrast, if we were to use
// `std::mem::transmute`, this macro would not work for such crates
// in `no_std` contexts, and if we were to use
// `core::mem::transmute`, this macro would not work in `std`
// contexts in which `core` was not manually imported. This is not a
// problem for 2018 edition crates.
unsafe {
// Clippy: It's okay to transmute a type to itself.
#[allow(clippy::useless_transmute)]
$crate::macro_util::core_reexport::mem::transmute(e)
}
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `Src: AsBytes` and `Dst: FromBytes` thanks to the
// uses of `AssertIsAsBytes` and `AssertIsFromBytes` above.
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
// the use of `assert_align_gt_eq!` above.
unsafe { $crate::macro_util::transmute_ref(e) }
}
}}
}
Expand Down Expand Up @@ -2191,22 +2181,41 @@ macro_rules! transmute_mut {
// because there's no way, in a generic context, to enforce that two
// types have the same size or alignment.

let e = $e;
// Ensure that the source type is a mutable reference.
let e = &mut *$e;

#[allow(unused, clippy::diverging_sub_expression)]
if false {
// This branch, though never taken, ensures that the type of `e` is
// `&mut T` where `T: 't + Sized + AsBytes + FromBytes`, that the
// `&mut T` where `T: 't + Sized + FromBytes + AsBytes`, that the
// type of this macro expression is `&mut U` where `U: 'u + Sized +
// AsBytes + FromBytes`.
fn transmute<'t, T, U>(_t: &'t mut T) -> &'t mut U
where
T: 't + Sized + $crate::AsBytes + $crate::FromBytes,
U: 't + Sized + $crate::AsBytes + $crate::FromBytes,
{
loop {}
// FromBytes + AsBytes`.

// We use immutable references here rather than mutable so that, if
// this macro is used in a const context (in which, as of this
// writing, mutable references are banned), the error message
// appears to originate in the user's code rather than in the
// internals of this macro.
struct AssertSrcIsFromBytes<'a, T: ::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertSrcIsAsBytes<'a, T: ::core::marker::Sized + $crate::AsBytes>(&'a T);
struct AssertDstIsFromBytes<'a, T: ::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertDstIsAsBytes<'a, T: ::core::marker::Sized + $crate::AsBytes>(&'a T);

if true {
let _ = AssertSrcIsFromBytes(&*e);
} else {
let _ = AssertSrcIsAsBytes(&*e);
}

if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsFromBytes(loop {});
&mut *u.0
} else {
#[allow(unused, unreachable_code)]
let u = AssertDstIsAsBytes(loop {});
&mut *u.0
}
transmute(e)
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
Expand All @@ -2226,33 +2235,16 @@ macro_rules! transmute_mut {

&mut u
} else {
// SAFETY:
// - We know that the input and output types are both `Sized` (ie,
// thin) references thanks to the trait bounds on `transmute`
// above, and thanks to the fact that transmute takes and returns
// references.
// - We know that it is sound to view the target type of the input
// reference (`T`) as the target type of the output reference
// (`U`) and visa versa because `T: AsBytes + FromBytes` and `U:
// AsBytes + FromBytes` (guaranteed by trait bounds on
// `transmute`) and because `size_of::<T>() == size_of::<U>()`
// (guaranteed by the `assert_size_eq` above).
// - We know that alignment is not increased thanks to the
// `assert_align_gt_eq` above.
//
// We use this reexport of `core::mem::transmute` because we know it
// will always be available for crates which are using the 2015
// edition of Rust. By contrast, if we were to use
// `std::mem::transmute`, this macro would not work for such crates
// in `no_std` contexts, and if we were to use
// `core::mem::transmute`, this macro would not work in `std`
// contexts in which `core` was not manually imported. This is not a
// problem for 2018 edition crates.
unsafe {
// Clippy: It's okay to transmute a type to itself.
#[allow(clippy::useless_transmute)]
$crate::macro_util::core_reexport::mem::transmute(e)
}
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `Src: FromBytes + AsBytes` and `Dst: FromBytes +
// AsBytes` thanks to the uses of `AssertSrcIsFromBytes`,
// `AssertSrcIsAsBytes`, `AssertDstIsFromBytes`, and
// `AssertDstIsAsBytes` above.
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
// the use of `assert_align_gt_eq!` above.
unsafe { $crate::macro_util::transmute_mut(e) }
}
}}
}
Expand Down Expand Up @@ -3479,6 +3471,8 @@ mod alloc_support {

#[cfg(test)]
mod tests {
use core::convert::TryFrom as _;

use super::*;

#[test]
Expand Down Expand Up @@ -3654,7 +3648,7 @@ pub use alloc_support::*;
mod tests {
#![allow(clippy::unreadable_literal)]

use core::ops::Deref;
use core::{convert::TryInto as _, ops::Deref};

use static_assertions::assert_impl_all;

Expand Down
60 changes: 60 additions & 0 deletions src/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,66 @@ macro_rules! assert_size_eq {
}};
}

/// Transmutes a reference of one type to a reference of another type.
///
/// # Safety
///
/// The caller must guarantee that:
/// - `Src: AsBytes`
/// - `Dst: FromBytes`
/// - `size_of::<Src>() == size_of::<Dst>()`
/// - `align_of::<Src>() >= align_of::<Dst>()`
#[inline(always)]
pub const unsafe fn transmute_ref<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
src: &'src Src,
) -> &'dst Dst {
let src: *const Src = src;
let dst = src.cast::<Dst>();
// SAFETY:
// - We know that it is sound to view the target type of the input reference
// (`Src`) as the target type of the output reference (`Dst`) because the
// caller has guaranteed that `Src: AsBytes`, `Dst: FromBytes`, and
// `size_of::<Src>() == size_of::<Dst>()`.
// - We know that there are no `UnsafeCell`s, and thus we don't have to
// worry about `UnsafeCell` overlap, because `Src: AsBytes` and `Dst:
// FromBytes` both forbid `UnsafeCell`s.
// - The caller has guaranteed that alignment is not increased.
// - We know that the returned lifetime will not outlive the input lifetime
// thanks to the lifetime bounds on this function.
unsafe { &*dst }
}

/// Transmutes a mutable reference of one type to a mutable reference of another
/// type.
///
/// # Safety
///
/// The caller must guarantee that:
/// - `Src: FromBytes + AsBytes`
/// - `Dst: FromBytes + AsBytes`
/// - `size_of::<Src>() == size_of::<Dst>()`
/// - `align_of::<Src>() >= align_of::<Dst>()`
#[inline(always)]
pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
src: &'src mut Src,
) -> &'dst mut Dst {
let src: *mut Src = src;
let dst = src.cast::<Dst>();
// SAFETY:
// - We know that it is sound to view the target type of the input reference
// (`Src`) as the target type of the output reference (`Dst`) and
// vice-versa because the caller has guaranteed that `Src: FromBytes +
// AsBytes`, `Dst: FromBytes + AsBytes`, and `size_of::<Src>() ==
// size_of::<Dst>()`.
// - We know that there are no `UnsafeCell`s, and thus we don't have to
// worry about `UnsafeCell` overlap, because `Src: FromBytes + AsBytes`
// and `Dst: FromBytes + AsBytes` forbid `UnsafeCell`s.
// - The caller has guaranteed that alignment is not increased.
// - We know that the returned lifetime will not outlive the input lifetime
// thanks to the lifetime bounds on this function.
unsafe { &mut *dst }
}

pub mod core_reexport {
pub mod mem {
pub use core::mem::transmute;
Expand Down
4 changes: 2 additions & 2 deletions tests/ui-msrv/include_value_not_from_bytes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ error[E0277]: the trait bound `UnsafeCell<u32>: FromBytes` is not satisfied
12 | include_value!("../../testdata/include_value/data");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `UnsafeCell<u32>`
|
note: required by a bound in `NOT_FROM_BYTES::transmute`
note: required by a bound in `AssertIsFromBytes`
--> tests/ui-msrv/include_value_not_from_bytes.rs:12:5
|
12 | include_value!("../../testdata/include_value/data");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `NOT_FROM_BYTES::transmute`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
= note: this error originates in the macro `$crate::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
4 changes: 2 additions & 2 deletions tests/ui-msrv/transmute-dst-not-frombytes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ error[E0277]: the trait bound `NotZerocopy: FromBytes` is not satisfied
18 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `NotZerocopy`
|
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
note: required by a bound in `AssertIsFromBytes`
--> tests/ui-msrv/transmute-dst-not-frombytes.rs:18:41
|
18 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `DST_NOT_FROM_BYTES::transmute`
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion tests/ui-msrv/transmute-mut-alignment-increase.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ error[E0658]: mutable references are not allowed in constants
|
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information

error[E0015]: cannot call non-const fn `INCREASE_ALIGNMENT::transmute::<[u8; 2], AU16>` in constants
error[E0015]: cannot call non-const fn `transmute_mut::<[u8; 2], AU16>` in constants
--> tests/ui-msrv/transmute-mut-alignment-increase.rs:19:39
|
19 | const INCREASE_ALIGNMENT: &mut AU16 = transmute_mut!(&mut [0u8; 2]);
Expand Down
2 changes: 1 addition & 1 deletion tests/ui-msrv/transmute-mut-const.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ error[E0658]: mutable references are not allowed in constants
|
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information

error[E0015]: cannot call non-const fn `CONST_CONTEXT::transmute::<[u8; 2], [u8; 2]>` in constants
error[E0015]: cannot call non-const fn `transmute_mut::<[u8; 2], [u8; 2]>` in constants
--> tests/ui-msrv/transmute-mut-const.rs:20:37
|
20 | const CONST_CONTEXT: &mut [u8; 2] = transmute_mut!(&mut ARRAY_OF_U8S);
Expand Down
20 changes: 20 additions & 0 deletions tests/ui-msrv/transmute-mut-dst-not-a-reference.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,23 @@ error[E0308]: mismatched types
= note: expected type `usize`
found mutable reference `&mut _`
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
--> tests/ui-msrv/transmute-mut-dst-not-a-reference.rs:17:36
|
17 | const DST_NOT_A_REFERENCE: usize = transmute_mut!(&mut 0u8);
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&mut _`
|
= note: expected type `usize`
found mutable reference `&mut _`
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
--> tests/ui-msrv/transmute-mut-dst-not-a-reference.rs:17:36
|
17 | const DST_NOT_A_REFERENCE: usize = transmute_mut!(&mut 0u8);
| ^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `&mut _`
|
= note: expected type `usize`
found mutable reference `&mut _`
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
7 changes: 2 additions & 5 deletions tests/ui-msrv/transmute-mut-dst-not-asbytes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ error[E0277]: the trait bound `Dst: AsBytes` is not satisfied
24 | const DST_NOT_AS_BYTES: &mut Dst = transmute_mut!(&mut Src);
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsBytes` is not implemented for `Dst`
|
note: required by a bound in `DST_NOT_AS_BYTES::transmute`
note: required by a bound in `AssertDstIsAsBytes`
--> tests/ui-msrv/transmute-mut-dst-not-asbytes.rs:24:36
|
24 | const DST_NOT_AS_BYTES: &mut Dst = transmute_mut!(&mut Src);
| ^^^^^^^^^^^^^^^^^^^^^^^^
| |
| required by a bound in this
| required by this bound in `DST_NOT_AS_BYTES::transmute`
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertDstIsAsBytes`
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
7 changes: 2 additions & 5 deletions tests/ui-msrv/transmute-mut-dst-not-frombytes.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ error[E0277]: the trait bound `Dst: FromBytes` is not satisfied
24 | const DST_NOT_FROM_BYTES: &mut Dst = transmute_mut!(&mut Src);
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromBytes` is not implemented for `Dst`
|
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
note: required by a bound in `AssertDstIsFromBytes`
--> tests/ui-msrv/transmute-mut-dst-not-frombytes.rs:24:38
|
24 | const DST_NOT_FROM_BYTES: &mut Dst = transmute_mut!(&mut Src);
| ^^^^^^^^^^^^^^^^^^^^^^^^
| |
| required by a bound in this
| required by this bound in `DST_NOT_FROM_BYTES::transmute`
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertDstIsFromBytes`
= note: this error originates in the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)
Loading

0 comments on commit 4e14a22

Please sign in to comment.