Skip to content

Commit

Permalink
Lower MSRV to 1.56
Browse files Browse the repository at this point in the history
Makes progress on #554
  • Loading branch information
joshlf committed Feb 9, 2024
1 parent 3974be4 commit 8775f70
Show file tree
Hide file tree
Showing 23 changed files with 230 additions and 183 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
matrix:
# See `INTERNAL.md` for an explanation of these pinned toolchain
# versions.
toolchain: [ "msrv", "stable", "nightly", "zerocopy-aarch64-simd", "zerocopy-generic-bounds-in-const-fn" ]
toolchain: [ "msrv", "stable", "nightly", "zerocopy-generic-bounds-in-const-fn", "zerocopy-aarch64-simd", "zerocopy-panic-in-const", ]
target: [
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
Expand All @@ -59,10 +59,13 @@ jobs:
features: "--all-features"
- toolchain: "stable"
features: "--all-features"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
features: "--all-features"
- toolchain: "zerocopy-aarch64-simd"
features: "--all-features"
- toolchain: "zerocopy-generic-bounds-in-const-fn"
- toolchain: "zerocopy-panic-in-const"
features: "--all-features"

# Exclude any combination for the zerocopy-derive crate which
# uses zerocopy features.
- crate: "zerocopy-derive"
Expand All @@ -75,10 +78,12 @@ jobs:
# other than "msrv", "stable", and "nightly". These other versions
# exist to exercise zerocopy behavior which differs by toolchain;
# zerocopy-derive doesn't behave different on these toolchains.
- crate: "zerocopy-derive"
toolchain: "zerocopy-generic-bounds-in-const-fn"
- crate: "zerocopy-derive"
toolchain: "zerocopy-aarch64-simd"
- crate: "zerocopy-derive"
toolchain: "zerocopy-generic-bounds-in-const-fn"
toolchain: "zerocopy-panic-in-const"

name: Build & Test (crate:${{ matrix.crate }}, toolchain:${{ matrix.toolchain }}, target:${{ matrix.target }}, features:${{ matrix.features }})

Expand Down
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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.57.0"
rust-version = "1.56.0"

exclude = [".*"]

Expand All @@ -30,13 +30,16 @@ exclude = [".*"]
# as high as the specified version. In the emitted `--cfg`, dashes are replaced
# by underscores.

# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn = "1.61.0"

# When the "simd" feature is enabled, include SIMD types from the
# `core::arch::aarch64` module, which was stabilized in 1.59.0. On earlier Rust
# versions, these types require the "simd-nightly" feature.
zerocopy-aarch64-simd = "1.59.0"

# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn = "1.61.0"
# Permit panicking in `const fn`s.
zerocopy-panic-in-const = "1.57.0"

[package.metadata.ci]
# The versions of the stable and nightly compiler toolchains to use in CI.
Expand Down
84 changes: 49 additions & 35 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,13 +363,15 @@ pub struct DstLayout {
size_info: SizeInfo,
}

#[cfg_attr(any(kani, test), derive(Copy, Clone, Debug, PartialEq, Eq))]
#[cfg_attr(any(kani, test), derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
enum SizeInfo<E = usize> {
Sized { _size: usize },
SliceDst(TrailingSliceLayout<E>),
}

#[cfg_attr(any(kani, test), derive(Copy, Clone, Debug, PartialEq, Eq))]
#[cfg_attr(any(kani, test), derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
struct TrailingSliceLayout<E = usize> {
// The offset of the first byte of the trailing slice field. Note that this
// is NOT the same as the minimum size of the type. For example, consider
Expand Down Expand Up @@ -419,7 +421,7 @@ impl DstLayout {
/// The minimum possible alignment of a type.
const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) {
Some(min_align) => min_align,
None => unreachable!(),
None => const_unreachable!(),
};

/// The maximum theoretic possible alignment of a type.
Expand All @@ -430,7 +432,7 @@ impl DstLayout {
const THEORETICAL_MAX_ALIGN: NonZeroUsize =
match NonZeroUsize::new(1 << (POINTER_WIDTH_BITS - 1)) {
Some(max_align) => max_align,
None => unreachable!(),
None => const_unreachable!(),
};

/// The current, documented max alignment of a type \[1\].
Expand All @@ -442,7 +444,7 @@ impl DstLayout {
#[cfg(not(kani))]
const CURRENT_MAX_ALIGN: NonZeroUsize = match NonZeroUsize::new(1 << 28) {
Some(max_align) => max_align,
None => unreachable!(),
None => const_unreachable!(),
};

/// Constructs a `DstLayout` for a zero-sized type with `repr_align`
Expand All @@ -464,7 +466,7 @@ impl DstLayout {
None => Self::MIN_ALIGN,
};

assert!(align.get().is_power_of_two());
const_assert!(align.get().is_power_of_two());

DstLayout { align, size_info: SizeInfo::Sized { _size: 0 } }
}
Expand All @@ -483,7 +485,7 @@ impl DstLayout {
DstLayout {
align: match NonZeroUsize::new(mem::align_of::<T>()) {
Some(align) => align,
None => unreachable!(),
None => const_unreachable!(),
},
size_info: SizeInfo::Sized { _size: mem::size_of::<T>() },
}
Expand All @@ -506,7 +508,7 @@ impl DstLayout {
DstLayout {
align: match NonZeroUsize::new(mem::align_of::<T>()) {
Some(align) => align,
None => unreachable!(),
None => const_unreachable!(),
},
size_info: SizeInfo::SliceDst(TrailingSliceLayout {
_offset: 0,
Expand Down Expand Up @@ -552,17 +554,17 @@ impl DstLayout {
None => Self::THEORETICAL_MAX_ALIGN,
};

assert!(max_align.get().is_power_of_two());
const_assert!(max_align.get().is_power_of_two());

// We use Kani to prove that this method is robust to future increases
// in Rust's maximum allowed alignment. However, if such a change ever
// actually occurs, we'd like to be notified via assertion failures.
#[cfg(not(kani))]
{
debug_assert!(self.align.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
debug_assert!(field.align.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
const_debug_assert!(self.align.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
const_debug_assert!(field.align.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
if let Some(repr_packed) = repr_packed {
debug_assert!(repr_packed.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
const_debug_assert!(repr_packed.get() <= DstLayout::CURRENT_MAX_ALIGN.get());
}
}

Expand All @@ -583,7 +585,7 @@ impl DstLayout {
let size_info = match self.size_info {
// If the layout is already a DST, we panic; DSTs cannot be extended
// with additional fields.
SizeInfo::SliceDst(..) => panic!("Cannot extend a DST with additional fields."),
SizeInfo::SliceDst(..) => const_panic!("Cannot extend a DST with additional fields."),

SizeInfo::Sized { _size: preceding_size } => {
// Compute the minimum amount of inter-field padding needed to
Expand All @@ -604,7 +606,7 @@ impl DstLayout {
// exceeding `isize::MAX`).
let offset = match preceding_size.checked_add(padding) {
Some(offset) => offset,
None => panic!("Adding padding to `self`'s size overflows `usize`."),
None => const_panic!("Adding padding to `self`'s size overflows `usize`."),
};

match field.size_info {
Expand All @@ -622,7 +624,7 @@ impl DstLayout {
// `usize::MAX`).
let size = match offset.checked_add(field_size) {
Some(size) => size,
None => panic!("`field` cannot be appended without the total size overflowing `usize`"),
None => const_panic!("`field` cannot be appended without the total size overflowing `usize`"),
};
SizeInfo::Sized { _size: size }
}
Expand All @@ -644,7 +646,7 @@ impl DstLayout {
// `usize::MAX`).
let offset = match offset.checked_add(trailing_offset) {
Some(offset) => offset,
None => panic!("`field` cannot be appended without the total size overflowing `usize`"),
None => const_panic!("`field` cannot be appended without the total size overflowing `usize`"),
};
SizeInfo::SliceDst(TrailingSliceLayout { _offset: offset, _elem_size })
}
Expand Down Expand Up @@ -691,7 +693,7 @@ impl DstLayout {
let padding = padding_needed_for(unpadded_size, self.align);
let size = match unpadded_size.checked_add(padding) {
Some(size) => size,
None => panic!("Adding padding caused size to overflow `usize`."),
None => const_panic!("Adding padding caused size to overflow `usize`."),
};
SizeInfo::Sized { _size: size }
}
Expand Down Expand Up @@ -777,9 +779,9 @@ impl DstLayout {
cast_type: _CastType,
) -> Option<(usize, usize)> {
// `debug_assert!`, but with `#[allow(clippy::arithmetic_side_effects)]`.
macro_rules! __debug_assert {
macro_rules! __const_debug_assert {
($e:expr $(, $msg:expr)?) => {
debug_assert!({
const_debug_assert!({
#[allow(clippy::arithmetic_side_effects)]
let e = $e;
e
Expand All @@ -798,11 +800,14 @@ impl DstLayout {
// 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"),
None => const_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");
__const_debug_assert!(
addr.checked_add(bytes_len).is_some(),
"`addr` + `bytes_len` > usize::MAX"
);

// Alignment checks go in their own block to avoid introducing variables
// into the top-level scope.
Expand Down Expand Up @@ -898,7 +903,7 @@ impl DstLayout {
}
};

__debug_assert!(self_bytes <= bytes_len);
__const_debug_assert!(self_bytes <= bytes_len);

let split_at = match cast_type {
_CastType::_Prefix => self_bytes,
Expand Down Expand Up @@ -3951,7 +3956,7 @@ macro_rules! transmute_ref {

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&T`) as `&t`.
let mut t = unreachable!();
let mut t = loop {};
e = &t;

// `u` is inferred to have type `U` because it's used as `&u` as the
Expand Down Expand Up @@ -4096,7 +4101,7 @@ macro_rules! transmute_mut {

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&mut T`) as `&mut t`.
let mut t = unreachable!();
let mut t = loop {};
e = &mut t;

// `u` is inferred to have type `U` because it's used as `&mut u` as
Expand Down Expand Up @@ -5324,7 +5329,7 @@ impl<'a> sealed::ByteSliceSealed for cell::Ref<'a, [u8]> {}
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for cell::Ref<'a, [u8]> {
const INTO_REF_INTO_MUT_ARE_SOUND: bool = if !cfg!(doc) {
panic!("Ref::into_ref and Ref::into_mut are unsound when used with core::cell::Ref; see https://github.com/google/zerocopy/issues/716")
const_panic!("Ref::into_ref and Ref::into_mut are unsound when used with core::cell::Ref; see https://github.com/google/zerocopy/issues/716")
} else {
// When compiling documentation, allow the evaluation of this constant
// to succeed. This doesn't represent a soundness hole - it just delays
Expand All @@ -5344,7 +5349,7 @@ impl<'a> sealed::ByteSliceSealed for RefMut<'a, [u8]> {}
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for RefMut<'a, [u8]> {
const INTO_REF_INTO_MUT_ARE_SOUND: bool = if !cfg!(doc) {
panic!("Ref::into_ref and Ref::into_mut are unsound when used with core::cell::RefMut; see https://github.com/google/zerocopy/issues/716")
const_panic!("Ref::into_ref and Ref::into_mut are unsound when used with core::cell::RefMut; see https://github.com/google/zerocopy/issues/716")
} else {
// When compiling documentation, allow the evaluation of this constant
// to succeed. This doesn't represent a soundness hole - it just delays
Expand Down Expand Up @@ -5810,7 +5815,7 @@ mod tests {
// attempt to expose UB.
#[test]
#[cfg_attr(miri, ignore)]
fn testvalidate_cast_and_convert_metadata() {
fn test_validate_cast_and_convert_metadata() {
impl From<usize> for SizeInfo {
fn from(_size: usize) -> SizeInfo {
SizeInfo::Sized { _size }
Expand Down Expand Up @@ -5855,9 +5860,16 @@ mod tests {
/// - If it is `Ok(pat)`, then the pattern `pat` is supplied to
/// `assert_matches!` to validate the computed result for each
/// combination of input values.
/// - If it is `Err(msg)`, then `test!` validates that the call to
/// `validate_cast_and_convert_metadata` panics with the given panic
/// message.
/// - If it is `Err(Some(msg) | None)`, then `test!` validates that the
/// call to `validate_cast_and_convert_metadata` panics with the given
/// panic message or, if the current Rust toolchain version is too
/// early to support panicking in `const fn`s, panics with *some*
/// message. In the latter case, the `const_panic!` macro is used,
/// which emits code which causes a non-panicking error at const eval
/// time, but which does panic when invoked at runtime. Thus, it is
/// merely difficult to predict the *value* of this panic. We deem
/// that testing against the real panic strings on stable and nightly
/// toolchains is enough to ensure correctness.
///
/// Note that the meta-variables that match these variables have the
/// `tt` type, and some valid expressions are not valid `tt`s (such as
Expand Down Expand Up @@ -5889,7 +5901,9 @@ mod tests {
let actual = std::panic::catch_unwind(|| {
layout(size_info, align).validate_cast_and_convert_metadata(addr, bytes_len, cast_type)
}).map_err(|d| {
*d.downcast::<&'static str>().expect("expected string panic message").as_ref()
let msg = d.downcast::<&'static str>().ok().map(|s| *s.as_ref());
assert!(msg.is_some() || cfg!(zerocopy_panic_in_const), "non-string panic messages are only permitted when `--cfg zerocopy_panic_in_const`");
msg
});
std::panic::set_hook(previous_hook);

Expand Down Expand Up @@ -5965,18 +5979,18 @@ mod tests {
}

// casts with ZST trailing element types are unsupported
test!(layout((_, [0]), _).validate(_, _, _), Err(msgs::TRAILING),);
test!(layout((_, [0]), _).validate(_, _, _), Err(Some(msgs::TRAILING) | None),);

// addr + bytes_len must not overflow usize
test!(layout(_, _).validate([usize::MAX], (1..100), _), Err(msgs::OVERFLOW));
test!(layout(_, _).validate((1..100), [usize::MAX], _), Err(msgs::OVERFLOW));
test!(layout(_, _).validate([usize::MAX], (1..100), _), Err(Some(msgs::OVERFLOW) | None));
test!(layout(_, _).validate((1..100), [usize::MAX], _), Err(Some(msgs::OVERFLOW) | None));
test!(
layout(_, _).validate(
[usize::MAX / 2 + 1, usize::MAX],
[usize::MAX / 2 + 1, usize::MAX],
_
),
Err(msgs::OVERFLOW)
Err(Some(msgs::OVERFLOW) | None)
);

// Validates that `validate_cast_and_convert_metadata` satisfies its own
Expand Down
Loading

0 comments on commit 8775f70

Please sign in to comment.