From be7c02c298282dcb741be2f9d3971591b2dd4838 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 31 Oct 2023 00:55:02 +0000 Subject: [PATCH] Lower MSRV to 1.56 In order to do this, we make a few changes: - In 1.56, panicking is not supported in `const fn`s. In order to support 1.56 while still panicking in `const fn`s in later versions, we make a few changes: - We introduce a `build.rs` script which detects the Rust toolchain version and emits the cfg `zerocopy_panic_in_const_fn` if the Rust version is at least 1.57. - In some `const fn`s, we put debug assertions behind `#[cfg(zerocopy_panic_in_const_fn)]`. - In some `const fn`s, we replace `unreachable!()` with non-panicking code when `#[cfg(not(zerocopy_panic_in_const_fn))]`. - In one case, we compile a method as either a `const fn` or a non-`const fn` depending on `zerocopy_panic_in_const_fn`. - We replace `&*dst` with `transmute(dst)`. - We make sure that, in `impl` blocks, `const` parameters always come last. - We make the `byteorder` feature/crate dependency off by default. This is a breaking change, and so we bump the version number to 0.8.0-alpha. Release 0.8.0-alpha. Makes progress on https://github.com/google/zerocopy/issues/554 --- Cargo.toml | 12 +- build.rs | 12 + src/lib.rs | 418 ++++++++++++++-------------- src/macro_util.rs | 4 +- src/macros.rs | 14 +- src/third_party/libc/LICENSE-APACHE | 177 ++++++++++++ src/third_party/libc/LICENSE-MIT | 26 ++ src/third_party/libc/README.fuchsia | 7 + src/third_party/libc/build.rs | 44 +++ src/third_party/rust/layout.rs | 1 + src/util.rs | 36 ++- src/wrappers.rs | 5 +- tests/trybuild.rs | 4 +- testutil/Cargo.toml | 2 +- testutil/src/lib.rs | 11 +- zerocopy-derive/Cargo.toml | 6 +- 16 files changed, 549 insertions(+), 230 deletions(-) create mode 100644 build.rs create mode 100644 src/third_party/libc/LICENSE-APACHE create mode 100644 src/third_party/libc/LICENSE-MIT create mode 100644 src/third_party/libc/README.fuchsia create mode 100644 src/third_party/libc/build.rs diff --git a/Cargo.toml b/Cargo.toml index f3b5459873..468e558522 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,12 @@ [package] edition = "2018" name = "zerocopy" -version = "0.7.21" +version = "0.8.0-alpha" authors = ["Joshua Liebow-Feeser "] 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.60.0" +rust-version = "1.56.0" exclude = [".*"] @@ -36,8 +36,6 @@ pinned-nightly = "nightly-2023-10-31" features = ["__internal_use_only_features_that_work_on_stable"] [features] -default = ["byteorder"] - alloc = [] derive = ["zerocopy-derive"] simd = [] @@ -48,7 +46,7 @@ simd-nightly = ["simd"] __internal_use_only_features_that_work_on_stable = ["alloc", "derive", "simd"] [dependencies] -zerocopy-derive = { version = "=0.7.21", path = "zerocopy-derive", optional = true } +zerocopy-derive = { version = "=0.8.0-alpha", path = "zerocopy-derive", optional = true } [dependencies.byteorder] version = "1.3" @@ -59,7 +57,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.21", path = "zerocopy-derive" } +zerocopy-derive = { version = "=0.8.0-alpha", path = "zerocopy-derive" } [dev-dependencies] assert_matches = "1.5" @@ -74,6 +72,6 @@ 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.21", path = "zerocopy-derive" } +zerocopy-derive = { version = "=0.8.0-alpha", path = "zerocopy-derive" } # TODO(#381) Remove this dependency once we have our own layout gadgets. elain = "0.3.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..b93d484a96 --- /dev/null +++ b/build.rs @@ -0,0 +1,12 @@ +#[path = "src/third_party/libc/build.rs"] +pub(crate) mod libc; + +fn main() { + // Avoid unnecessary re-building. + println!("cargo:rerun-if-changed=build.rs"); + + let (minor, _nightly) = libc::rustc_minor_nightly(); + if minor >= 57 { + println!("cargo:rustc-cfg=zerocopy_panic_in_const_fn"); + } +} diff --git a/src/lib.rs b/src/lib.rs index c42584d7dc..5534adec31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -393,10 +393,7 @@ impl DstLayout { // sound to initialize `size_info` to `SizeInfo::Sized { size }`; the // `size` field is also correct by construction. DstLayout { - _align: match NonZeroUsize::new(mem::align_of::()) { - Some(align) => align, - None => unreachable!(), - }, + _align: util::_nonzero_align_of::(), _size_info: SizeInfo::Sized { _size: mem::size_of::() }, } } @@ -406,6 +403,8 @@ impl DstLayout { /// # Safety /// /// Unsafe code may assume that `DstLayout` is the correct layout for `[T]`. + #[doc(hidden)] + #[inline] const fn for_slice() -> DstLayout { // SAFETY: The alignment of a slice is equal to the alignment of its // element type, and so `align` is initialized correctly. @@ -416,10 +415,7 @@ impl DstLayout { // construction. Since `[T]` is a (degenerate case of a) slice DST, it // is correct to initialize `size_info` to `SizeInfo::SliceDst`. DstLayout { - _align: match NonZeroUsize::new(mem::align_of::()) { - Some(align) => align, - None => unreachable!(), - }, + _align: util::_nonzero_align_of::(), _size_info: SizeInfo::SliceDst(TrailingSliceLayout { _offset: 0, _elem_size: mem::size_of::(), @@ -427,212 +423,222 @@ impl DstLayout { } } - /// Validates that a cast is sound from a layout perspective. - /// - /// Validates that the size and alignment requirements of a type with the - /// layout described in `self` would not be violated by performing a - /// `cast_type` cast from a pointer with address `addr` which refers to a - /// memory region of size `bytes_len`. - /// - /// If the cast is valid, `validate_cast_and_convert_metadata` returns - /// `(elems, split_at)`. If `self` describes a dynamically-sized type, then - /// `elems` is the maximum number of trailing slice elements for which a - /// cast would be valid (for sized types, `elem` is meaningless and should - /// be ignored). `split_at` is the index at which to split the memory region - /// in order for the prefix (suffix) to contain the result of the cast, and - /// in order for the remaining suffix (prefix) to contain the leftover - /// bytes. - /// - /// There are three conditions under which a cast can fail: - /// - The smallest possible value for the type is larger than the provided - /// memory region - /// - A prefix cast is requested, and `addr` does not satisfy `self`'s - /// alignment requirement - /// - A suffix cast is requested, and `addr + bytes_len` does not satisfy - /// `self`'s alignment requirement (as a consequence, since all instances - /// of the type are a multiple of its alignment, no size for the type will - /// result in a starting address which is properly aligned) - /// - /// # Safety - /// - /// The caller may assume that this implementation is correct, and may rely - /// on that assumption for the soundness of their code. In particular, the - /// caller may assume that, if `validate_cast_and_convert_metadata` returns - /// `Some((elems, split_at))`, then: - /// - A pointer to the type (for dynamically sized types, this includes - /// `elems` as its pointer metadata) describes an object of size `size <= - /// bytes_len` - /// - If this is a prefix cast: - /// - `addr` satisfies `self`'s alignment - /// - `size == split_at` - /// - If this is a suffix cast: - /// - `split_at == bytes_len - size` - /// - `addr + split_at` satisfies `self`'s alignment - /// - /// Note that this method does *not* ensure that a pointer constructed from - /// its return values will be a valid pointer. In particular, this method - /// does not reason about `isize` overflow, which is a requirement of many - /// Rust pointer APIs, and may at some point be determined to be a validity - /// invariant of pointer types themselves. This should never be a problem so - /// long as the arguments to this method are derived from a known-valid - /// pointer (e.g., one derived from a safe Rust reference), but it is - /// nonetheless the caller's responsibility to justify that pointer - /// arithmetic will not overflow based on a safety argument *other than* the - /// mere fact that this method returned successfully. - /// - /// # Panics - /// - /// `validate_cast_and_convert_metadata` will panic if `self` describes a - /// DST whose trailing slice element is zero-sized. - /// - /// If `addr + bytes_len` overflows `usize`, - /// `validate_cast_and_convert_metadata` may panic, or it may return - /// incorrect results. No guarantees are made about when - /// `validate_cast_and_convert_metadata` will panic. The caller should not - /// rely on `validate_cast_and_convert_metadata` panicking in any particular - /// condition, even if `debug_assertions` are enabled. - const fn _validate_cast_and_convert_metadata( - &self, - addr: usize, - bytes_len: usize, - cast_type: _CastType, - ) -> Option<(usize, usize)> { - // `debug_assert!`, but with `#[allow(clippy::arithmetic_side_effects)]`. - macro_rules! __debug_assert { - ($e:expr $(, $msg:expr)?) => { - debug_assert!({ - #[allow(clippy::arithmetic_side_effects)] - let e = $e; - e - } $(, $msg)?); - }; - } - - // Note that, in practice, `self` is always a compile-time constant. We - // do this check earlier than needed to ensure that we always panic as a - // result of bugs in the program (such as calling this function on an - // invalid type) instead of allowing this panic to be hidden if the cast - // would have failed anyway for runtime reasons (such as a too-small - // memory region). - // - // TODO(#67): Once our MSRV is 1.65, use let-else: - // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements - let size_info = match self._size_info._try_to_nonzero_elem_size() { - Some(size_info) => size_info, - None => panic!("attempted to cast to slice type with zero-sized element"), - }; - - // Precondition - __debug_assert!(addr.checked_add(bytes_len).is_some(), "`addr` + `bytes_len` > usize::MAX"); + maybe_const_panicking_fn! { + /// Validates that a cast is sound from a layout perspective. + /// + /// Validates that the size and alignment requirements of a type with + /// the layout described in `self` would not be violated by performing a + /// `cast_type` cast from a pointer with address `addr` which refers to + /// a memory region of size `bytes_len`. + /// + /// If the cast is valid, `validate_cast_and_convert_metadata` returns + /// `(elems, split_at)`. If `self` describes a dynamically-sized type, + /// then `elems` is the maximum number of trailing slice elements for + /// which a cast would be valid (for sized types, `elem` is meaningless + /// and should be ignored). `split_at` is the index at which to split + /// the memory region in order for the prefix (suffix) to contain the + /// result of the cast, and in order for the remaining suffix (prefix) + /// to contain the leftover bytes. + /// + /// There are three conditions under which a cast can fail: + /// - The smallest possible value for the type is larger than the + /// provided memory region + /// - A prefix cast is requested, and `addr` does not satisfy `self`'s + /// alignment requirement + /// - A suffix cast is requested, and `addr + bytes_len` does not + /// satisfy `self`'s alignment requirement (as a consequence, since + /// all instances of the type are a multiple of its alignment, no size + /// for the type will result in a starting address which is properly + /// aligned) + /// + /// # Safety + /// + /// The caller may assume that this implementation is correct, and may + /// rely on that assumption for the soundness of their code. In + /// particular, the caller may assume that, if + /// `validate_cast_and_convert_metadata` returns `Some((elems, + /// split_at))`, then: + /// - A pointer to the type (for dynamically sized types, this includes + /// `elems` as its pointer metadata) describes an object of size `size + /// <= bytes_len` + /// - If this is a prefix cast: + /// - `addr` satisfies `self`'s alignment + /// - `size == split_at` + /// - If this is a suffix cast: + /// - `split_at == bytes_len - size` + /// - `addr + split_at` satisfies `self`'s alignment + /// + /// Note that this method does *not* ensure that a pointer constructed + /// from its return values will be a valid pointer. In particular, this + /// method does not reason about `isize` overflow, which is a + /// requirement of many Rust pointer APIs, and may at some point be + /// determined to be a validity invariant of pointer types themselves. + /// This should never be a problem so long as the arguments to this + /// method are derived from a known-valid pointer (e.g., one derived + /// from a safe Rust reference), but it is nonetheless the caller's + /// responsibility to justify that pointer arithmetic will not overflow + /// based on a safety argument *other than* the mere fact that this + /// method returned successfully. + /// + /// # Panics + /// + /// `validate_cast_and_convert_metadata` will panic if `self` describes + /// a DST whose trailing slice element is zero-sized. + /// + /// If `addr + bytes_len` overflows `usize`, + /// `validate_cast_and_convert_metadata` may panic, or it may return + /// incorrect results. No guarantees are made about when + /// `validate_cast_and_convert_metadata` will panic. The caller should + /// not rely on `validate_cast_and_convert_metadata` panicking in any + /// particular condition, even if `debug_assertions` are enabled. + #[doc(hidden)] + #[inline] + const fn _validate_cast_and_convert_metadata( + &self, + addr: usize, + bytes_len: usize, + cast_type: _CastType, + ) -> Option<(usize, usize)> { + // `debug_assert!`, but with + // `#[allow(clippy::arithmetic_side_effects)]`. + macro_rules! __debug_assert { + ($e:expr $(, $msg:expr)?) => { + debug_assert!({ + #[allow(clippy::arithmetic_side_effects)] + let e = $e; + e + } $(, $msg)?); + }; + } - // Alignment checks go in their own block to avoid introducing variables - // into the top-level scope. - { - // We check alignment for `addr` (for prefix casts) or `addr + - // bytes_len` (for suffix casts). For a prefix cast, the correctness - // of this check is trivial - `addr` is the address the object will - // live at. + // Note that, in practice, `self` is always a compile-time constant. + // We do this check earlier than needed to ensure that we always + // panic as a result of bugs in the program (such as calling this + // function on an invalid type) instead of allowing this panic to be + // hidden if the cast would have failed anyway for runtime reasons + // (such as a too-small memory region). // - // For a suffix cast, we know that all valid sizes for the type are - // a multiple of the alignment (and by safety precondition, we know - // `DstLayout` may only describe valid Rust types). Thus, a - // validly-sized instance which lives at a validly-aligned address - // must also end at a validly-aligned address. Thus, if the end - // address for a suffix cast (`addr + bytes_len`) is not aligned, - // then no valid start address will be aligned either. - let offset = match cast_type { - _CastType::_Prefix => 0, - _CastType::_Suffix => bytes_len, + // TODO(#67): Once our MSRV is 1.65, use let-else: + // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements + let size_info = match self._size_info._try_to_nonzero_elem_size() { + Some(size_info) => size_info, + None => panic!("attempted to cast to slice type with zero-sized element"), }; - // Addition is guaranteed not to overflow because `offset <= - // bytes_len`, and `addr + bytes_len <= usize::MAX` is a - // precondition of this method. Modulus is guaranteed not to divide - // by 0 because `align` is non-zero. - #[allow(clippy::arithmetic_side_effects)] - if (addr + offset) % self._align.get() != 0 { - return None; - } - } + // Precondition + __debug_assert!(addr.checked_add(bytes_len).is_some(), "`addr` + `bytes_len` > usize::MAX"); - let (elems, self_bytes) = match size_info { - SizeInfo::Sized { _size: size } => { - if size > bytes_len { - return None; - } - (0, size) - } - SizeInfo::SliceDst(TrailingSliceLayout { _offset: offset, _elem_size: elem_size }) => { - // Calculate the maximum number of bytes that could be consumed - // - any number of bytes larger than this will either not be a - // multiple of the alignment, or will be larger than - // `bytes_len`. - let max_total_bytes = - util::_round_down_to_next_multiple_of_alignment(bytes_len, self._align); - // Calculate the maximum number of bytes that could be consumed - // by the trailing slice. + // Alignment checks go in their own block to avoid introducing + // variables into the top-level scope. + { + // We check alignment for `addr` (for prefix casts) or `addr + + // bytes_len` (for suffix casts). For a prefix cast, the + // correctness of this check is trivial - `addr` is the address + // the object will live at. // - // TODO(#67): Once our MSRV is 1.65, use let-else: - // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements - let max_slice_and_padding_bytes = match max_total_bytes.checked_sub(offset) { - Some(max) => max, - // `bytes_len` too small even for 0 trailing slice elements. - None => return None, + // For a suffix cast, we know that all valid sizes for the type + // are a multiple of the alignment (and by safety precondition, + // we know `DstLayout` may only describe valid Rust types). + // Thus, a validly-sized instance which lives at a + // validly-aligned address must also end at a validly-aligned + // address. Thus, if the end address for a suffix cast (`addr + + // bytes_len`) is not aligned, then no valid start address will + // be aligned either. + let offset = match cast_type { + _CastType::_Prefix => 0, + _CastType::_Suffix => bytes_len, }; - // Calculate the number of elements that fit in - // `max_slice_and_padding_bytes`; any remaining bytes will be - // considered padding. - // - // Guaranteed not to divide by zero: `elem_size` is non-zero. - #[allow(clippy::arithmetic_side_effects)] - let elems = max_slice_and_padding_bytes / elem_size.get(); - // Guaranteed not to overflow on multiplication: `usize::MAX >= - // max_slice_and_padding_bytes >= (max_slice_and_padding_bytes / - // elem_size) * elem_size`. - // - // Guaranteed not to overflow on addition: - // - max_slice_and_padding_bytes == max_total_bytes - offset - // - elems * elem_size <= max_slice_and_padding_bytes == max_total_bytes - offset - // - elems * elem_size + offset <= max_total_bytes <= usize::MAX - #[allow(clippy::arithmetic_side_effects)] - let without_padding = offset + elems * elem_size.get(); - // `self_bytes` is equal to the offset bytes plus the bytes - // consumed by the trailing slice plus any padding bytes - // required to satisfy the alignment. Note that we have computed - // the maximum number of trailing slice elements that could fit - // in `self_bytes`, so any padding is guaranteed to be less than - // the size of an extra element. - // - // Guaranteed not to overflow: - // - By previous comment: without_padding == elems * elem_size + - // offset <= max_total_bytes - // - By construction, `max_total_bytes` is a multiple of - // `self._align`. - // - At most, adding padding needed to round `without_padding` - // up to the next multiple of the alignment will bring - // `self_bytes` up to `max_total_bytes`. + // Addition is guaranteed not to overflow because `offset <= + // bytes_len`, and `addr + bytes_len <= usize::MAX` is a + // precondition of this method. Modulus is guaranteed not to + // divide by 0 because `align` is non-zero. #[allow(clippy::arithmetic_side_effects)] - let self_bytes = without_padding - + util::core_layout::_padding_needed_for(without_padding, self._align); - (elems, self_bytes) + if (addr + offset) % self._align.get() != 0 { + return None; + } } - }; - __debug_assert!(self_bytes <= bytes_len); + let (elems, self_bytes) = match size_info { + SizeInfo::Sized { _size: size } => { + if size > bytes_len { + return None; + } + (0, size) + } + SizeInfo::SliceDst(TrailingSliceLayout { _offset: offset, _elem_size: elem_size }) => { + // Calculate the maximum number of bytes that could be + // consumed - any number of bytes larger than this will + // either not be a multiple of the alignment, or will be + // larger than `bytes_len`. + let max_total_bytes = + util::_round_down_to_next_multiple_of_alignment(bytes_len, self._align); + // Calculate the maximum number of bytes that could be + // consumed by the trailing slice. + // + // TODO(#67): Once our MSRV is 1.65, use let-else: + // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#let-else-statements + let max_slice_and_padding_bytes = match max_total_bytes.checked_sub(offset) { + Some(max) => max, + // `bytes_len` too small even for 0 trailing slice elements. + None => return None, + }; - let split_at = match cast_type { - _CastType::_Prefix => self_bytes, - // Guaranteed not to underflow: - // - In the `Sized` branch, only returns `size` if `size <= - // bytes_len`. - // - In the `SliceDst` branch, calculates `self_bytes <= - // max_toatl_bytes`, which is upper-bounded by `bytes_len`. - #[allow(clippy::arithmetic_side_effects)] - _CastType::_Suffix => bytes_len - self_bytes, - }; + // Calculate the number of elements that fit in + // `max_slice_and_padding_bytes`; any remaining bytes will be + // considered padding. + // + // Guaranteed not to divide by zero: `elem_size` is non-zero. + #[allow(clippy::arithmetic_side_effects)] + let elems = max_slice_and_padding_bytes / elem_size.get(); + // Guaranteed not to overflow on multiplication: `usize::MAX + // >= max_slice_and_padding_bytes >= + // (max_slice_and_padding_bytes / elem_size) * elem_size`. + // + // Guaranteed not to overflow on addition: + // - max_slice_and_padding_bytes == max_total_bytes - offset + // - elems * elem_size <= max_slice_and_padding_bytes == max_total_bytes - offset + // - elems * elem_size + offset <= max_total_bytes <= usize::MAX + #[allow(clippy::arithmetic_side_effects)] + let without_padding = offset + elems * elem_size.get(); + // `self_bytes` is equal to the offset bytes plus the bytes + // consumed by the trailing slice plus any padding bytes + // required to satisfy the alignment. Note that we have + // computed the maximum number of trailing slice elements + // that could fit in `self_bytes`, so any padding is + // guaranteed to be less than the size of an extra element. + // + // Guaranteed not to overflow: + // - By previous comment: without_padding == elems * + // elem_size + offset <= max_total_bytes + // - By construction, `max_total_bytes` is a multiple of + // `self._align`. + // - At most, adding padding needed to round + // `without_padding` up to the next multiple of the + // alignment will bring `self_bytes` up to + // `max_total_bytes`. + #[allow(clippy::arithmetic_side_effects)] + let self_bytes = without_padding + + util::core_layout::_padding_needed_for(without_padding, self._align); + (elems, self_bytes) + } + }; - Some((elems, split_at)) + __debug_assert!(self_bytes <= bytes_len); + + let split_at = match cast_type { + _CastType::_Prefix => self_bytes, + // Guaranteed not to underflow: + // - In the `Sized` branch, only returns `size` if `size <= + // bytes_len`. + // - In the `SliceDst` branch, calculates `self_bytes <= + // max_toatl_bytes`, which is upper-bounded by `bytes_len`. + #[allow(clippy::arithmetic_side_effects)] + _CastType::_Suffix => bytes_len - self_bytes, + }; + + Some((elems, split_at)) + } } } @@ -3774,7 +3780,8 @@ mod tests { assert_matches::assert_matches!( actual, $expect, - "layout({size_info:?}, {align}).validate_cast_and_convert_metadata({addr}, {bytes_len}, {cast_type:?})", + "layout({:?}, {}).validate_cast_and_convert_metadata({}, {}, {:?})", + size_info, align, addr, bytes_len, cast_type ); }); }; @@ -3869,7 +3876,8 @@ mod tests { { let (size_info, align) = (layout._size_info, layout._align); let debug_str = format!( - "layout({size_info:?}, {align}).validate_cast_and_convert_metadata({addr}, {bytes_len}, {cast_type:?}) => ({elems}, {split_at})", + "layout({:?}, {}).validate_cast_and_convert_metadata({}, {}, {:?}) => ({}, {})", + size_info, align, addr, bytes_len, cast_type, elems, split_at ); // If this is a sized type (no trailing slice), then `elems` is @@ -4450,7 +4458,11 @@ mod tests { const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7]; const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]]; #[allow(clippy::redundant_static_lifetimes)] + #[cfg(zerocopy_panic_in_const_fn)] const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S); + #[allow(clippy::redundant_static_lifetimes, non_snake_case)] + #[cfg(not(zerocopy_panic_in_const_fn))] + let X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S); assert_eq!(*X, ARRAY_OF_ARRAYS); // Test that it's legal to transmute a reference while shrinking the diff --git a/src/macro_util.rs b/src/macro_util.rs index 574cb2b17a..4b18147f2e 100644 --- a/src/macro_util.rs +++ b/src/macro_util.rs @@ -363,7 +363,9 @@ pub const unsafe fn transmute_ref<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>( // - 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 } + // + // TODO(#67): Once our MSRV is 1.58, replace this `transmute` with `&*dst`. + unsafe { core::mem::transmute(dst) } } /// Transmutes a mutable reference of one type to a mutable reference of another diff --git a/src/macros.rs b/src/macros.rs index 7d30033bd4..40cd8cf7f8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -95,7 +95,7 @@ macro_rules! unsafe_impl { $($tyvar:ident $(: $(? $optbound:ident +)* + $($bound:ident +)* )?,)* => $trait:ident for $ty:ty ) => { - unsafe impl<$(const $constname: $constty,)* $($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> $trait for $ty { + unsafe impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?),* $(, const $constname: $constty,)*> $trait for $ty { #[allow(clippy::missing_inline_in_public_items)] fn only_derive_is_allowed_to_implement_this_trait() {} } @@ -212,7 +212,7 @@ macro_rules! impl_known_layout { use core::ptr::NonNull; // SAFETY: Delegates safety to `DstLayout::for_type`. - unsafe impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> KnownLayout for $ty { + unsafe impl<$($tyvar $(: ?$optbound)?)? $(, const $constvar : $constty)?> KnownLayout for $ty { #[allow(clippy::missing_inline_in_public_items)] fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {} @@ -287,3 +287,13 @@ macro_rules! assert_unaligned { $(assert_unaligned!($ty);)* }; } + +macro_rules! maybe_const_panicking_fn { + ($(#[$attr:meta])* $vis:vis const fn $name:ident($(&$self:ident)? $(,)? $($args:ident: $arg_tys:ty),* $(,)?) $(-> $ret_ty:ty)? $body:block) => { + #[cfg(zerocopy_panic_in_const_fn)] + $(#[$attr])* $vis const fn $name($(&$self,)? $($args: $arg_tys),*) $(-> $ret_ty)? $body + + #[cfg(not(zerocopy_panic_in_const_fn))] + $(#[$attr])* $vis fn $name($(&$self,)? $($args: $arg_tys),*) $(-> $ret_ty)? $body + }; +} diff --git a/src/third_party/libc/LICENSE-APACHE b/src/third_party/libc/LICENSE-APACHE new file mode 100644 index 0000000000..d085c25804 --- /dev/null +++ b/src/third_party/libc/LICENSE-APACHE @@ -0,0 +1,177 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + diff --git a/src/third_party/libc/LICENSE-MIT b/src/third_party/libc/LICENSE-MIT new file mode 100644 index 0000000000..d7a194930a --- /dev/null +++ b/src/third_party/libc/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2014-2020 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + diff --git a/src/third_party/libc/README.fuchsia b/src/third_party/libc/README.fuchsia new file mode 100644 index 0000000000..d8f52d3a6e --- /dev/null +++ b/src/third_party/libc/README.fuchsia @@ -0,0 +1,7 @@ +Name: libc +License File: LICENSE-APACHE +License File: LICENSE-MIT +Description: + +See https://github.com/google/zerocopy/pull/492 for an explanation of why this +file exists. diff --git a/src/third_party/libc/build.rs b/src/third_party/libc/build.rs new file mode 100644 index 0000000000..a3583f49e7 --- /dev/null +++ b/src/third_party/libc/build.rs @@ -0,0 +1,44 @@ +use std::env; +use std::process::Command; +use std::str; +use std::string::String; + +pub(crate) fn rustc_minor_nightly() -> (u32, bool) { + macro_rules! otry { + ($e:expr) => { + match $e { + Some(e) => e, + None => panic!("Failed to get rustc version"), + } + }; + } + + let rustc = otry!(env::var_os("RUSTC")); + let output = + Command::new(rustc).arg("--version").output().ok().expect("Failed to get rustc version"); + if !output.status.success() { + panic!("failed to run rustc: {}", String::from_utf8_lossy(output.stderr.as_slice())); + } + + let version = otry!(str::from_utf8(&output.stdout).ok()); + let mut pieces = version.split('.'); + + if pieces.next() != Some("rustc 1") { + panic!("Failed to get rustc version"); + } + + let minor = pieces.next(); + + // If `rustc` was built from a tarball, its version string + // will have neither a git hash nor a commit date + // (e.g. "rustc 1.39.0"). Treat this case as non-nightly, + // since a nightly build should either come from CI + // or a git checkout + let nightly_raw = otry!(pieces.next()).split('-').nth(1); + let nightly = nightly_raw + .map(|raw| raw.starts_with("dev") || raw.starts_with("nightly")) + .unwrap_or(false); + let minor = otry!(otry!(minor).parse().ok()); + + (minor, nightly) +} diff --git a/src/third_party/rust/layout.rs b/src/third_party/rust/layout.rs index 92366a5799..79888c781b 100644 --- a/src/third_party/rust/layout.rs +++ b/src/third_party/rust/layout.rs @@ -38,6 +38,7 @@ pub(crate) const fn _padding_needed_for(len: usize, align: NonZeroUsize) -> usiz // the allocator to yield an error anyway.) let align = align.get(); + #[cfg(zerocopy_panic_in_const_fn)] debug_assert!(align.is_power_of_two()); let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1); len_rounded_up.wrapping_sub(len) diff --git a/src/util.rs b/src/util.rs index 91f54aec7f..a0d90bbc03 100644 --- a/src/util.rs +++ b/src/util.rs @@ -313,7 +313,7 @@ pub(crate) mod ptr { // - If `size_of::() == 0`, `N == 4` // - Else, `N == 4 * size_of::()` - fn test() { + fn test() { let mut bytes = [MaybeUninit::::uninit(); N]; let initialized = [MaybeUninit::new(0u8); N]; for start in 0..=bytes.len() { @@ -421,11 +421,11 @@ pub(crate) mod ptr { $({ const S: usize = core::mem::size_of::<$ty>(); const N: usize = if S == 0 { 4 } else { S * 4 }; - test::(); + test::<$ty, N>(); // We don't support casting into DSTs whose trailing slice // element is a ZST. if S > 0 { - test::(); + test::<[$ty], N>(); } // TODO: Test with a slice DST once we have any that // implement `KnownLayout + FromBytes`. @@ -504,6 +504,7 @@ pub(crate) const fn _round_down_to_next_multiple_of_alignment( align: NonZeroUsize, ) -> usize { let align = align.get(); + #[cfg(zerocopy_panic_in_const_fn)] debug_assert!(align.is_power_of_two()); // Subtraction can't underflow because `align.get() >= 1`. @@ -512,6 +513,33 @@ pub(crate) const fn _round_down_to_next_multiple_of_alignment( n & mask } +/// Returns the alignment of `T` as a [`NonZeroUsize`]. +#[inline(always)] +pub(crate) const fn _nonzero_align_of() -> NonZeroUsize { + match NonZeroUsize::new(mem::align_of::()) { + Some(align) => align, + None => { + #[cfg(zerocopy_panic_in_const_fn)] + unreachable!(); + // SAFETY: `mem::align_of` returns the alignment of `T`, which is + // guaranteed to be non-zero. [1] Since `unaligned_unchecked` is not + // const-stable as of our MSRV, we use `mem::transmute(0usize)` as a + // stand-in. + // + // [1] Per + // https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment: + // + // Alignment is measured in bytes, and must be at least 1, and + // always a power of 2. + #[cfg(not(zerocopy_panic_in_const_fn))] + #[allow(invalid_value)] + unsafe { + mem::transmute(0usize) + } + } + } +} + /// Since we support multiple versions of Rust, there are often features which /// have been stabilized in the most recent stable release which do not yet /// exist (stably) on our MSRV. This module provides polyfills for those @@ -628,7 +656,7 @@ mod tests { let align = NonZeroUsize::new(align).unwrap(); let want = alt_impl(n, align); let got = _round_down_to_next_multiple_of_alignment(n, align); - assert_eq!(got, want, "round_down_to_next_multiple_of_alignment({n}, {align})"); + assert_eq!(got, want, "round_down_to_next_multiple_of_alignment({}, {})", n, align); } } } diff --git a/src/wrappers.rs b/src/wrappers.rs index 532d872978..eaca316fcb 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -471,8 +471,9 @@ mod tests { // Make sure that `deref_unchecked` is `const`. // // SAFETY: The `Align<_, AU64>` guarantees proper alignment. - let au64 = unsafe { x.t.deref_unchecked() }; - match au64 { + let _au64 = unsafe { x.t.deref_unchecked() }; + #[cfg(zerocopy_panic_in_const_fn)] + match _au64 { AU64(123) => {} _ => unreachable!(), } diff --git a/tests/trybuild.rs b/tests/trybuild.rs index 24abc28622..46edbeb8e9 100644 --- a/tests/trybuild.rs +++ b/tests/trybuild.rs @@ -17,7 +17,7 @@ fn ui() { let source_files_dirname = version.get_ui_source_files_dirname_and_maybe_print_warning(); let t = trybuild::TestCases::new(); - t.compile_fail(format!("tests/{source_files_dirname}/*.rs")); + t.compile_fail(format!("tests/{}/*.rs", source_files_dirname)); } // The file `invalid-impls.rs` directly includes `src/macros.rs` in order to @@ -37,5 +37,5 @@ fn ui_invalid_impls() { let source_files_dirname = version.get_ui_source_files_dirname_and_maybe_print_warning(); let t = trybuild::TestCases::new(); - t.compile_fail(format!("tests/{source_files_dirname}/invalid-impls/*.rs")); + t.compile_fail(format!("tests/{}/invalid-impls/*.rs", source_files_dirname)); } diff --git a/testutil/Cargo.toml b/testutil/Cargo.toml index 4b715f9699..7219052632 100644 --- a/testutil/Cargo.toml +++ b/testutil/Cargo.toml @@ -9,7 +9,7 @@ [package] name = "testutil" version = "0.0.0" -edition = "2021" +edition = "2018" [dependencies] cargo_metadata = "0.18.0" diff --git a/testutil/src/lib.rs b/testutil/src/lib.rs index f9a50b9985..27b8b4cb2f 100644 --- a/testutil/src/lib.rs +++ b/testutil/src/lib.rs @@ -40,10 +40,10 @@ impl PinnedVersions { .ok_or("failed to find msrv: no `rust-version` key present")? .to_string(); let extract = |version_name, key| -> Result { - let value = pkg.metadata.pointer(&format!("/ci/{key}")).ok_or_else(|| { - format!("failed to find {version_name}: no `metadata.ci.{key}` key present") + let value = pkg.metadata.pointer(&format!("/ci/{}", key)).ok_or_else(|| { + format!("failed to find {}: no `metadata.ci.{}` key present", version_name, key) })?; - value.as_str().map(str::to_string).ok_or_else(|| format!("failed to find {version_name}: key `metadata.ci.{key}` (contents: {value:?}) failed to parse as JSON string")) + value.as_str().map(str::to_string).ok_or_else(|| format!("failed to find {}: key `metadata.ci.{}` (contents: {:?}) failed to parse as JSON string", version_name, key, value)) }; let stable = extract("stable", "pinned-stable")?; let nightly = extract("nightly", "pinned-nightly")?; @@ -85,7 +85,7 @@ impl ToolchainVersion { } Channel::Stable => { let Version { major, minor, patch, .. } = current.semver; - format!("{major}.{minor}.{patch}") + format!("{}.{}.{}", major, minor, patch) } }; @@ -116,7 +116,8 @@ impl ToolchainVersion { _ if current.channel == Channel::Nightly => ToolchainVersion::OtherNightly, _ => { return Err(format!( - "current toolchain ({current:?}) doesn't match any known version" + "current toolchain ({:?}) doesn't match any known version", + current ) .into()) } diff --git a/zerocopy-derive/Cargo.toml b/zerocopy-derive/Cargo.toml index 861c5f9be8..ed31cbd86a 100644 --- a/zerocopy-derive/Cargo.toml +++ b/zerocopy-derive/Cargo.toml @@ -9,12 +9,12 @@ [package] edition = "2018" name = "zerocopy-derive" -version = "0.7.21" +version = "0.8.0-alpha" authors = ["Joshua Liebow-Feeser "] description = "Custom derive for traits from the zerocopy crate" license = "BSD-2-Clause OR Apache-2.0 OR MIT" repository = "https://github.com/google/zerocopy" -rust-version = "1.60.0" +rust-version = "1.56.0" exclude = [".*"] @@ -34,4 +34,4 @@ testutil = { path = "../testutil" } # sometimes change the output format slightly, so a version mismatch can cause # CI test failures. trybuild = { version = "=1.0.85", features = ["diff"] } -zerocopy = { path = "../", features = ["default", "derive"] } +zerocopy = { path = "../", features = ["derive"] }