diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..314a1e92a0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..fb91526242 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,130 @@ +name: Build & Tests + +on: + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + build_test: + runs-on: ubuntu-latest + + strategy: + matrix: + # See `INTERNAL.md` for an explanation of these pinned toolchain + # versions. + channel: [ "1.56.1", "1.64.0", "nightly-2022-09-26" ] + target: [ "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "arm-unknown-linux-gnueabi", "aarch64-unknown-linux-gnu", "powerpc-unknown-linux-gnu", "powerpc64-unknown-linux-gnu", "wasm32-wasi" ] + features: [ "" , "alloc,simd", "alloc,simd,simd-nightly" ] + exclude: + # Exclude any combination which uses a non-nightly toolchain but + # enables nightly features. + - channel: "1.56.1" + features: "alloc,simd,simd-nightly" + - channel: "1.64.0" + features: "alloc,simd,simd-nightly" + + name: Build & Test (${{ matrix.channel }} for ${{ matrix.target }}, features set to "${{ matrix.features }}") + + steps: + - uses: actions/checkout@v3 + + - name: Install Rust with toolchain ${{ matrix.channel }} and target ${{ matrix.target }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.channel }} + target: ${{ matrix.target }} + # Only nightly has a working Miri, so we skip installing on all other + # toolchains. This expression is effectively a ternary expression - + # see [1] for details. + # + # [1] + # https://github.com/actions/runner/issues/409#issuecomment-752775072 + components: ${{ contains(matrix.channel, 'nightly') && 'miri' || '' }} + + - name: Rust Cache + uses: Swatinem/rust-cache@v2.0.0 + with: + key: "${{ matrix.channel }}-${{ matrix.target }}-${{ matrix.features }}-${{ hashFiles('**/Cargo.lock') }}" + + - name: Check + run: cargo +${{ matrix.channel }} check --target ${{ matrix.target }} --features "${{ matrix.features }}" --verbose + + - name: Check zerocopy-derive + run: cargo +${{ matrix.channel }} check --manifest-path ./zerocopy-derive/Cargo.toml --target ${{ matrix.target }} --verbose + # Don't bother to check `zerocopy-derive` multiple times; that's what + # would happen if we ran this step once for each set of `zerocopy` + # features. + if: ${{ matrix.features == '' }} + + - name: Build + run: cargo +${{ matrix.channel }} build --target ${{ matrix.target }} --features "${{ matrix.features }}" --verbose + + - name: Build zerocopy-derive + run: cargo +${{ matrix.channel }} build --manifest-path ./zerocopy-derive/Cargo.toml --target ${{ matrix.target }} --verbose + # Don't bother to build `zerocopy-derive` multiple times; that's what + # would happen if we ran this step once for each set of `zerocopy` + # features. + if: ${{ matrix.features == '' }} + + # When building tests for the i686 target, we need certain libraries which + # are not installed by default; `gcc-multilib` includes these libraries. + - name: Install gcc-multilib + run: sudo apt-get install gcc-multilib + if: ${{ contains(matrix.target, 'i686') }} + + - name: Run tests + run: cargo +${{ matrix.channel }} test --target ${{ matrix.target }} --features "${{ matrix.features }}" --verbose + # Only run tests when targetting x86 (32- or 64-bit) - we're executing on + # x86_64, so we can't run tests for any non-x86 target. + if: ${{ contains(matrix.target, 'x86_64') || contains(matrix.target, 'i686') }} + + - name: Run zerocopy-derive tests + run: cargo +${{ matrix.channel }} test --manifest-path ./zerocopy-derive/Cargo.toml --target ${{ matrix.target }} --verbose + # Don't bother to test `zerocopy-derive` multiple times; that's what would + # happen if we ran this step once for each set of `zerocopy` features. + # Also, only run tests when targetting x86 (32- or 64-bit) - we're + # executing on x86_64, so we can't run tests for any non-x86 target. + # + # TODO(https://github.com/dtolnay/trybuild/issues/184#issuecomment-1269097742): + # Run compile tests when building for other targets. + if: ${{ matrix.features == '' && (contains(matrix.target, 'x86_64') || contains(matrix.target, 'i686')) }} + + - name: Run tests under Miri + # Skip the `ui` test since it invokes the compiler, which we can't do from + # Miri (and wouldn't want to do anyway). + # + run: cargo +${{ matrix.channel }} miri test --target ${{ matrix.target }} --features "${{ matrix.features }}" -- --skip ui + # Only nightly has a working Miri, so we skip installing on all other + # toolchains. + # + # TODO(#22): Re-enable testing on wasm32-wasi once it works. + if: ${{ contains(matrix.channel, 'nightly') && matrix.target != 'wasm32-wasi' }} + + check_fmt: + runs-on: ubuntu-latest + name: Check Rust formatting + steps: + - uses: actions/checkout@v3 + - name: Check Rust formatting + run: | + set -e + cargo fmt --check + cargo fmt --check --manifest-path ./zerocopy-derive/Cargo.toml + rustfmt --check ./zerocopy-derive/tests/ui/*.rs + + check_readme: + runs-on: ubuntu-latest + name: Check README.md + steps: + - uses: actions/checkout@v3 + # Cache the `cargo-readme` installation. + - name: Rust Cache + uses: Swatinem/rust-cache@v2.0.0 + - name: Check README.md + run: | + set -e + cargo install cargo-readme --version 3.2.0 + diff <(./generate-readme.sh) README.md + exit $? diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2fc9ee9c5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Copyright 2022 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +target +Cargo.lock \ No newline at end of file diff --git a/BUILD.gn b/BUILD.gn deleted file mode 100644 index ce7fb4e523..0000000000 --- a/BUILD.gn +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2018 The Fuchsia Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//build/rust/rustc_library.gni") -import("//build/test/test_package.gni") - -rustc_library("zerocopy") { - name = "zerocopy" - version = "0.1.0" - edition = "2018" - - with_unit_tests = true - - deps = [ - "//src/lib/zerocopy/zerocopy-derive", - "//third_party/rust_crates:byteorder", - "//third_party/rust_crates:rand", - ] -} - -unittest_package("zerocopy_tests") { - deps = [ ":zerocopy_test" ] - - tests = [ - { - name = "zerocopy_lib_test" - environments = basic_envs - }, - ] -} - -group("tests") { - testonly = true - - deps = [ ":zerocopy_tests" ] -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..3bfd4024ba --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,207 @@ + + +# How to Contribute + +We'd love to accept your patches and contributions to zerocopy. There are just a +few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code Reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult [GitHub +Help][about_pull_requests] for more information on using pull requests. + +## Code Guidelines + +### Philosophy + +This section is inspired by [Flutter's style guide][flutter_philosophy], which +contains many general principles that you should apply to all your programming +work. Read it. The below calls out specific aspects that we feel are +particularly important. + +#### Dogfood Your Features + +In non-library code, it's often advised to only implement features you need. +After all, it's hard to correctly design code without a concrete use case to +guide its design. Since zerocopy is a library, this advice is not as applicable; +we want our API surface to be featureful and complete even if not every feature +or method has a known use case. However, the observation that unused code is +hard to design still holds. + +Thus, when designing external-facing features, try to make use of them somehow. +This could be by using them to implement other features, or it could be by +writing prototype code which won't actually be checked in anywhere. If you're +feeling ambitious, you could even add (and check in) a [Cargo +example][cargo_example] that exercises the new feature. + +#### Go Down the Rabbit Hole + +You will occasionally encounter behavior that surprises you or seems wrong. It +probably is! Invest the time to find the root cause - you will either learn +something, or fix something, and both are worth your time. Do not work around +behavior you don't understand. + +### Avoid Duplication + +Avoid duplicating code whenever possible. In cases where existing code is not +exposed in a manner suitable to your needs, prefer to extract the necessary +parts into a common dependency. + +### Comments + +When writing comments, take a moment to consider the future reader of your +comment. Ensure that your comments are complete sentences with proper grammar +and punctuation. Note that adding more comments or more verbose comments is not +always better; for example, avoid comments that repeat the code they're anchored +on. + +Documentation comments should be self-contained; in other words, do not assume +that the reader is aware of documentation in adjacent files or on adjacent +structures. Avoid documentation comments on types which describe _instances_ of +the type; for example, `AddressSet is a set of client addresses.` is a comment +that describes a field of type `AddressSet`, but the type may be used to hold +any kind of `Address`, not just a client's. + +Phrase your comments to avoid references that might become stale; for example: +do not mention a variable or type by name when possible (certain doc comments +are necessary exceptions). Also avoid references to past or future versions of +or past or future work surrounding the item being documented; explain things +from first principles rather than making external references (including past +revisions). + +When writing TODOs: + +1. Include an issue reference using the format `TODO(#123):` +1. Phrase the text as an action that is to be taken; it should be possible for + another contributor to pick up the TODO without consulting any external + sources, including the referenced issue. + +### Tests + +Much of the code in zerocopy has the property that, if it is buggy, those bugs +may not cause user code to fail. This makes it extra important to write thorough +tests, but it also makes it harder to write those tests correctly. Here are some +guidelines on how to test code in zerocopy: +1. All code added to zerocopy must include tests that exercise it completely. +1. Tests must be deterministic. Threaded or time-dependent code, random number + generators (RNGs), and communication with external processes are common + sources of nondeterminism. See [Write reproducible, deterministic + tests][determinism] for tips. +1. Avoid [change detector tests][change_detector_tests]; tests that are + unnecessarily sensitive to changes, especially ones external to the code + under test, can hamper feature development and refactoring. +1. Since we run tests in [Miri][miri], make sure that tests exist which exercise + any potential [undefined behavior][undefined_behavior] so that Miri can catch + it. +1. If there's some user code that should be impossible to compile, add a + [compile-test][compile_test] to ensure that it's properly rejected. + +### Source Control Best Practices + +Commits should be arranged for ease of reading; that is, incidental changes +such as code movement or formatting changes should be committed separately from +actual code changes. + +Commits should always be focused. For example, a commit could add a feature, +fix a bug, or refactor code, but not a mixture. + +Commits should be thoughtfully sized; avoid overly large or complex commits +which can be logically separated, but also avoid overly separated commits that +require code reviews to load multiple commits into their mental working memory +in order to properly understand how the various pieces fit together. + +#### Commit Messages + +Commit messages should be _concise_ but self-contained (avoid relying on issue +references as explanations for changes) and written such that they are helpful +to people reading in the future (include rationale and any necessary context). + +Avoid superfluous details or narrative. + +Commit messages should consist of a brief subject line and a separate +explanatory paragraph in accordance with the following: + +1. [Separate subject from body with a blank line](https://chris.beams.io/posts/git-commit/#separate) +1. [Limit the subject line to 50 characters](https://chris.beams.io/posts/git-commit/#limit-50) +1. [Capitalize the subject line](https://chris.beams.io/posts/git-commit/#capitalize) +1. [Do not end the subject line with a period](https://chris.beams.io/posts/git-commit/#end) +1. [Use the imperative mood in the subject line](https://chris.beams.io/posts/git-commit/#imperative) +1. [Wrap the body at 72 characters](https://chris.beams.io/posts/git-commit/#wrap-72) +1. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how) + +If the code affects a particular subsystem, prefix the subject line with the +name of that subsystem in square brackets, omitting any "zerocopy" prefix +(that's implicit). For example, for a commit adding a feature to the +zerocopy-derive crate: + +```text +[derive] Support AsBytes on types with parameters +``` + +The body may be omitted if the subject is self-explanatory; e.g. when fixing a +typo. The git book contains a [Commit Guidelines][commit_guidelines] section +with much of the same advice, and the list above is part of a [blog +post][beams_git_commit] by [Chris Beams][chris_beams]. + +Commit messages should make use of issue integration. Including an issue +reference like `#123` will cause the GitHub UI to link the text of that +reference to the referenced issue, and will also make it so that the referenced +issue back-links to the commit. Use "Closes", "Fixes", or "Resolves" on its own +line to automatically close an issue when your commit is merged: + +```text +Closes #123 +Fixes #123 +Resolves #123 +``` + +When using issue integration, don't omit necessary context that may also be +included in the relevant issue (see "Commit messages should be _concise_ but +self-contained" above). Git history is more likely to be retained indefinitely +than issue history (for example, if this repository is migrated away from GitHub +at some point in the future). + +Commit messages should never contain references to any of: + +1. Relative moments in time +1. Non-public URLs +1. Individuals +1. Hosted code reviews (such as on https://github.com/google/zerocopy/pulls) + + Refer to commits in this repository by their SHA-1 hash + + Refer to commits in other repositories by public web address (such as + https://github.com/google/zerocopy/commit/789b3deb) +1. Other entities which may not make sense to arbitrary future readers + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines][google_open_source_guidelines]. + +[magic_number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[miri]: https://github.com/rust-lang/miri +[cargo_example]: http://xion.io/post/code/rust-examples.html +[commit_guidelines]: https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines +[compile_test]: https://crates.io/crates/compiletest_rs +[flutter_philosophy]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#philosophy +[change_detector_tests]: https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html +[determinism]: https://fuchsia.dev/fuchsia-src/contribute/testing/best-practices#write_reproducible_deterministic_tests +[undefined_behavior]: https://raphlinus.github.io/programming/rust/2018/08/17/undefined-behavior.html +[about_pull_requests]: https://help.github.com/articles/about-pull-requests/ +[beams_git_commit]: https://chris.beams.io/posts/git-commit/ +[chris_beams]: https://chris.beams.io/ +[google_open_source_guidelines]: https://opensource.google/conduct/ diff --git a/Cargo.toml.crates-io b/Cargo.toml similarity index 61% rename from Cargo.toml.crates-io rename to Cargo.toml index 9e91c1b144..76aea47fcb 100644 --- a/Cargo.toml.crates-io +++ b/Cargo.toml @@ -7,16 +7,24 @@ [package] edition = "2018" name = "zerocopy" -version = "0.3.0" +version = "0.7.0-alpha" authors = ["Joshua Liebow-Feeser "] description = "Utilities for zero-copy parsing and serialization" -license = "BSD-3-Clause" -repository = "https://fuchsia.googlesource.com/fuchsia/+/master/src/lib/zerocopy" +license-file = "LICENSE" +repository = "https://github.com/google/zerocopy" -include = ["src/*", "Cargo.toml", "LICENSE"] +exclude = [".*"] + +[package.metadata.docs.rs] +all-features = true + +[features] +alloc = [] +simd = [] +simd-nightly = ["simd"] [dependencies] -zerocopy-derive = { version = "0.2.0" } +zerocopy-derive = { version = "0.3.1", path = "zerocopy-derive" } [dependencies.byteorder] version = "1.3" @@ -24,3 +32,4 @@ default-features = false [dev-dependencies] rand = "0.6" +trybuild = "1.0" diff --git a/INTERNAL.md b/INTERNAL.md new file mode 100644 index 0000000000..aa6076d6b9 --- /dev/null +++ b/INTERNAL.md @@ -0,0 +1,22 @@ +# Internal details + +This file documents various internal details of zerocopy and its infrastructure +that consumers don't need to be concerned about. It focuses on details that +affect multiple files, and allows each affected code location to reference this +document rather than requiring us to repeat the same explanation in multiple +locations. + +## CI and toolchain versions + +In CI (`.github/workflows/ci.yml`), we pin to specific versions or dates of the +stable and nightly toolchains. The reason is twofold: First, `zerocopy-derive`'s +UI tests (see `zerocopy-derive/tests/trybuild.rs`) depend on the format of +rustc's error messages, and that format can change between toolchain versions +(we also maintain multiple copies of our UI tests - one for each toolchain +version pinned in CI - for this reason). Second, not all nightlies have a +working Miri, so we need to pin to one that does (see +https://rust-lang.github.io/rustup-components-history/). + +Updating the versions pinned in CI may cause the UI tests to break. In order to +fix UI tests after a version update, set the environment variable +`TRYBUILD=overwrite` while running `cargo test`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7ed244f42d --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright 2019 The Fuchsia Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/OWNERS b/OWNERS deleted file mode 100644 index d66f727d30..0000000000 --- a/OWNERS +++ /dev/null @@ -1 +0,0 @@ -joshlf@google.com diff --git a/README.md b/README.md new file mode 100644 index 0000000000..c34e33733f --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ + + +# zerocopy + +Utilities for safe zero-copy parsing and serialization. + +This crate provides utilities which make it easy to perform zero-copy +parsing and serialization by allowing zero-copy conversion to/from byte +slices. + +This is enabled by three core marker traits, each of which can be derived +(e.g., `#[derive(FromBytes)]`): +- `FromBytes` indicates that a type may safely be converted from an + arbitrary byte sequence +- `AsBytes` indicates that a type may safely be converted *to* a byte + sequence +- `Unaligned` indicates that a type's alignment requirement is 1 + +Types which implement a subset of these traits can then be converted to/from +byte sequences with little to no runtime overhead. + +Note that these traits are ignorant of byte order. For byte order-aware +types, see the `byteorder` module. + +## Features + +`alloc`: By default, `zerocopy` is `no_std`. When the `alloc` feature is +enabled, the `alloc` crate is added as a dependency, and some +allocation-related functionality is added. + +`simd`: When the `simd` feature is enabled, `FromBytes` and `AsBytes` impls +are emitted for all stable SIMD types which exist on the target platform. +Note that the layout of SIMD types is not yet stabilized, so these impls may +be removed in the future if layout changes make them invalid. For more +information, see the Unsafe Code Guidelines Reference page on the [Layout of +packed SIMD vectors][simd-layout]. + +`simd-nightly`: Enables the `simd` feature and adds support for SIMD types +which are only available on nightly. Since these types are unstable, support +for any type may be removed at any point in the future. + +[simd-layout]: https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html + +## Dislcaimer + +Disclaimer: Zerocopy is not an officially supported Google product. diff --git a/generate-readme.sh b/generate-readme.sh new file mode 100755 index 0000000000..1479e0c326 --- /dev/null +++ b/generate-readme.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Copyright 2018 The Fuchsia Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +COPYRIGHT_HEADER=$(mktemp) +BODY=$(mktemp) +DISCLAIMER_FOOTER=$(mktemp) + +cat > $COPYRIGHT_HEADER < + +EOF + +# This uses the `cargo readme` tool, which you can install via `cargo install +# cargo-readme --version 3.2.0`. +# +# The `sed` command is used to strip code links like: +# +# /// Here is a link to [`Vec`]. +# +# These links don't work in a Markdown file, and so we remove the `[` and `]` +# characters to convert them to non-link code snippets. +cargo readme | sed 's/\[\(`[^`]*`\)]/\1/g' > $BODY + +cat > $DISCLAIMER_FOOTER < { @@ -79,6 +90,26 @@ macro_rules! impl_fmt_trait { }; } +macro_rules! impl_fmt_traits { + ($name:ident, $native:ident, "floating point number") => { + impl_fmt_trait!($name, $native, Display); + }; + ($name:ident, $native:ident, "unsigned integer") => { + impl_fmt_traits!($name, $native, @all_traits); + }; + ($name:ident, $native:ident, "signed integer") => { + impl_fmt_traits!($name, $native, @all_traits); + }; + + ($name:ident, $native:ident, @all_traits) => { + impl_fmt_trait!($name, $native, Display); + impl_fmt_trait!($name, $native, Octal); + impl_fmt_trait!($name, $native, LowerHex); + impl_fmt_trait!($name, $native, UpperHex); + impl_fmt_trait!($name, $native, Binary); + }; +} + macro_rules! doc_comment { ($x:expr, $($tt:tt)*) => { #[doc = $x] @@ -87,7 +118,7 @@ macro_rules! doc_comment { } macro_rules! define_max_value_constant { - ($name:ident, $bytes:expr, unsigned) => { + ($name:ident, $bytes:expr, "unsigned integer") => { /// The maximum value. /// /// This constant should be preferred to constructing a new value using @@ -95,22 +126,33 @@ macro_rules! define_max_value_constant { /// endianness `O` and the endianness of the platform. pub const MAX_VALUE: $name = $name([0xFFu8; $bytes], PhantomData); }; - ($name:ident, $bytes:expr, signed) => { - // We don't provide maximum and minimum value constants for signed - // values because there's no way to do it generically - it would require - // a different value depending on the value of the ByteOrder type - // parameter. Currently, one workaround would be to provide - // implementations for concrete implementations of that trait. In the - // long term, if we are ever able to make the `new` constructor a const - // fn, we could use that instead. - }; + // We don't provide maximum and minimum value constants for signed values + // and floats because there's no way to do it generically - it would require + // a different value depending on the value of the `ByteOrder` type + // parameter. Currently, one workaround would be to provide implementations + // for concrete implementations of that trait. In the long term, if we are + // ever able to make the `new` constructor a const fn, we could use that + // instead. + ($name:ident, $bytes:expr, "signed integer") => {}; + ($name:ident, $bytes:expr, "floating point number") => {}; } macro_rules! define_type { - ($article:ident, $name:ident, $native:ident, $bits:expr, $bytes:expr, $read_method:ident, $write_method:ident, $sign:ident) => { + ($article:ident, + $name:ident, + $native:ident, + $bits:expr, + $bytes:expr, + $read_method:ident, + $write_method:ident, + $number_kind:tt, + [$($larger_native:ty),*], + [$($larger_native_try:ty),*], + [$($larger_byteorder:ident),*], + [$($larger_byteorder_try:ident),*]) => { doc_comment! { - concat!("A ", stringify!($bits), "-bit ", stringify!($sign), " integer -stored in `O` byte order. + concat!("A ", stringify!($bits), "-bit ", $number_kind, + " stored in `O` byte order. `", stringify!($name), "` is like the native `", stringify!($native), "` type with two major differences: First, it has no alignment requirement (its alignment is 1). @@ -135,12 +177,18 @@ example of how it can be used for parsing UDP packets. [`FromBytes`]: crate::FromBytes [`AsBytes`]: crate::AsBytes [`Unaligned`]: crate::Unaligned"), - #[derive(FromBytes, Unaligned, Default, Copy, Clone, Eq, PartialEq, Hash)] + #[derive(FromBytes, Unaligned, Copy, Clone, Eq, PartialEq, Hash)] #[repr(transparent)] - pub struct $name([u8; $bytes], PhantomData); + pub struct $name([u8; $bytes], PhantomData); } - // TODO(joshlf): Replace this with #[derive(AsBytes)] once that derive + impl Default for $name { + fn default() -> $name { + $name::ZERO + } + } + + // TODO(#10): Replace this with `#[derive(AsBytes)]` once that derive // supports type parameters. unsafe impl AsBytes for $name { fn only_derive_is_allowed_to_implement_this_trait() @@ -150,7 +198,7 @@ example of how it can be used for parsing UDP packets. } } - impl $name { + impl $name { /// The value zero. /// /// This constant should be preferred to constructing a new value @@ -158,10 +206,18 @@ example of how it can be used for parsing UDP packets. /// on the endianness and platform. pub const ZERO: $name = $name([0u8; $bytes], PhantomData); - define_max_value_constant!($name, $bytes, $sign); + define_max_value_constant!($name, $bytes, $number_kind); - // TODO(joshlf): Make these const fns if the ByteOrder methods ever - // become const fns. + /// Constructs a new value from bytes which are already in the + /// endianness `O`. + pub const fn from_bytes(bytes: [u8; $bytes]) -> $name { + $name(bytes, PhantomData) + } + } + + impl $name { + // TODO(joshlf): Make these const fns if the `ByteOrder` methods + // ever become const fns. /// Constructs a new value, possibly performing an endianness swap /// to guarantee that the returned value has endianness `O`. @@ -186,15 +242,10 @@ example of how it can be used for parsing UDP packets. } } - // NOTE: The reasoning behind which traits to implement here is a) only - // implement traits which do not involve implicit endianness swaps and, - // b) only implement traits which won't cause inference issues. Most of - // the traits which would cause inference issues would also involve - // endianness swaps anyway (like comparison/ordering with the native - // representation or conversion from/to that representation). Note that - // we make an exception for the format traits since the cost of - // formatting dwarfs cost of performing an endianness swap, and they're - // very useful. + // The reasoning behind which traits to implement here is to only + // implement traits which won't cause inference issues. Notably, + // comparison traits like PartialEq and PartialOrd tend to cause + // inference issues. impl From<$name> for [u8; $bytes] { fn from(x: $name) -> [u8; $bytes] { @@ -208,6 +259,52 @@ example of how it can be used for parsing UDP packets. } } + impl From<$name> for $native { + fn from(x: $name) -> $native { + x.get() + } + } + + impl From<$native> for $name { + fn from(x: $native) -> $name { + $name::new(x) + } + } + + $( + impl From<$name> for $larger_native { + fn from(x: $name) -> $larger_native { + x.get().into() + } + } + )* + + $( + impl TryFrom<$larger_native_try> for $name { + type Error = TryFromIntError; + fn try_from(x: $larger_native_try) -> Result<$name, TryFromIntError> { + $native::try_from(x).map($name::new) + } + } + )* + + $( + impl From<$name> for $larger_byteorder

{ + fn from(x: $name) -> $larger_byteorder

{ + $larger_byteorder::new(x.get().into()) + } + } + )* + + $( + impl TryFrom<$larger_byteorder_try

> for $name { + type Error = TryFromIntError; + fn try_from(x: $larger_byteorder_try

) -> Result<$name, TryFromIntError> { + x.get().try_into().map($name::new) + } + } + )* + impl AsRef<[u8; $bytes]> for $name { fn as_ref(&self) -> &[u8; $bytes] { &self.0 @@ -232,39 +329,130 @@ example of how it can be used for parsing UDP packets. } } - impl_fmt_trait!($name, $native, Display); - impl_fmt_trait!($name, $native, Octal); - impl_fmt_trait!($name, $native, LowerHex); - impl_fmt_trait!($name, $native, UpperHex); - impl_fmt_trait!($name, $native, Binary); + impl_fmt_traits!($name, $native, $number_kind); impl Debug for $name { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // This results in a format like "U16(42)" - write!(f, concat!(stringify!($name), "({})"), self.get()) + // This results in a format like "U16(42)". + f.debug_tuple(stringify!($name)).field(&self.get()).finish() } } }; } -define_type!(A, U16, u16, 16, 2, read_u16, write_u16, unsigned); -define_type!(A, U32, u32, 32, 4, read_u32, write_u32, unsigned); -define_type!(A, U64, u64, 64, 8, read_u64, write_u64, unsigned); -define_type!(A, U128, u128, 128, 16, read_u128, write_u128, unsigned); -define_type!(An, I16, i16, 16, 2, read_i16, write_i16, signed); -define_type!(An, I32, i32, 32, 4, read_i32, write_i32, signed); -define_type!(An, I64, i64, 64, 8, read_i64, write_i64, signed); -define_type!(An, I128, i128, 128, 16, read_i128, write_i128, signed); +define_type!( + A, + U16, + u16, + 16, + 2, + read_u16, + write_u16, + "unsigned integer", + [u32, u64, u128, usize], + [u32, u64, u128, usize], + [U32, U64, U128], + [U32, U64, U128] +); +define_type!( + A, + U32, + u32, + 32, + 4, + read_u32, + write_u32, + "unsigned integer", + [u64, u128], + [u64, u128], + [U64, U128], + [U64, U128] +); +define_type!( + A, + U64, + u64, + 64, + 8, + read_u64, + write_u64, + "unsigned integer", + [u128], + [u128], + [U128], + [U128] +); +define_type!(A, U128, u128, 128, 16, read_u128, write_u128, "unsigned integer", [], [], [], []); +define_type!( + An, + I16, + i16, + 16, + 2, + read_i16, + write_i16, + "signed integer", + [i32, i64, i128, isize], + [i32, i64, i128, isize], + [I32, I64, I128], + [I32, I64, I128] +); +define_type!( + An, + I32, + i32, + 32, + 4, + read_i32, + write_i32, + "signed integer", + [i64, i128], + [i64, i128], + [I64, I128], + [I64, I128] +); +define_type!( + An, + I64, + i64, + 64, + 8, + read_i64, + write_i64, + "signed integer", + [i128], + [i128], + [I128], + [I128] +); +define_type!(An, I128, i128, 128, 16, read_i128, write_i128, "signed integer", [], [], [], []); +define_type!( + An, + F32, + f32, + 32, + 4, + read_f32, + write_f32, + "floating point number", + [f64], + [], + [F64], + [] +); +define_type!(An, F64, f64, 64, 8, read_f64, write_f64, "floating point number", [], [], [], []); #[cfg(test)] mod tests { use byteorder::NativeEndian; - use super::*; - use crate::{AsBytes, FromBytes, Unaligned}; + use { + super::*, + crate::{AsBytes, FromBytes, Unaligned}, + }; - // A native integer type (u16, i32, etc) - trait Native: FromBytes + AsBytes + Copy + Eq + Debug { + // A native integer type (u16, i32, etc). + trait Native: FromBytes + AsBytes + Copy + PartialEq + Debug { const ZERO: Self; const MAX_VALUE: Self; @@ -323,7 +511,7 @@ mod tests { macro_rules! impl_traits { ($name:ident, $native:ident, $bytes:expr, $sign:ident) => { impl Native for $native { - const ZERO: $native = 0; + const ZERO: $native = 0 as _; const MAX_VALUE: $native = ::core::$native::MAX; fn rand() -> $native { @@ -370,6 +558,8 @@ mod tests { impl_traits!(I32, i32, 4, signed); impl_traits!(I64, i64, 8, signed); impl_traits!(I128, i128, 16, signed); + impl_traits!(F32, f32, 4, signed); + impl_traits!(F64, f64, 8, signed); macro_rules! call_for_all_types { ($fn:ident, $byteorder:ident) => { @@ -381,6 +571,8 @@ mod tests { $fn::>(); $fn::>(); $fn::>(); + $fn::>(); + $fn::>(); }; } @@ -466,4 +658,13 @@ mod tests { call_for_all_types!(test_non_native_endian, NonNativeEndian); } + + #[test] + fn test_debug_impl() { + // Ensure that Debug applies format options to the inner value. + let val = U16::::new(10); + assert_eq!(format!("{:?}", val), "U16(10)"); + assert_eq!(format!("{:03?}", val), "U16(010)"); + assert_eq!(format!("{:x?}", val), "U16(a)"); + } } diff --git a/src/lib.rs b/src/lib.rs index f14a27be72..a30c82c470 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// After updating the following doc comment, make sure to run the following +// command to update `README.md` based on its contents: +// +// ./generate-readme.sh > README.md + //! Utilities for safe zero-copy parsing and serialization. //! //! This crate provides utilities which make it easy to perform zero-copy @@ -21,8 +26,29 @@ //! //! Note that these traits are ignorant of byte order. For byte order-aware //! types, see the [`byteorder`] module. +//! +//! # Features +//! +//! `alloc`: By default, `zerocopy` is `no_std`. When the `alloc` feature is +//! enabled, the `alloc` crate is added as a dependency, and some +//! allocation-related functionality is added. +//! +//! `simd`: When the `simd` feature is enabled, `FromBytes` and `AsBytes` impls +//! are emitted for all stable SIMD types which exist on the target platform. +//! Note that the layout of SIMD types is not yet stabilized, so these impls may +//! be removed in the future if layout changes make them invalid. For more +//! information, see the Unsafe Code Guidelines Reference page on the [Layout of +//! packed SIMD vectors][simd-layout]. +//! +//! `simd-nightly`: Enables the `simd` feature and adds support for SIMD types +//! which are only available on nightly. Since these types are unstable, support +//! for any type may be removed at any point in the future. +//! +//! [simd-layout]: https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html +#![deny(missing_docs, clippy::indexing_slicing)] #![cfg_attr(not(test), no_std)] +#![cfg_attr(feature = "simd-nightly", feature(stdsimd))] #![recursion_limit = "2048"] pub mod byteorder; @@ -30,21 +56,36 @@ pub mod byteorder; pub use crate::byteorder::*; pub use zerocopy_derive::*; -use core::cell::{Ref, RefMut}; -use core::fmt::{self, Debug, Display, Formatter}; -use core::marker::PhantomData; -use core::mem; -use core::ops::{Deref, DerefMut}; -use core::slice; - -// This is a hack to allow derives of FromBytes, AsBytes, and Unaligned to work -// in this crate. They assume that zerocopy is linked as an extern crate, so -// they access items from it as `zerocopy::Xxx`. This makes that still work. +use core::{ + cell::{Ref, RefMut}, + cmp::Ordering, + fmt::{self, Debug, Display, Formatter}, + marker::PhantomData, + mem::{self, MaybeUninit}, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping, + }, + ops::{Deref, DerefMut}, + ptr, slice, +}; + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use { + alloc::boxed::Box, + core::{alloc::Layout, ptr::NonNull}, +}; + +// This is a hack to allow derives of `FromBytes`, `AsBytes`, and `Unaligned` to +// work in this crate. They assume that zerocopy is linked as an extern crate, +// so they access items from it as `zerocopy::Xxx`. This makes that still work. mod zerocopy { pub use crate::*; } -// implement an unsafe trait for a range of container types +// Implements an unsafe trait for a range of container types. macro_rules! impl_for_composite_types { ($trait:ident) => { unsafe impl $trait for PhantomData { @@ -61,6 +102,16 @@ macro_rules! impl_for_composite_types { { } } + // According to the `Wrapping` docs, "`Wrapping` is guaranteed to + // have the same layout and ABI as `T`." + unsafe impl $trait for Wrapping { + fn only_derive_is_allowed_to_implement_this_trait() + where + Self: Sized, + { + } + } + // Unit type has an empty representation. unsafe impl $trait for () { fn only_derive_is_allowed_to_implement_this_trait() where @@ -68,46 +119,64 @@ macro_rules! impl_for_composite_types { { } } - impl_for_array_sizes!($trait); + // Constant sized array with elements implementing `$trait`. + unsafe impl $trait for [T; N] { + fn only_derive_is_allowed_to_implement_this_trait() + where + Self: Sized, + { + } + } }; } -// implement an unsafe trait for all signed and unsigned primitive types -macro_rules! impl_for_primitives { - ($trait:ident) => ( - impl_for_primitives!(@inner $trait, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64); - ); - (@inner $trait:ident, $type:ty) => ( - unsafe impl $trait for $type { - fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {} - } - ); - (@inner $trait:ident, $type:ty, $($types:ty),*) => ( - unsafe impl $trait for $type { - fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {} - } - impl_for_primitives!(@inner $trait, $($types),*); +/// Implements `$trait` for one or more `$type`s. +macro_rules! impl_for_types { + ($trait:ident, $($types:ty),* $(,)?) => ( + $( + unsafe impl $trait for $types { + fn only_derive_is_allowed_to_implement_this_trait() {} + } + )* ); } -// implement an unsafe trait for all array lengths up to 64, plus several -// useful powers-of-two beyond that, plus lengths needed by Fuchsia with -// an element type that implements the trait -macro_rules! impl_for_array_sizes { - ($trait:ident) => ( - impl_for_array_sizes!(@inner $trait, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 98, 126, 128, 236, 255, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536); - ); - (@inner $trait:ident, $n:expr) => ( - unsafe impl $trait for [T; $n] { - fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {} - } - ); - (@inner $trait:ident, $n:expr, $($ns:expr),*) => ( - unsafe impl $trait for [T; $n] { - fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {} - } - impl_for_array_sizes!(@inner $trait, $($ns),*); - ); +/// Implements `$trait` for all signed and unsigned primitive types. +macro_rules! impl_for_primitives { + ($trait:ident) => { + impl_for_types!( + $trait, + u8, + i8, + u16, + i16, + u32, + i32, + u64, + i64, + u128, + i128, + usize, + isize, + f32, + f64, + // The Rust compiler reuses `0` value to represent `None`, so + // `size_of::>() == size_of::()`; see + // `NonZeroXXX` documentation. + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + }; } /// Types for which any byte pattern is valid. @@ -166,12 +235,154 @@ macro_rules! impl_for_array_sizes { /// Whether a struct is soundly `FromBytes` therefore solely depends on whether /// its fields are `FromBytes`. pub unsafe trait FromBytes { - // NOTE: The Self: Sized bound makes it so that FromBytes is still object + // The `Self: Sized` bound makes it so that `FromBytes` is still object // safe. #[doc(hidden)] fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized; + + /// Reads a copy of `Self` from `bytes`. + /// + /// If `bytes.len() != size_of::()`, `read_from` returns `None`. + fn read_from(bytes: B) -> Option + where + Self: Sized, + { + let lv = LayoutVerified::<_, Unalign>::new_unaligned(bytes)?; + Some(lv.read().into_inner()) + } + + /// Reads a copy of `Self` from the prefix of `bytes`. + /// + /// `read_from_prefix` reads a `Self` from the first `size_of::()` + /// bytes of `bytes`. If `bytes.len() < size_of::()`, it returns + /// `None`. + fn read_from_prefix(bytes: B) -> Option + where + Self: Sized, + { + let (lv, _suffix) = LayoutVerified::<_, Unalign>::new_unaligned_from_prefix(bytes)?; + Some(lv.read().into_inner()) + } + + /// Reads a copy of `Self` from the suffix of `bytes`. + /// + /// `read_from_suffix` reads a `Self` from the last `size_of::()` + /// bytes of `bytes`. If `bytes.len() < size_of::()`, it returns + /// `None`. + fn read_from_suffix(bytes: B) -> Option + where + Self: Sized, + { + let (_prefix, lv) = LayoutVerified::<_, Unalign>::new_unaligned_from_suffix(bytes)?; + Some(lv.read().into_inner()) + } + + /// Creates an instance of `Self` from zeroed bytes. + fn new_zeroed() -> Self + where + Self: Sized, + { + unsafe { + // SAFETY: `FromBytes` says all bit patterns (including zeroes) are + // legal. + mem::zeroed() + } + } + + /// Creates a `Box` from zeroed bytes. + /// + /// This function is useful for allocating large values on the heap and + /// zero-initializing them, without ever creating a temporary instance of + /// `Self` on the stack. For example, `<[u8; 1048576]>::new_box_zeroed()` + /// will allocate `[u8; 1048576]` directly on the heap; it does not require + /// storing `[u8; 1048576]` in a temporary variable on the stack. + /// + /// On systems that use a heap implementation that supports allocating from + /// pre-zeroed memory, using `new_box_zeroed` (or related functions) may + /// have performance benefits. + /// + /// Note that `Box` can be converted to `Arc` and other + /// container types without reallocation. + /// + /// # Panics + /// + /// Panics if allocation of `size_of::()` bytes fails. + #[cfg(feature = "alloc")] + fn new_box_zeroed() -> Box + where + Self: Sized, + { + // If `T` is a ZST, then return a proper boxed instance of it. There is + // no allocation, but `Box` does require a correct dangling pointer. + let layout = Layout::new::(); + if layout.size() == 0 { + return Box::new(Self::new_zeroed()); + } + + unsafe { + let ptr = alloc::alloc::alloc_zeroed(layout) as *mut Self; + if ptr.is_null() { + alloc::alloc::handle_alloc_error(layout); + } + Box::from_raw(ptr) + } + } + + /// Creates a `Box<[Self]>` (a boxed slice) from zeroed bytes. + /// + /// This function is useful for allocating large values of `[Self]` on the + /// heap and zero-initializing them, without ever creating a temporary + /// instance of `[Self; _]` on the stack. For example, + /// `u8::new_box_slice_zeroed(1048576)` will allocate the slice directly on + /// the heap; it does not require storing the slice on the stack. + /// + /// On systems that use a heap implementation that supports allocating from + /// pre-zeroed memory, using `new_box_slice_zeroed` may have performance + /// benefits. + /// + /// If `Self` is a zero-sized type, then this function will return a + /// `Box<[Self]>` that has the correct `len`. Such a box cannot contain any + /// actual information, but its `len()` property will report the correct + /// value. + /// + /// # Panics + /// + /// * Panics if `size_of::() * len` overflows. + /// * Panics if allocation of `size_of::() * len` bytes fails. + #[cfg(feature = "alloc")] + fn new_box_slice_zeroed(len: usize) -> Box<[Self]> + where + Self: Sized, + { + // TODO(#2): Use `Layout::repeat` when `alloc_layout_extra` is + // stabilized. + // + // This will intentionally panic if it overflows. + unsafe { + // SAFETY: `from_size_align_unchecked` is sound because + // slice_len_bytes is guaranteed to be properly aligned (we just + // multiplied it by `size_of::()`, which is guaranteed to be + // aligned). + let layout = Layout::from_size_align_unchecked( + mem::size_of::().checked_mul(len).unwrap(), + mem::align_of::(), + ); + if layout.size() != 0 { + let ptr = alloc::alloc::alloc_zeroed(layout) as *mut Self; + if ptr.is_null() { + alloc::alloc::handle_alloc_error(layout); + } + Box::from_raw(slice::from_raw_parts_mut(ptr, len)) + } else { + // `Box<[T]>` does not allocate when `T` is zero-sized or when + // `len` is zero, but it does require a non-null dangling + // pointer for its allocation. + Box::from_raw(slice::from_raw_parts_mut(NonNull::::dangling().as_ptr(), len)) + } + } + } } /// Types which are safe to treat as an immutable byte slice. @@ -228,25 +439,26 @@ pub unsafe trait FromBytes { /// /// [Rust Reference]: https://doc.rust-lang.org/reference/type-layout.html pub unsafe trait AsBytes { + // The `Self: Sized` bound makes it so that `AsBytes` is still object safe. #[doc(hidden)] fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized; - /// Get the bytes of this value. + /// Gets the bytes of this value. /// /// `as_bytes` provides access to the bytes of this value as an immutable /// byte slice. fn as_bytes(&self) -> &[u8] { unsafe { - // NOTE: This function does not have a Self: Sized bound. - // size_of_val works for unsized values too. + // Note that this method does not have a `Self: Sized` bound; + // `size_of_val` works for unsized values too. let len = mem::size_of_val(self); slice::from_raw_parts(self as *const Self as *const u8, len) } } - /// Get the bytes of this value mutably. + /// Gets the bytes of this value mutably. /// /// `as_bytes_mut` provides access to the bytes of this value as a mutable /// byte slice. @@ -255,16 +467,81 @@ pub unsafe trait AsBytes { Self: FromBytes, { unsafe { - // NOTE: This function does not have a Self: Sized bound. - // size_of_val works for unsized values too. + // Note that this method does not have a `Self: Sized` bound; + // `size_of_val` works for unsized values too. let len = mem::size_of_val(self); slice::from_raw_parts_mut(self as *mut Self as *mut u8, len) } } + + /// Writes a copy of `self` to `bytes`. + /// + /// If `bytes.len() != size_of_val(self)`, `write_to` returns `None`. + fn write_to(&self, mut bytes: B) -> Option<()> { + if bytes.len() != mem::size_of_val(self) { + return None; + } + + bytes.copy_from_slice(self.as_bytes()); + Some(()) + } + + /// Writes a copy of `self` to the prefix of `bytes`. + /// + /// `write_to_prefix` writes `self` to the first `size_of_val(self)` bytes + /// of `bytes`. If `bytes.len() < size_of_val(self)`, it returns `None`. + fn write_to_prefix(&self, mut bytes: B) -> Option<()> { + let size = mem::size_of_val(self); + bytes.get_mut(..size)?.copy_from_slice(self.as_bytes()); + Some(()) + } + + /// Writes a copy of `self` to the suffix of `bytes`. + /// + /// `write_to_suffix` writes `self` to the last `size_of_val(self)` bytes of + /// `bytes`. If `bytes.len() < size_of_val(self)`, it returns `None`. + fn write_to_suffix(&self, mut bytes: B) -> Option<()> { + let start = bytes.len().checked_sub(mem::size_of_val(self))?; + bytes + .get_mut(start..) + .expect("`start` should be in-bounds of `bytes`") + .copy_from_slice(self.as_bytes()); + Some(()) + } } -// Special case for bool -unsafe impl AsBytes for bool { +// Special case for `AsBytes`-only types (they are not included in +// `impl_for_primitives!`). +impl_for_types!( + AsBytes, + bool, + char, + str, + // `NonZeroXxx` is `AsBytes`, but not `FromBytes`. + // + // SAFETY: `NonZeroXxx` has the same layout as its associated primitive. + // Since it is the same size, this guarantees it has no padding - integers + // have no padding, and there's no room for padding if it can represent all + // of the same values except 0. + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, + NonZeroUsize, + NonZeroI8, + NonZeroI16, + NonZeroI32, + NonZeroI64, + NonZeroI128, + NonZeroIsize, +); + +// `MaybeUninit` is `FromBytes`, but never `AsBytes` since it may contain +// uninitialized bytes. +// +// SAFETY: `MaybeUninit` has no restrictions on its contents. +unsafe impl FromBytes for MaybeUninit { fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized, @@ -291,7 +568,7 @@ impl_for_composite_types!(AsBytes); /// is marked as `Unaligned` which violates this contract, it may cause /// undefined behavior. pub unsafe trait Unaligned { - // NOTE: The Self: Sized bound makes it so that Unaligned is still object + // The `Self: Sized` bound makes it so that `Unaligned` is still object // safe. #[doc(hidden)] fn only_derive_is_allowed_to_implement_this_trait() @@ -299,21 +576,280 @@ pub unsafe trait Unaligned { Self: Sized; } -unsafe impl Unaligned for u8 { - fn only_derive_is_allowed_to_implement_this_trait() - where - Self: Sized, - { +impl_for_types!(Unaligned, u8, i8, bool); +impl_for_composite_types!(Unaligned); + +// SIMD support +// +// Per the Unsafe Code Guidelines Reference [1]: +// +// Packed SIMD vector types are `repr(simd)` homogeneous tuple-structs +// containing `N` elements of type `T` where `N` is a power-of-two and the +// size and alignment requirements of `T` are equal: +// +// ```rust +// #[repr(simd)] +// struct Vector(T_0, ..., T_(N - 1)); +// ``` +// +// ... +// +// The size of `Vector` is `N * size_of::()` and its alignment is an +// implementation-defined function of `T` and `N` greater than or equal to +// `align_of::()`. +// +// ... +// +// Vector elements are laid out in source field order, enabling random access +// to vector elements by reinterpreting the vector as an array: +// +// ```rust +// union U { +// vec: Vector, +// arr: [T; N] +// } +// +// assert_eq!(size_of::>(), size_of::<[T; N]>()); +// assert!(align_of::>() >= align_of::<[T; N]>()); +// +// unsafe { +// let u = U { vec: Vector(t_0, ..., t_(N - 1)) }; +// +// assert_eq!(u.vec.0, u.arr[0]); +// // ... +// assert_eq!(u.vec.(N - 1), u.arr[N - 1]); +// } +// ``` +// +// Given this background, we can observe that: +// - The size and bit pattern requirements of a SIMD type are equivalent to the +// equivalent array type. Thus, for any SIMD type whose primitive `T` is +// `FromBytes`, that SIMD type is also `FromBytes`. The same holds for +// `AsBytes`. +// - Since no upper bound is placed on the alignment, no SIMD type can be +// guaranteed to be `Unaligned`. +// +// Also per [1]: +// +// This chapter represents the consensus from issue #38. The statements in +// here are not (yet) "guaranteed" not to change until an RFC ratifies them. +// +// See issue #38 [2]. While this behavior is not technically guaranteed, the +// likelihood that the behavior will change such that SIMD types are no longer +// `FromBytes` or `AsBytes` is next to zero, as that would defeat the entire +// purpose of SIMD types. Nonetheless, we put this behavior behind the `simd` +// Cargo feature, which requires consumers to opt into this stability hazard. +// +// [1] https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html +// [2] https://github.com/rust-lang/unsafe-code-guidelines/issues/38 +#[cfg(feature = "simd")] +mod simd { + /// Defines a module which implements `FromBytes` and `AsBytes` for a set of + /// types from a module in `core::arch`. + /// + /// `$arch` is both the name of the defined module and the name of the + /// module in `core::arch`, and `$typ` is the list of items from that module + /// to implement `FromBytes` and `AsBytes` for. + #[allow(unused_macros)] // `allow(unused_macros)` is needed because some + // target/feature combinations don't emit any impls + // and thus don't use this macro. + macro_rules! simd_arch_mod { + ($arch:ident, $($typ:ident),*) => { + mod $arch { + use core::arch::$arch::{$($typ),*}; + + use crate::*; + + impl_for_types!(FromBytes, $($typ),*); + impl_for_types!(AsBytes, $($typ),*); + } + }; + } + + #[cfg(target_arch = "x86")] + simd_arch_mod!(x86, __m128, __m128d, __m128i, __m256, __m256d, __m256i); + #[cfg(target_arch = "x86_64")] + simd_arch_mod!(x86_64, __m128, __m128d, __m128i, __m256, __m256d, __m256i); + #[cfg(target_arch = "wasm32")] + simd_arch_mod!(wasm32, v128); + #[cfg(all(feature = "simd-nightly", target_arch = "powerpc"))] + simd_arch_mod!( + powerpc, + vector_bool_long, + vector_double, + vector_signed_long, + vector_unsigned_long + ); + #[cfg(all(feature = "simd-nightly", target_arch = "powerpc64"))] + simd_arch_mod!( + powerpc64, + vector_bool_long, + vector_double, + vector_signed_long, + vector_unsigned_long + ); + #[cfg(all(feature = "simd-nightly", target_arch = "aarch64"))] + #[rustfmt::skip] + simd_arch_mod!( + aarch64, float32x2_t, float32x4_t, float64x1_t, float64x2_t, int8x8_t, int8x8x2_t, + int8x8x3_t, int8x8x4_t, int8x16_t, int8x16x2_t, int8x16x3_t, int8x16x4_t, int16x4_t, + int16x8_t, int32x2_t, int32x4_t, int64x1_t, int64x2_t, poly8x8_t, poly8x8x2_t, poly8x8x3_t, + poly8x8x4_t, poly8x16_t, poly8x16x2_t, poly8x16x3_t, poly8x16x4_t, poly16x4_t, poly16x8_t, + poly64x1_t, poly64x2_t, uint8x8_t, uint8x8x2_t, uint8x8x3_t, uint8x8x4_t, uint8x16_t, + uint8x16x2_t, uint8x16x3_t, uint8x16x4_t, uint16x4_t, uint16x8_t, uint32x2_t, uint32x4_t, + uint64x1_t, uint64x2_t + ); + #[cfg(all(feature = "simd-nightly", target_arch = "arm"))] + #[rustfmt::skip] + simd_arch_mod!(arm, int8x4_t, uint8x4_t); +} + +/// A type with no alignment requirement. +/// +/// A `Unalign` wraps a `T`, removing any alignment requirement. `Unalign` +/// has the same size and ABI as `T`, but not necessarily the same alignment. +/// This is useful if a type with an alignment requirement needs to be read from +/// a chunk of memory which provides no alignment guarantees. +/// +/// Since `Unalign` has no alignment requirement, the inner `T` may not be +/// properly aligned in memory, and so `Unalign` provides no way of getting a +/// reference to the inner `T`. Instead, the `T` may only be obtained by value +/// (see [`get`] and [`into_inner`]). +/// +/// [`get`]: Unalign::get +/// [`into_inner`]: Unalign::into_inner +#[derive(FromBytes, Unaligned, Copy)] +#[repr(C, packed)] +pub struct Unalign(T); + +// Note that `Unalign: Clone` only if `T: Copy`. Since the inner `T` may not be +// aligned, there's no way to safely call `T::clone`, and so a `T: Clone` bound +// is not sufficient to implement `Clone` for `Unalign`. +impl Clone for Unalign { + fn clone(&self) -> Unalign { + *self + } +} + +impl Unalign { + /// Constructs a new `Unalign`. + pub fn new(val: T) -> Unalign { + Unalign(val) + } + + /// Consumes `self`, returning the inner `T`. + pub fn into_inner(self) -> T { + let Unalign(val) = self; + val + } + + /// Gets an unaligned raw pointer to the inner `T`. + /// + /// # Safety + /// + /// The returned raw pointer is not necessarily aligned to + /// `align_of::()`. Most functions which operate on raw pointers require + /// those pointers to be aligned, so calling those functions with the result + /// of `get_ptr` will be undefined behavior if alignment is not guaranteed + /// using some out-of-band mechanism. In general, the only functions which + /// are safe to call with this pointer are those which are explicitly + /// documented as being sound to use with an unaligned pointer, such as + /// [`read_unaligned`]. + /// + /// [`read_unaligned`]: core::ptr::read_unaligned + pub fn get_ptr(&self) -> *const T { + ptr::addr_of!(self.0) + } + + /// Gets an unaligned mutable raw pointer to the inner `T`. + /// + /// # Safety + /// + /// The returned raw pointer is not necessarily aligned to + /// `align_of::()`. Most functions which operate on raw pointers require + /// those pointers to be aligned, so calling those functions with the result + /// of `get_ptr` will be undefined behavior if alignment is not guaranteed + /// using some out-of-band mechanism. In general, the only functions which + /// are safe to call with this pointer are those which are explicitly + /// documented as being sound to use with an unaligned pointer, such as + /// [`read_unaligned`]. + /// + /// [`read_unaligned`]: core::ptr::read_unaligned + pub fn get_mut_ptr(&mut self) -> *mut T { + ptr::addr_of_mut!(self.0) } } -unsafe impl Unaligned for i8 { + +impl Unalign { + /// Gets a copy of the inner `T`. + pub fn get(&self) -> T { + let Unalign(val) = *self; + val + } +} + +// SAFETY: Since `T: AsBytes`, we know that it's safe to construct a `&[u8]` +// from an aligned `&T`. Since `&[u8]` itself has no alignment requirements, it +// must also be safe to construct a `&[u8]` from a `&T` at any address. Since +// `Unalign` is `#[repr(C, packed)]`, everything about its layout except for +// its alignment is the same as `T`'s layout. +unsafe impl AsBytes for Unalign { fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized, { } } -impl_for_composite_types!(Unaligned); + +// Used in `transmute!` below. +#[doc(hidden)] +pub use core::mem::transmute as __real_transmute; + +/// Safely transmutes a value of one type to a value of another type of the same +/// size. +/// +/// The expression `$e` must have a concrete type, `T`, which implements +/// `AsBytes`. The `transmute!` expression must also have a concrete type, `U` +/// (`U` is inferred from the calling context), and `U` must implement +/// `FromBytes`. +/// +/// Note that the `T` produced by the expression `$e` will *not* be dropped. +/// Semantically, its bits will be copied into a new value of type `U`, the +/// original `T` will be forgotten, and the value of type `U` will be returned. +#[macro_export] +macro_rules! transmute { + ($e:expr) => {{ + // NOTE: This must be a macro (rather than a function with trait bounds) + // because there's no way, in a generic context, to enforce that two + // types have the same size. `core::mem::transmute` uses compiler magic + // to enforce this so long as the types are concrete. + + let e = $e; + if false { + // This branch, though never taken, ensures that the type of `e` is + // `AsBytes` and that the type of this macro invocation expression + // is `FromBytes`. + fn transmute(_t: T) -> U { + unreachable!() + } + transmute(e) + } else { + // `core::mem::transmute` ensures that the type of `e` and the type + // of this macro invocation expression have the same size. We know + // this transmute is safe thanks to the `AsBytes` and `FromBytes` + // bounds enforced by the `false` branch. + // + // We use `$crate::__real_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 { $crate::__real_transmute(e) } + } + }} +} /// A length- and alignment-checked reference to a byte slice which can safely /// be reinterpreted as another type. @@ -370,7 +906,7 @@ impl LayoutVerified where B: ByteSlice, { - /// Construct a new `LayoutVerified`. + /// Constructs a new `LayoutVerified`. /// /// `new` verifies that `bytes.len() == size_of::()` and that `bytes` is /// aligned to `align_of::()`, and constructs a new `LayoutVerified`. If @@ -383,7 +919,7 @@ where Some(LayoutVerified(bytes, PhantomData)) } - /// Construct a new `LayoutVerified` from the prefix of a byte slice. + /// Constructs a new `LayoutVerified` from the prefix of a byte slice. /// /// `new_from_prefix` verifies that `bytes.len() >= size_of::()` and that /// `bytes` is aligned to `align_of::()`. It consumes the first @@ -399,7 +935,7 @@ where Some((LayoutVerified(bytes, PhantomData), suffix)) } - /// Construct a new `LayoutVerified` from the suffix of a byte slice. + /// Constructs a new `LayoutVerified` from the suffix of a byte slice. /// /// `new_from_suffix` verifies that `bytes.len() >= size_of::()` and that /// the last `size_of::()` bytes of `bytes` are aligned to @@ -421,23 +957,11 @@ where } } -impl LayoutVerified -where - B: ByteSlice, - T: ?Sized, -{ - // Get the underlying bytes. - #[inline] - pub fn bytes(&self) -> &[u8] { - &self.0 - } -} - impl LayoutVerified where B: ByteSlice, { - /// Construct a new `LayoutVerified` of a slice type. + /// Constructs a new `LayoutVerified` of a slice type. /// /// `new_slice` verifies that `bytes.len()` is a multiple of /// `size_of::()` and that `bytes` is aligned to `align_of::()`, and @@ -457,6 +981,58 @@ where } Some(LayoutVerified(bytes, PhantomData)) } + + /// Constructs a new `LayoutVerified` of a slice type from the prefix of a + /// byte slice. + /// + /// `new_slice_from_prefix` verifies that `bytes.len() >= size_of::() * + /// count` and that `bytes` is aligned to `align_of::()`. It consumes the + /// first `size_of::() * count` bytes from `bytes` to construct a + /// `LayoutVerified`, and returns the remaining bytes to the caller. It also + /// ensures that `sizeof::() * count` does not overflow a `usize`. If any + /// of the length, alignment, or overflow checks fail, it returns `None`. + /// + /// # Panics + /// + /// `new_slice_from_prefix` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_from_prefix(bytes: B, count: usize) -> Option<(LayoutVerified, B)> { + let expected_len = match mem::size_of::().checked_mul(count) { + Some(len) => len, + None => return None, + }; + if bytes.len() < expected_len { + return None; + } + let (prefix, bytes) = bytes.split_at(expected_len); + Self::new_slice(prefix).map(move |l| (l, bytes)) + } + + /// Constructs a new `LayoutVerified` of a slice type from the suffix of a + /// byte slice. + /// + /// `new_slice_from_suffix` verifies that `bytes.len() >= size_of::() * + /// count` and that `bytes` is aligned to `align_of::()`. It consumes the + /// last `size_of::() * count` bytes from `bytes` to construct a + /// `LayoutVerified`, and returns the preceding bytes to the caller. It also + /// ensures that `sizeof::() * count` does not overflow a `usize`. If any + /// of the length, alignment, or overflow checks fail, it returns `None`. + /// + /// # Panics + /// + /// `new_slice_from_suffix` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_from_suffix(bytes: B, count: usize) -> Option<(B, LayoutVerified)> { + let expected_len = match mem::size_of::().checked_mul(count) { + Some(len) => len, + None => return None, + }; + if bytes.len() < expected_len { + return None; + } + let (bytes, suffix) = bytes.split_at(expected_len); + Self::new_slice(suffix).map(move |l| (bytes, l)) + } } fn map_zeroed( @@ -464,9 +1040,7 @@ fn map_zeroed( ) -> Option> { match opt { Some(mut lv) => { - for b in lv.0.iter_mut() { - *b = 0; - } + lv.0.fill(0); Some(lv) } None => None, @@ -478,9 +1052,7 @@ fn map_prefix_tuple_zeroed( ) -> Option<(LayoutVerified, B)> { match opt { Some((mut lv, rest)) => { - for b in lv.0.iter_mut() { - *b = 0; - } + lv.0.fill(0); Some((lv, rest)) } None => None, @@ -497,7 +1069,7 @@ impl LayoutVerified where B: ByteSliceMut, { - /// Construct a new `LayoutVerified` after zeroing the bytes. + /// Constructs a new `LayoutVerified` after zeroing the bytes. /// /// `new_zeroed` verifies that `bytes.len() == size_of::()` and that /// `bytes` is aligned to `align_of::()`, and constructs a new @@ -511,7 +1083,7 @@ where map_zeroed(Self::new(bytes)) } - /// Construct a new `LayoutVerified` from the prefix of a byte slice, + /// Constructs a new `LayoutVerified` from the prefix of a byte slice, /// zeroing the prefix. /// /// `new_from_prefix_zeroed` verifies that `bytes.len() >= size_of::()` @@ -528,11 +1100,11 @@ where map_prefix_tuple_zeroed(Self::new_from_prefix(bytes)) } - /// Construct a new `LayoutVerified` from the suffix of a byte slice, + /// Constructs a new `LayoutVerified` from the suffix of a byte slice, /// zeroing the suffix. /// - /// `new_from_suffix_zeroed` verifies that `bytes.len() >= size_of::()` and that - /// the last `size_of::()` bytes of `bytes` are aligned to + /// `new_from_suffix_zeroed` verifies that `bytes.len() >= size_of::()` + /// and that the last `size_of::()` bytes of `bytes` are aligned to /// `align_of::()`. It consumes the last `size_of::()` bytes from /// `bytes` to construct a `LayoutVerified`, and returns the preceding bytes /// to the caller. If either the length or alignment checks fail, it returns @@ -551,7 +1123,7 @@ impl LayoutVerified where B: ByteSliceMut, { - /// Construct a new `LayoutVerified` of a slice type after zeroing the + /// Constructs a new `LayoutVerified` of a slice type after zeroing the /// bytes. /// /// `new_slice_zeroed` verifies that `bytes.len()` is a multiple of @@ -570,6 +1142,56 @@ where pub fn new_slice_zeroed(bytes: B) -> Option> { map_zeroed(Self::new_slice(bytes)) } + + /// Constructs a new `LayoutVerified` of a slice type from the prefix of a + /// byte slice, after zeroing the bytes. + /// + /// `new_slice_from_prefix` verifies that `bytes.len() >= size_of::() * + /// count` and that `bytes` is aligned to `align_of::()`. It consumes the + /// first `size_of::() * count` bytes from `bytes` to construct a + /// `LayoutVerified`, and returns the remaining bytes to the caller. It also + /// ensures that `sizeof::() * count` does not overflow a `usize`. If any + /// of the length, alignment, or overflow checks fail, it returns `None`. + /// + /// If the checks succeed, then the suffix which is consumed will be + /// initialized to zero. This can be useful when re-using buffers to ensure + /// that sensitive data previously stored in the buffer is not leaked. + /// + /// # Panics + /// + /// `new_slice_from_prefix_zeroed` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_from_prefix_zeroed( + bytes: B, + count: usize, + ) -> Option<(LayoutVerified, B)> { + map_prefix_tuple_zeroed(Self::new_slice_from_prefix(bytes, count)) + } + + /// Constructs a new `LayoutVerified` of a slice type from the prefix of a + /// byte slice, after zeroing the bytes. + /// + /// `new_slice_from_suffix` verifies that `bytes.len() >= size_of::() * + /// count` and that `bytes` is aligned to `align_of::()`. It consumes the + /// last `size_of::() * count` bytes from `bytes` to construct a + /// `LayoutVerified`, and returns the preceding bytes to the caller. It also + /// ensures that `sizeof::() * count` does not overflow a `usize`. If any + /// of the length, alignment, or overflow checks fail, it returns `None`. + /// + /// If the checks succeed, then the consumed suffix will be initialized to + /// zero. This can be useful when re-using buffers to ensure that sensitive + /// data previously stored in the buffer is not leaked. + /// + /// # Panics + /// + /// `new_slice_from_suffix_zeroed` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_from_suffix_zeroed( + bytes: B, + count: usize, + ) -> Option<(B, LayoutVerified)> { + map_suffix_tuple_zeroed(Self::new_slice_from_suffix(bytes, count)) + } } impl LayoutVerified @@ -577,7 +1199,7 @@ where B: ByteSlice, T: Unaligned, { - /// Construct a new `LayoutVerified` for a type with no alignment + /// Constructs a new `LayoutVerified` for a type with no alignment /// requirement. /// /// `new_unaligned` verifies that `bytes.len() == size_of::()` and @@ -591,7 +1213,7 @@ where Some(LayoutVerified(bytes, PhantomData)) } - /// Construct a new `LayoutVerified` from the prefix of a byte slice for a + /// Constructs a new `LayoutVerified` from the prefix of a byte slice for a /// type with no alignment requirement. /// /// `new_unaligned_from_prefix` verifies that `bytes.len() >= @@ -607,7 +1229,7 @@ where Some((LayoutVerified(bytes, PhantomData), suffix)) } - /// Construct a new `LayoutVerified` from the suffix of a byte slice for a + /// Constructs a new `LayoutVerified` from the suffix of a byte slice for a /// type with no alignment requirement. /// /// `new_unaligned_from_suffix` verifies that `bytes.len() >= @@ -630,7 +1252,7 @@ where B: ByteSlice, T: Unaligned, { - /// Construct a new `LayoutVerified` of a slice type with no alignment + /// Constructs a new `LayoutVerified` of a slice type with no alignment /// requirement. /// /// `new_slice_unaligned` verifies that `bytes.len()` is a multiple of @@ -648,6 +1270,64 @@ where } Some(LayoutVerified(bytes, PhantomData)) } + + /// Constructs a new `LayoutVerified` of a slice type with no alignment + /// requirement from the prefix of a byte slice. + /// + /// `new_slice_from_prefix` verifies that `bytes.len() >= size_of::() * + /// count`. It consumes the first `size_of::() * count` bytes from + /// `bytes` to construct a `LayoutVerified`, and returns the remaining bytes + /// to the caller. It also ensures that `sizeof::() * count` does not + /// overflow a `usize`. If either the length, or overflow checks fail, it + /// returns `None`. + /// + /// # Panics + /// + /// `new_slice_unaligned_from_prefix` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_unaligned_from_prefix( + bytes: B, + count: usize, + ) -> Option<(LayoutVerified, B)> { + let expected_len = match mem::size_of::().checked_mul(count) { + Some(len) => len, + None => return None, + }; + if bytes.len() < expected_len { + return None; + } + let (prefix, bytes) = bytes.split_at(expected_len); + Self::new_slice_unaligned(prefix).map(move |l| (l, bytes)) + } + + /// Constructs a new `LayoutVerified` of a slice type with no alignment + /// requirement from the suffix of a byte slice. + /// + /// `new_slice_from_suffix` verifies that `bytes.len() >= size_of::() * + /// count`. It consumes the last `size_of::() * count` bytes from `bytes` + /// to construct a `LayoutVerified`, and returns the remaining bytes to the + /// caller. It also ensures that `sizeof::() * count` does not overflow a + /// `usize`. If either the length, or overflow checks fail, it returns + /// `None`. + /// + /// # Panics + /// + /// `new_slice_unaligned_from_suffix` panics if `T` is a zero-sized type. + #[inline] + pub fn new_slice_unaligned_from_suffix( + bytes: B, + count: usize, + ) -> Option<(B, LayoutVerified)> { + let expected_len = match mem::size_of::().checked_mul(count) { + Some(len) => len, + None => return None, + }; + if bytes.len() < expected_len { + return None; + } + let (bytes, suffix) = bytes.split_at(expected_len); + Self::new_slice_unaligned(suffix).map(move |l| (bytes, l)) + } } impl LayoutVerified @@ -655,7 +1335,7 @@ where B: ByteSliceMut, T: Unaligned, { - /// Construct a new `LayoutVerified` for a type with no alignment + /// Constructs a new `LayoutVerified` for a type with no alignment /// requirement, zeroing the bytes. /// /// `new_unaligned_zeroed` verifies that `bytes.len() == size_of::()` and @@ -670,7 +1350,7 @@ where map_zeroed(Self::new_unaligned(bytes)) } - /// Construct a new `LayoutVerified` from the prefix of a byte slice for a + /// Constructs a new `LayoutVerified` from the prefix of a byte slice for a /// type with no alignment requirement, zeroing the prefix. /// /// `new_unaligned_from_prefix_zeroed` verifies that `bytes.len() >= @@ -686,7 +1366,7 @@ where map_prefix_tuple_zeroed(Self::new_unaligned_from_prefix(bytes)) } - /// Construct a new `LayoutVerified` from the suffix of a byte slice for a + /// Constructs a new `LayoutVerified` from the suffix of a byte slice for a /// type with no alignment requirement, zeroing the suffix. /// /// `new_unaligned_from_suffix_zeroed` verifies that `bytes.len() >= @@ -708,7 +1388,7 @@ where B: ByteSliceMut, T: Unaligned, { - /// Construct a new `LayoutVerified` for a slice type with no alignment + /// Constructs a new `LayoutVerified` for a slice type with no alignment /// requirement, zeroing the bytes. /// /// `new_slice_unaligned_zeroed` verifies that `bytes.len()` is a multiple @@ -726,6 +1406,58 @@ where pub fn new_slice_unaligned_zeroed(bytes: B) -> Option> { map_zeroed(Self::new_slice_unaligned(bytes)) } + + /// Constructs a new `LayoutVerified` of a slice type with no alignment + /// requirement from the prefix of a byte slice, after zeroing the bytes. + /// + /// `new_slice_from_prefix` verifies that `bytes.len() >= size_of::() * + /// count`. It consumes the first `size_of::() * count` bytes from + /// `bytes` to construct a `LayoutVerified`, and returns the remaining bytes + /// to the caller. It also ensures that `sizeof::() * count` does not + /// overflow a `usize`. If either the length, or overflow checks fail, it + /// returns `None`. + /// + /// If the checks succeed, then the prefix will be initialized to zero. This + /// can be useful when re-using buffers to ensure that sensitive data + /// previously stored in the buffer is not leaked. + /// + /// # Panics + /// + /// `new_slice_unaligned_from_prefix_zeroed` panics if `T` is a zero-sized + /// type. + #[inline] + pub fn new_slice_unaligned_from_prefix_zeroed( + bytes: B, + count: usize, + ) -> Option<(LayoutVerified, B)> { + map_prefix_tuple_zeroed(Self::new_slice_unaligned_from_prefix(bytes, count)) + } + + /// Constructs a new `LayoutVerified` of a slice type with no alignment + /// requirement from the suffix of a byte slice, after zeroing the bytes. + /// + /// `new_slice_from_suffix` verifies that `bytes.len() >= size_of::() * + /// count`. It consumes the last `size_of::() * count` bytes from `bytes` + /// to construct a `LayoutVerified`, and returns the remaining bytes to the + /// caller. It also ensures that `sizeof::() * count` does not overflow a + /// `usize`. If either the length, or overflow checks fail, it returns + /// `None`. + /// + /// If the checks succeed, then the suffix will be initialized to zero. This + /// can be useful when re-using buffers to ensure that sensitive data + /// previously stored in the buffer is not leaked. + /// + /// # Panics + /// + /// `new_slice_unaligned_from_suffix_zeroed` panics if `T` is a zero-sized + /// type. + #[inline] + pub fn new_slice_unaligned_from_suffix_zeroed( + bytes: B, + count: usize, + ) -> Option<(B, LayoutVerified)> { + map_suffix_tuple_zeroed(Self::new_slice_unaligned_from_suffix(bytes, count)) + } } impl<'a, B, T> LayoutVerified @@ -733,17 +1465,17 @@ where B: 'a + ByteSlice, T: FromBytes, { - /// Convert this `LayoutVerified` into a reference. + /// Converts this `LayoutVerified` into a reference. /// /// `into_ref` consumes the `LayoutVerified`, and returns a reference to /// `T`. pub fn into_ref(self) -> &'a T { - // NOTE: This is safe because `B` is guaranteed to live for the lifetime - // `'a`, meaning that a) the returned reference cannot outlive the `B` - // from which `self` was constructed and, b) no mutable methods on that - // `B` can be called during the lifetime of the returned reference. See - // the documentation on `deref_helper` for what invariants we are - // required to uphold. + // SAFETY: This is sound because `B` is guaranteed to live for the + // lifetime `'a`, meaning that a) the returned reference cannot outlive + // the `B` from which `self` was constructed and, b) no mutable methods + // on that `B` can be called during the lifetime of the returned + // reference. See the documentation on `deref_helper` for what + // invariants we are required to uphold. unsafe { self.deref_helper() } } } @@ -753,17 +1485,17 @@ where B: 'a + ByteSliceMut, T: FromBytes + AsBytes, { - /// Convert this `LayoutVerified` into a mutable reference. + /// Converts this `LayoutVerified` into a mutable reference. /// /// `into_mut` consumes the `LayoutVerified`, and returns a mutable /// reference to `T`. pub fn into_mut(mut self) -> &'a mut T { - // NOTE: This is safe because `B` is guaranteed to live for the lifetime - // `'a`, meaning that a) the returned reference cannot outlive the `B` - // from which `self` was constructed and, b) no other methods - mutable - // or immutable - on that `B` can be called during the lifetime of the - // returned reference. See the documentation on `deref_mut_helper` for - // what invariants we are required to uphold. + // SAFETY: This is sound because `B` is guaranteed to live for the + // lifetime `'a`, meaning that a) the returned reference cannot outlive + // the `B` from which `self` was constructed and, b) no other methods - + // mutable or immutable - on that `B` can be called during the lifetime + // of the returned reference. See the documentation on + // `deref_mut_helper` for what invariants we are required to uphold. unsafe { self.deref_mut_helper() } } } @@ -773,17 +1505,17 @@ where B: 'a + ByteSlice, T: FromBytes, { - /// Convert this `LayoutVerified` into a slice reference. + /// Converts this `LayoutVerified` into a slice reference. /// /// `into_slice` consumes the `LayoutVerified`, and returns a reference to /// `[T]`. pub fn into_slice(self) -> &'a [T] { - // NOTE: This is safe because `B` is guaranteed to live for the lifetime - // `'a`, meaning that a) the returned reference cannot outlive the `B` - // from which `self` was constructed and, b) no mutable methods on that - // `B` can be called during the lifetime of the returned reference. See - // the documentation on `deref_slice_helper` for what invariants we are - // required to uphold. + // SAFETY: This is sound because `B` is guaranteed to live for the + // lifetime `'a`, meaning that a) the returned reference cannot outlive + // the `B` from which `self` was constructed and, b) no mutable methods + // on that `B` can be called during the lifetime of the returned + // reference. See the documentation on `deref_slice_helper` for what + // invariants we are required to uphold. unsafe { self.deref_slice_helper() } } } @@ -793,17 +1525,18 @@ where B: 'a + ByteSliceMut, T: FromBytes + AsBytes, { - /// Convert this `LayoutVerified` into a mutable slice reference. + /// Converts this `LayoutVerified` into a mutable slice reference. /// - /// `into_mut_slice` consumes the `LayoutVerified`, and returns a mutable reference to - /// `[T]`. + /// `into_mut_slice` consumes the `LayoutVerified`, and returns a mutable + /// reference to `[T]`. pub fn into_mut_slice(mut self) -> &'a mut [T] { - // NOTE: This is safe because `B` is guaranteed to live for the lifetime - // `'a`, meaning that a) the returned reference cannot outlive the `B` - // from which `self` was constructed and, b) no other methods - mutable - // or immutable - on that `B` can be called during the lifetime of the - // returned reference. See the documentation on `deref_mut_slice_helper` - // for what invariants we are required to uphold. + // SAFETY: This is sound because `B` is guaranteed to live for the + // lifetime `'a`, meaning that a) the returned reference cannot outlive + // the `B` from which `self` was constructed and, b) no other methods - + // mutable or immutable - on that `B` can be called during the lifetime + // of the returned reference. See the documentation on + // `deref_mut_slice_helper` for what invariants we are required to + // uphold. unsafe { self.deref_mut_slice_helper() } } } @@ -813,7 +1546,7 @@ where B: ByteSlice, T: FromBytes, { - /// Create an immutable reference to `T` with a specific lifetime. + /// Creates an immutable reference to `T` with a specific lifetime. /// /// # Safety /// @@ -834,7 +1567,7 @@ where B: ByteSliceMut, T: FromBytes + AsBytes, { - /// Create a mutable reference to `T` with a specific lifetime. + /// Creates a mutable reference to `T` with a specific lifetime. /// /// # Safety /// @@ -855,7 +1588,7 @@ where B: ByteSlice, T: FromBytes, { - /// Create an immutable reference to `[T]` with a specific lifetime. + /// Creates an immutable reference to `[T]` with a specific lifetime. /// /// # Safety /// @@ -875,7 +1608,7 @@ where B: ByteSliceMut, T: FromBytes + AsBytes, { - /// Create a mutable reference to `[T]` with a specific lifetime. + /// Creates a mutable reference to `[T]` with a specific lifetime. /// /// # Safety /// @@ -891,23 +1624,69 @@ where } } +#[inline] fn aligned_to(bytes: &[u8], align: usize) -> bool { (bytes as *const _ as *const () as usize) % align == 0 } impl LayoutVerified where - B: ByteSliceMut, + B: ByteSlice, T: ?Sized, { - // Get the underlying bytes mutably. + /// Gets the underlying bytes. #[inline] - pub fn bytes_mut(&mut self) -> &mut [u8] { - &mut self.0 + pub fn bytes(&self) -> &[u8] { + &self.0 } } -impl Deref for LayoutVerified +impl LayoutVerified +where + B: ByteSliceMut, + T: ?Sized, +{ + /// Gets the underlying bytes mutably. + #[inline] + pub fn bytes_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +impl LayoutVerified +where + B: ByteSlice, + T: FromBytes, +{ + /// Reads a copy of `T`. + #[inline] + pub fn read(&self) -> T { + // SAFETY: Because of the invariants on `LayoutVerified`, we know that + // `self.0` is at least `size_of::()` bytes long, and that it is at + // least as aligned as `align_of::()`. Because `T: FromBytes`, it is + // sound to interpret these bytes as a `T`. + unsafe { ptr::read(self.0.as_ptr() as *const T) } + } +} + +impl LayoutVerified +where + B: ByteSliceMut, + T: AsBytes, +{ + /// Writes the bytes of `t` and then forgets `t`. + #[inline] + pub fn write(&mut self, t: T) { + // SAFETY: Because of the invariants on `LayoutVerified`, we know that + // `self.0` is at least `size_of::()` bytes long, and that it is at + // least as aligned as `align_of::()`. Writing `t` to the buffer will + // allow all of the bytes of `t` to be accessed as a `[u8]`, but because + // `T: AsBytes`, we know this is sound. + unsafe { ptr::write(self.0.as_mut_ptr() as *mut T, t) } + } +} + +impl Deref for LayoutVerified where B: ByteSlice, T: FromBytes, @@ -915,10 +1694,10 @@ where type Target = T; #[inline] fn deref(&self) -> &T { - // NOTE: This is safe because the lifetime of `self` is the same as the - // lifetime of the return value, meaning that a) the returned reference - // cannot outlive `self` and, b) no mutable methods on `self` can be - // called during the lifetime of the returned reference. See the + // SAFETY: This is sound because the lifetime of `self` is the same as + // the lifetime of the return value, meaning that a) the returned + // reference cannot outlive `self` and, b) no mutable methods on `self` + // can be called during the lifetime of the returned reference. See the // documentation on `deref_helper` for what invariants we are required // to uphold. unsafe { self.deref_helper() } @@ -932,10 +1711,10 @@ where { #[inline] fn deref_mut(&mut self) -> &mut T { - // NOTE: This is safe because the lifetime of `self` is the same as the - // lifetime of the return value, meaning that a) the returned reference - // cannot outlive `self` and, b) no other methods on `self` can be - // called during the lifetime of the returned reference. See the + // SAFETY: This is sound because the lifetime of `self` is the same as + // the lifetime of the return value, meaning that a) the returned + // reference cannot outlive `self` and, b) no other methods on `self` + // can be called during the lifetime of the returned reference. See the // documentation on `deref_mut_helper` for what invariants we are // required to uphold. unsafe { self.deref_mut_helper() } @@ -950,10 +1729,10 @@ where type Target = [T]; #[inline] fn deref(&self) -> &[T] { - // NOTE: This is safe because the lifetime of `self` is the same as the - // lifetime of the return value, meaning that a) the returned reference - // cannot outlive `self` and, b) no mutable methods on `self` can be - // called during the lifetime of the returned reference. See the + // SAFETY: This is sound because the lifetime of `self` is the same as + // the lifetime of the return value, meaning that a) the returned + // reference cannot outlive `self` and, b) no mutable methods on `self` + // can be called during the lifetime of the returned reference. See the // documentation on `deref_slice_helper` for what invariants we are // required to uphold. unsafe { self.deref_slice_helper() } @@ -967,10 +1746,10 @@ where { #[inline] fn deref_mut(&mut self) -> &mut [T] { - // NOTE: This is safe because the lifetime of `self` is the same as the - // lifetime of the return value, meaning that a) the returned reference - // cannot outlive `self` and, b) no other methods on `self` can be - // called during the lifetime of the returned reference. See the + // SAFETY: This is sound because the lifetime of `self` is the same as + // the lifetime of the return value, meaning that a) the returned + // reference cannot outlive `self` and, b) no other methods on `self` + // can be called during the lifetime of the returned reference. See the // documentation on `deref_mut_slice_helper` for what invariants we are // required to uphold. unsafe { self.deref_mut_slice_helper() } @@ -989,6 +1768,19 @@ where } } +impl Display for LayoutVerified +where + B: ByteSlice, + T: FromBytes, + [T]: Display, +{ + #[inline] + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + let inner: &[T] = self; + inner.fmt(fmt) + } +} + impl Debug for LayoutVerified where B: ByteSlice, @@ -1001,28 +1793,103 @@ where } } -impl Display for LayoutVerified +impl Debug for LayoutVerified where B: ByteSlice, - T: FromBytes, - [T]: Display, + T: FromBytes + Debug, { #[inline] fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { let inner: &[T] = self; - inner.fmt(fmt) + fmt.debug_tuple("LayoutVerified").field(&inner).finish() } } -impl Debug for LayoutVerified +impl Eq for LayoutVerified where B: ByteSlice, - T: FromBytes + Debug, + T: FromBytes + Eq, +{ +} + +impl Eq for LayoutVerified +where + B: ByteSlice, + T: FromBytes + Eq, +{ +} + +impl PartialEq for LayoutVerified +where + B: ByteSlice, + T: FromBytes + PartialEq, { #[inline] - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other.deref()) + } +} + +impl PartialEq for LayoutVerified +where + B: ByteSlice, + T: FromBytes + PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.deref().eq(other.deref()) + } +} + +impl Ord for LayoutVerified +where + B: ByteSlice, + T: FromBytes + Ord, +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + let inner: &T = self; + let other_inner: &T = other; + inner.cmp(other_inner) + } +} + +impl Ord for LayoutVerified +where + B: ByteSlice, + T: FromBytes + Ord, +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { let inner: &[T] = self; - fmt.debug_tuple("LayoutVerified").field(&inner).finish() + let other_inner: &[T] = other; + inner.cmp(other_inner) + } +} + +impl PartialOrd for LayoutVerified +where + B: ByteSlice, + T: FromBytes + PartialOrd, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + let inner: &T = self; + let other_inner: &T = other; + inner.partial_cmp(other_inner) + } +} + +impl PartialOrd for LayoutVerified +where + B: ByteSlice, + T: FromBytes + PartialOrd, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + let inner: &[T] = self; + let other_inner: &[T] = other; + inner.partial_cmp(other_inner) } } @@ -1045,80 +1912,315 @@ mod sealed { // unsafe code. Thus, we seal them and implement it only for known-good // reference types. For the same reason, they're unsafe traits. +#[allow(clippy::missing_safety_doc)] // TODO(fxbug.dev/99068) /// A mutable or immutable reference to a byte slice. /// /// `ByteSlice` abstracts over the mutability of a byte slice reference, and is /// implemented for various special reference types such as `Ref<[u8]>` and /// `RefMut<[u8]>`. +/// +/// Note that, while it would be technically possible, `ByteSlice` is not +/// implemented for [`Vec`], as the only way to implement the [`split_at`] +/// method would involve reallocation, and `split_at` must be a very cheap +/// operation in order for the utilities in this crate to perform as designed. +/// +/// [`Vec`]: std::vec::Vec +/// [`split_at`]: crate::ByteSlice::split_at pub unsafe trait ByteSlice: Deref + Sized + self::sealed::Sealed { - fn as_ptr(&self) -> *const u8; + /// Gets a raw pointer to the first byte in the slice. + #[inline] + fn as_ptr(&self) -> *const u8 { + <[u8]>::as_ptr(self) + } + + /// Splits the slice at the midpoint. + /// + /// `x.split_at(mid)` returns `x[..mid]` and `x[mid..]`. + /// + /// # Panics + /// + /// `x.split_at(mid)` panics if `mid > x.len()`. fn split_at(self, mid: usize) -> (Self, Self); } +#[allow(clippy::missing_safety_doc)] // TODO(fxbug.dev/99068) /// A mutable reference to a byte slice. /// /// `ByteSliceMut` abstracts over various ways of storing a mutable reference to /// a byte slice, and is implemented for various special reference types such as /// `RefMut<[u8]>`. pub unsafe trait ByteSliceMut: ByteSlice + DerefMut { - fn as_mut_ptr(&mut self) -> *mut u8; + /// Gets a mutable raw pointer to the first byte in the slice. + #[inline] + fn as_mut_ptr(&mut self) -> *mut u8 { + <[u8]>::as_mut_ptr(self) + } } unsafe impl<'a> ByteSlice for &'a [u8] { - fn as_ptr(&self) -> *const u8 { - <[u8]>::as_ptr(self) - } + #[inline] fn split_at(self, mid: usize) -> (Self, Self) { <[u8]>::split_at(self, mid) } } unsafe impl<'a> ByteSlice for &'a mut [u8] { - fn as_ptr(&self) -> *const u8 { - <[u8]>::as_ptr(self) - } + #[inline] fn split_at(self, mid: usize) -> (Self, Self) { <[u8]>::split_at_mut(self, mid) } } unsafe impl<'a> ByteSlice for Ref<'a, [u8]> { - fn as_ptr(&self) -> *const u8 { - <[u8]>::as_ptr(self) - } + #[inline] fn split_at(self, mid: usize) -> (Self, Self) { Ref::map_split(self, |slice| <[u8]>::split_at(slice, mid)) } } unsafe impl<'a> ByteSlice for RefMut<'a, [u8]> { - fn as_ptr(&self) -> *const u8 { - <[u8]>::as_ptr(self) - } + #[inline] fn split_at(self, mid: usize) -> (Self, Self) { RefMut::map_split(self, |slice| <[u8]>::split_at_mut(slice, mid)) } } -unsafe impl<'a> ByteSliceMut for &'a mut [u8] { - fn as_mut_ptr(&mut self) -> *mut u8 { - <[u8]>::as_mut_ptr(self) +unsafe impl<'a> ByteSliceMut for &'a mut [u8] {} +unsafe impl<'a> ByteSliceMut for RefMut<'a, [u8]> {} + +#[cfg(feature = "alloc")] +mod alloc_support { + use alloc::vec::Vec; + + use super::*; + + /// Extends a `Vec` by pushing `additional` new items onto the end of the + /// vector. The new items are initialized with zeroes. + /// + /// # Panics + /// + /// Panics if `Vec::reserve(additional)` fails to reserve enough memory. + pub fn extend_vec_zeroed(v: &mut Vec, additional: usize) { + insert_vec_zeroed(v, v.len(), additional); } -} -unsafe impl<'a> ByteSliceMut for RefMut<'a, [u8]> { - fn as_mut_ptr(&mut self) -> *mut u8 { - <[u8]>::as_mut_ptr(self) + + /// Inserts `additional` new items into `Vec` at `position`. + /// The new items are initialized with zeroes. + /// + /// # Panics + /// + /// * Panics if `position > v.len()`. + /// * Panics if `Vec::reserve(additional)` fails to reserve enough memory. + pub fn insert_vec_zeroed(v: &mut Vec, position: usize, additional: usize) { + assert!(position <= v.len()); + v.reserve(additional); + // SAFETY: The `reserve` call guarantees that these cannot overflow: + // * `ptr.add(position)` + // * `position + additional` + // * `v.len() + additional` + // + // `v.len() - position` cannot overflow because we asserted that + // `position <= v.len()`. + unsafe { + // This is a potentially overlapping copy. + let ptr = v.as_mut_ptr(); + ptr.add(position).copy_to(ptr.add(position + additional), v.len() - position); + ptr.add(position).write_bytes(0, additional); + v.set_len(v.len() + additional); + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_extend_vec_zeroed() { + // Test extending when there is an existing allocation. + let mut v: Vec = Vec::with_capacity(3); + v.push(100); + v.push(200); + v.push(300); + extend_vec_zeroed(&mut v, 3); + assert_eq!(v.len(), 6); + assert_eq!(&*v, &[100, 200, 300, 0, 0, 0]); + drop(v); + + // Test extending when there is no existing allocation. + let mut v: Vec = Vec::new(); + extend_vec_zeroed(&mut v, 3); + assert_eq!(v.len(), 3); + assert_eq!(&*v, &[0, 0, 0]); + drop(v); + } + + #[test] + fn test_extend_vec_zeroed_zst() { + // Test extending when there is an existing (fake) allocation. + let mut v: Vec<()> = Vec::with_capacity(3); + v.push(()); + v.push(()); + v.push(()); + extend_vec_zeroed(&mut v, 3); + assert_eq!(v.len(), 6); + assert_eq!(&*v, &[(), (), (), (), (), ()]); + drop(v); + + // Test extending when there is no existing (fake) allocation. + let mut v: Vec<()> = Vec::new(); + extend_vec_zeroed(&mut v, 3); + assert_eq!(&*v, &[(), (), ()]); + drop(v); + } + + #[test] + fn test_insert_vec_zeroed() { + // Insert at start (no existing allocation). + let mut v: Vec = Vec::new(); + insert_vec_zeroed(&mut v, 0, 2); + assert_eq!(v.len(), 2); + assert_eq!(&*v, &[0, 0]); + drop(v); + + // Insert at start. + let mut v: Vec = Vec::with_capacity(3); + v.push(100); + v.push(200); + v.push(300); + insert_vec_zeroed(&mut v, 0, 2); + assert_eq!(v.len(), 5); + assert_eq!(&*v, &[0, 0, 100, 200, 300]); + drop(v); + + // Insert at middle. + let mut v: Vec = Vec::with_capacity(3); + v.push(100); + v.push(200); + v.push(300); + insert_vec_zeroed(&mut v, 1, 1); + assert_eq!(v.len(), 4); + assert_eq!(&*v, &[100, 0, 200, 300]); + drop(v); + + // Insert at end. + let mut v: Vec = Vec::with_capacity(3); + v.push(100); + v.push(200); + v.push(300); + insert_vec_zeroed(&mut v, 3, 1); + assert_eq!(v.len(), 4); + assert_eq!(&*v, &[100, 200, 300, 0]); + drop(v); + } + + #[test] + fn test_insert_vec_zeroed_zst() { + // Insert at start (no existing fake allocation). + let mut v: Vec<()> = Vec::new(); + insert_vec_zeroed(&mut v, 0, 2); + assert_eq!(v.len(), 2); + assert_eq!(&*v, &[(), ()]); + drop(v); + + // Insert at start. + let mut v: Vec<()> = Vec::with_capacity(3); + v.push(()); + v.push(()); + v.push(()); + insert_vec_zeroed(&mut v, 0, 2); + assert_eq!(v.len(), 5); + assert_eq!(&*v, &[(), (), (), (), ()]); + drop(v); + + // Insert at middle. + let mut v: Vec<()> = Vec::with_capacity(3); + v.push(()); + v.push(()); + v.push(()); + insert_vec_zeroed(&mut v, 1, 1); + assert_eq!(v.len(), 4); + assert_eq!(&*v, &[(), (), (), ()]); + drop(v); + + // Insert at end. + let mut v: Vec<()> = Vec::with_capacity(3); + v.push(()); + v.push(()); + v.push(()); + insert_vec_zeroed(&mut v, 3, 1); + assert_eq!(v.len(), 4); + assert_eq!(&*v, &[(), (), (), ()]); + drop(v); + } + + #[test] + fn test_new_box_zeroed() { + assert_eq!(*u64::new_box_zeroed(), 0); + } + + #[test] + fn test_new_box_zeroed_array() { + drop(<[u32; 0x1000]>::new_box_zeroed()); + } + + #[test] + fn test_new_box_zeroed_zst() { + // This test exists in order to exercise unsafe code, especially + // when running under Miri. + #[allow(clippy::unit_cmp)] + { + assert_eq!(*<()>::new_box_zeroed(), ()); + } + } + + #[test] + fn test_new_box_slice_zeroed() { + let mut s: Box<[u64]> = u64::new_box_slice_zeroed(3); + assert_eq!(s.len(), 3); + assert_eq!(&*s, &[0, 0, 0]); + s[1] = 3; + assert_eq!(&*s, &[0, 3, 0]); + } + + #[test] + fn test_new_box_slice_zeroed_empty() { + let s: Box<[u64]> = u64::new_box_slice_zeroed(0); + assert_eq!(s.len(), 0); + } + + #[test] + fn test_new_box_slice_zeroed_zst() { + let mut s: Box<[()]> = <()>::new_box_slice_zeroed(3); + assert_eq!(s.len(), 3); + assert!(s.get(10).is_none()); + // This test exists in order to exercise unsafe code, especially + // when running under Miri. + #[allow(clippy::unit_cmp)] + { + assert_eq!(s[1], ()); + } + s[2] = (); + } + + #[test] + fn test_new_box_slice_zeroed_zst_empty() { + let s: Box<[()]> = <()>::new_box_slice_zeroed(0); + assert_eq!(s.len(), 0); + } } } +#[cfg(feature = "alloc")] +#[doc(inline)] +pub use alloc_support::*; + #[cfg(test)] mod tests { #![allow(clippy::unreadable_literal)] use core::ops::Deref; - use core::ptr; use super::*; - // B should be [u8; N]. T will require that the entire structure is aligned - // to the alignment of T. + // `B` should be `[u8; N]`. `T` will require that the entire structure is + // aligned to the alignment of `T`. #[derive(Default)] struct AlignedBuffer { buf: B, @@ -1131,20 +2233,75 @@ mod tests { } } - // convert a u64 to bytes using this platform's endianness + // Converts a `u64` to bytes using this platform's endianness. fn u64_to_bytes(u: u64) -> [u8; 8] { unsafe { ptr::read(&u as *const u64 as *const [u8; 8]) } } - // convert a u128 to bytes using this platform's endianness - fn u128_to_bytes(u: u128) -> [u8; 16] { - unsafe { ptr::read(&u as *const u128 as *const [u8; 16]) } + #[test] + fn test_read_write() { + const VAL: u64 = 0x12345678; + #[cfg(target_endian = "big")] + const VAL_BYTES: [u8; 8] = VAL.to_be_bytes(); + #[cfg(target_endian = "little")] + const VAL_BYTES: [u8; 8] = VAL.to_le_bytes(); + + // Test `FromBytes::{read_from, read_from_prefix, read_from_suffix}`. + + assert_eq!(u64::read_from(&VAL_BYTES[..]), Some(VAL)); + // The first 8 bytes are from `VAL_BYTES` and the second 8 bytes are all + // zeroes. + let bytes_with_prefix: [u8; 16] = transmute!([VAL_BYTES, [0; 8]]); + assert_eq!(u64::read_from_prefix(&bytes_with_prefix[..]), Some(VAL)); + assert_eq!(u64::read_from_suffix(&bytes_with_prefix[..]), Some(0)); + // The first 8 bytes are all zeroes and the second 8 bytes are from + // `VAL_BYTES` + let bytes_with_suffix: [u8; 16] = transmute!([[0; 8], VAL_BYTES]); + assert_eq!(u64::read_from_prefix(&bytes_with_suffix[..]), Some(0)); + assert_eq!(u64::read_from_suffix(&bytes_with_suffix[..]), Some(VAL)); + + // Test `AsBytes::{write_to, write_to_prefix, write_to_suffix}`. + + let mut bytes = [0u8; 8]; + assert_eq!(VAL.write_to(&mut bytes[..]), Some(())); + assert_eq!(bytes, VAL_BYTES); + let mut bytes = [0u8; 16]; + assert_eq!(VAL.write_to_prefix(&mut bytes[..]), Some(())); + let want: [u8; 16] = transmute!([VAL_BYTES, [0; 8]]); + assert_eq!(bytes, want); + let mut bytes = [0u8; 16]; + assert_eq!(VAL.write_to_suffix(&mut bytes[..]), Some(())); + let want: [u8; 16] = transmute!([[0; 8], VAL_BYTES]); + assert_eq!(bytes, want); + } + + #[test] + fn test_transmute() { + // Test that memory is transmuted as expected. + let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7]; + let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]]; + let x: [[u8; 2]; 4] = transmute!(array_of_u8s); + assert_eq!(x, array_of_arrays); + let x: [u8; 8] = transmute!(array_of_arrays); + assert_eq!(x, array_of_u8s); + + // Test that the source expression's value is forgotten rather than + // dropped. + #[derive(AsBytes)] + #[repr(transparent)] + struct PanicOnDrop(()); + impl Drop for PanicOnDrop { + fn drop(&mut self) { + panic!("PanicOnDrop::drop"); + } + } + let _: () = transmute!(PanicOnDrop(())); } #[test] fn test_address() { - // test that the Deref and DerefMut implementations return a reference which - // points to the right region of memory + // Test that the `Deref` and `DerefMut` implementations return a + // reference which points to the right region of memory. let buf = [0]; let lv = LayoutVerified::<_, u8>::new(&buf[..]).unwrap(); @@ -1159,91 +2316,109 @@ mod tests { assert_eq!(buf_ptr, deref_ptr); } - // verify that values written to a LayoutVerified are properly shared - // between the typed and untyped representations + // Verify that values written to a `LayoutVerified` are properly shared + // between the typed and untyped representations, that reads via `deref` and + // `read` behave the same, and that writes via `deref_mut` and `write` + // behave the same. fn test_new_helper<'a>(mut lv: LayoutVerified<&'a mut [u8], u64>) { // assert that the value starts at 0 assert_eq!(*lv, 0); + assert_eq!(lv.read(), 0); - // assert that values written to the typed value are reflected in the - // byte slice + // Assert that values written to the typed value are reflected in the + // byte slice. const VAL1: u64 = 0xFF00FF00FF00FF00; *lv = VAL1; assert_eq!(lv.bytes(), &u64_to_bytes(VAL1)); + *lv = 0; + lv.write(VAL1); + assert_eq!(lv.bytes(), &u64_to_bytes(VAL1)); - // assert that values written to the byte slice are reflected in the - // typed value - const VAL2: u64 = !VAL1; // different from VAL1 + // Assert that values written to the byte slice are reflected in the + // typed value. + const VAL2: u64 = !VAL1; // different from `VAL1` lv.bytes_mut().copy_from_slice(&u64_to_bytes(VAL2)[..]); assert_eq!(*lv, VAL2); + assert_eq!(lv.read(), VAL2); } - // verify that values written to a LayoutVerified are properly shared + // Verify that values written to a `LayoutVerified` are properly shared // between the typed and untyped representations; pass a value with - // byte length 16/typed length 2 - fn test_new_helper_slice<'a>(mut lv: LayoutVerified<&'a mut [u8], [u64]>) { - // assert that the value starts at [0, 0] - assert_eq!(*lv, [0, 0]); - - // assert that values written to the typed value are reflected in the - // byte slice + // `typed_len` `u64`s backed by an array of `typed_len * 8` bytes. + fn test_new_helper_slice<'a>(mut lv: LayoutVerified<&'a mut [u8], [u64]>, typed_len: usize) { + // Assert that the value starts out zeroed. + assert_eq!(&*lv, vec![0; typed_len].as_slice()); + + // Check the backing storage is the exact same slice. + let untyped_len = typed_len * 8; + assert_eq!(lv.bytes().len(), untyped_len); + assert_eq!(lv.bytes().as_ptr(), lv.as_ptr() as *const u8); + + // Assert that values written to the typed value are reflected in the + // byte slice. const VAL1: u64 = 0xFF00FF00FF00FF00; - const VAL1_DOUBLED: u128 = 0xFF00FF00FF00FF00FF00FF00FF00FF00; - lv[0] = VAL1; - lv[1] = VAL1; - assert_eq!(lv.bytes(), &u128_to_bytes(VAL1_DOUBLED)); + for typed in &mut *lv { + *typed = VAL1; + } + assert_eq!(lv.bytes(), VAL1.to_ne_bytes().repeat(typed_len).as_slice()); - // assert that values written to the byte slice are reflected in the - // typed value + // Assert that values written to the byte slice are reflected in the + // typed value. const VAL2: u64 = !VAL1; // different from VAL1 - const VAL2_DOUBLED: u128 = !VAL1_DOUBLED; - lv.bytes_mut().copy_from_slice(&u128_to_bytes(VAL2_DOUBLED)[..]); - assert_eq!(*lv, [VAL2, VAL2]); + lv.bytes_mut().copy_from_slice(&VAL2.to_ne_bytes().repeat(typed_len)); + assert!(lv.iter().copied().all(|x| x == VAL2)); } - // verify that values written to a LayoutVerified are properly shared - // between the typed and untyped representations + // Verify that values written to a `LayoutVerified` are properly shared + // between the typed and untyped representations, that reads via `deref` and + // `read` behave the same, and that writes via `deref_mut` and `write` + // behave the same. fn test_new_helper_unaligned<'a>(mut lv: LayoutVerified<&'a mut [u8], [u8; 8]>) { // assert that the value starts at 0 assert_eq!(*lv, [0; 8]); + assert_eq!(lv.read(), [0; 8]); - // assert that values written to the typed value are reflected in the - // byte slice + // Assert that values written to the typed value are reflected in the + // byte slice. const VAL1: [u8; 8] = [0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00]; *lv = VAL1; assert_eq!(lv.bytes(), &VAL1); - - // assert that values written to the byte slice are reflected in the - // typed value - const VAL2: [u8; 8] = [0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF]; // different from VAL1 - lv.bytes_mut().copy_from_slice(&VAL2[..]); - assert_eq!(*lv, VAL2); - } - - // verify that values written to a LayoutVerified are properly shared - // between the typed and untyped representations; pass a value with - // length 16 - fn test_new_helper_slice_unaligned<'a>(mut lv: LayoutVerified<&'a mut [u8], [u8]>) { - // assert that the value starts at [0; 16] - assert_eq!(*lv, [0u8; 16][..]); - - // assert that values written to the typed value are reflected in the - // byte slice - const VAL1: [u8; 16] = [ - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, - ]; - lv.copy_from_slice(&VAL1[..]); + *lv = [0; 8]; + lv.write(VAL1); assert_eq!(lv.bytes(), &VAL1); - // assert that values written to the byte slice are reflected in the - // typed value - const VAL2: [u8; 16] = [ - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, - ]; + // Assert that values written to the byte slice are reflected in the + // typed value. + const VAL2: [u8; 8] = [0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF]; // different from VAL1 lv.bytes_mut().copy_from_slice(&VAL2[..]); assert_eq!(*lv, VAL2); + assert_eq!(lv.read(), VAL2); + } + + // Verify that values written to a `LayoutVerified` are properly shared + // between the typed and untyped representations; pass a value with `len` + // `u8`s backed by an array of `len` bytes. + fn test_new_helper_slice_unaligned<'a>(mut lv: LayoutVerified<&'a mut [u8], [u8]>, len: usize) { + // Assert that the value starts out zeroed. + assert_eq!(&*lv, vec![0u8; len].as_slice()); + + // Check the backing storage is the exact same slice. + assert_eq!(lv.bytes().len(), len); + assert_eq!(lv.bytes().as_ptr(), lv.as_ptr()); + + // Assert that values written to the typed value are reflected in the + // byte slice. + let mut expected_bytes = [0xFF, 0x00].iter().copied().cycle().take(len).collect::>(); + lv.copy_from_slice(&expected_bytes); + assert_eq!(lv.bytes(), expected_bytes.as_slice()); + + // Assert that values written to the byte slice are reflected in the + // typed value. + for byte in &mut expected_bytes { + *byte = !*byte; // different from `expected_len` + } + lv.bytes_mut().copy_from_slice(&expected_bytes); + assert_eq!(&*lv, expected_bytes.as_slice()); } #[test] @@ -1255,14 +2430,14 @@ mod tests { // new_slice. Test that xxx_zeroed behaves the same, and zeroes the // memory. - // a buffer with an alignment of 8 + // A buffer with an alignment of 8. let mut buf = AlignedBuffer::::default(); - // buf.buf should be aligned to 8, so this should always succeed + // `buf.buf` should be aligned to 8, so this should always succeed. test_new_helper(LayoutVerified::<_, u64>::new(&mut buf.buf[..]).unwrap()); buf.buf = [0xFFu8; 8]; test_new_helper(LayoutVerified::<_, u64>::new_zeroed(&mut buf.buf[..]).unwrap()); { - // in a block so that lv and suffix don't live too long + // In a block so that `lv` and `suffix` don't live too long. buf.clear_buf(); let (lv, suffix) = LayoutVerified::<_, u64>::new_from_prefix(&mut buf.buf[..]).unwrap(); assert!(suffix.is_empty()); @@ -1289,26 +2464,58 @@ mod tests { test_new_helper(lv); } - // a buffer with alignment 8 and length 16 + // A buffer with alignment 8 and length 16. let mut buf = AlignedBuffer::::default(); - // buf.buf should be aligned to 8 and have a length which is a multiple - // of size_of::(), so this should always succeed - test_new_helper_slice(LayoutVerified::<_, [u64]>::new_slice(&mut buf.buf[..]).unwrap()); + // `buf.buf` should be aligned to 8 and have a length which is a + // multiple of `size_of::()`, so this should always succeed. + test_new_helper_slice(LayoutVerified::<_, [u64]>::new_slice(&mut buf.buf[..]).unwrap(), 2); buf.buf = [0xFFu8; 16]; test_new_helper_slice( LayoutVerified::<_, [u64]>::new_slice_zeroed(&mut buf.buf[..]).unwrap(), + 2, ); + + { + buf.clear_buf(); + let (lv, suffix) = + LayoutVerified::<_, [u64]>::new_slice_from_prefix(&mut buf.buf[..], 1).unwrap(); + assert_eq!(suffix, [0; 8]); + test_new_helper_slice(lv, 1); + } + { + buf.buf = [0xFFu8; 16]; + let (lv, suffix) = + LayoutVerified::<_, [u64]>::new_slice_from_prefix_zeroed(&mut buf.buf[..], 1) + .unwrap(); + assert_eq!(suffix, [0xFF; 8]); + test_new_helper_slice(lv, 1); + } + { + buf.clear_buf(); + let (prefix, lv) = + LayoutVerified::<_, [u64]>::new_slice_from_suffix(&mut buf.buf[..], 1).unwrap(); + assert_eq!(prefix, [0; 8]); + test_new_helper_slice(lv, 1); + } + { + buf.buf = [0xFFu8; 16]; + let (prefix, lv) = + LayoutVerified::<_, [u64]>::new_slice_from_suffix_zeroed(&mut buf.buf[..], 1) + .unwrap(); + assert_eq!(prefix, [0xFF; 8]); + test_new_helper_slice(lv, 1); + } } #[test] fn test_new_unaligned_sized() { // Test that an unaligned, properly-sized buffer works for - // new_unaligned, new_unaligned_from_prefix, and - // new_unaligned_from_suffix, and that new_unaligned_from_prefix - // new_unaligned_from_suffix return empty slices. Test that an unaligned - // buffer whose length is a multiple of the element size works for - // new_slice. Test that xxx_zeroed behaves the same, and zeroes the - // memory. + // `new_unaligned`, `new_unaligned_from_prefix`, and + // `new_unaligned_from_suffix`, and that `new_unaligned_from_prefix` + // `new_unaligned_from_suffix` return empty slices. Test that an + // unaligned buffer whose length is a multiple of the element size works + // for `new_slice`. Test that `xxx_zeroed` behaves the same, and zeroes + // the memory. let mut buf = [0u8; 8]; test_new_helper_unaligned( @@ -1319,7 +2526,7 @@ mod tests { LayoutVerified::<_, [u8; 8]>::new_unaligned_zeroed(&mut buf[..]).unwrap(), ); { - // in a block so that lv and suffix don't live too long + // In a block so that `lv` and `suffix` don't live too long. buf = [0u8; 8]; let (lv, suffix) = LayoutVerified::<_, [u8; 8]>::new_unaligned_from_prefix(&mut buf[..]).unwrap(); @@ -1351,54 +2558,89 @@ mod tests { } let mut buf = [0u8; 16]; - // buf.buf should be aligned to 8 and have a length which is a multiple - // of size_of::(), so this should always succeed + // `buf.buf` should be aligned to 8 and have a length which is a + // multiple of `size_of::()`, so this should always succeed. test_new_helper_slice_unaligned( - LayoutVerified::<_, [u8]>::new_slice(&mut buf[..]).unwrap(), + LayoutVerified::<_, [u8]>::new_slice_unaligned(&mut buf[..]).unwrap(), + 16, ); buf = [0xFFu8; 16]; test_new_helper_slice_unaligned( - LayoutVerified::<_, [u8]>::new_slice_zeroed(&mut buf[..]).unwrap(), + LayoutVerified::<_, [u8]>::new_slice_unaligned_zeroed(&mut buf[..]).unwrap(), + 16, ); + + { + buf = [0u8; 16]; + let (lv, suffix) = + LayoutVerified::<_, [u8]>::new_slice_unaligned_from_prefix(&mut buf[..], 8) + .unwrap(); + assert_eq!(suffix, [0; 8]); + test_new_helper_slice_unaligned(lv, 8); + } + { + buf = [0xFFu8; 16]; + let (lv, suffix) = + LayoutVerified::<_, [u8]>::new_slice_unaligned_from_prefix_zeroed(&mut buf[..], 8) + .unwrap(); + assert_eq!(suffix, [0xFF; 8]); + test_new_helper_slice_unaligned(lv, 8); + } + { + buf = [0u8; 16]; + let (prefix, lv) = + LayoutVerified::<_, [u8]>::new_slice_unaligned_from_suffix(&mut buf[..], 8) + .unwrap(); + assert_eq!(prefix, [0; 8]); + test_new_helper_slice_unaligned(lv, 8); + } + { + buf = [0xFFu8; 16]; + let (prefix, lv) = + LayoutVerified::<_, [u8]>::new_slice_unaligned_from_suffix_zeroed(&mut buf[..], 8) + .unwrap(); + assert_eq!(prefix, [0xFF; 8]); + test_new_helper_slice_unaligned(lv, 8); + } } #[test] fn test_new_oversized() { // Test that a properly-aligned, overly-sized buffer works for - // new_from_prefix and new_from_suffix, and that they return the - // remainder and prefix of the slice respectively. Test that xxx_zeroed - // behaves the same, and zeroes the memory. + // `new_from_prefix` and `new_from_suffix`, and that they return the + // remainder and prefix of the slice respectively. Test that + // `xxx_zeroed` behaves the same, and zeroes the memory. let mut buf = AlignedBuffer::::default(); { - // in a block so that lv and suffix don't live too long - // buf.buf should be aligned to 8, so this should always succeed + // In a block so that `lv` and `suffix` don't live too long. + // `buf.buf` should be aligned to 8, so this should always succeed. let (lv, suffix) = LayoutVerified::<_, u64>::new_from_prefix(&mut buf.buf[..]).unwrap(); assert_eq!(suffix.len(), 8); test_new_helper(lv); } { buf.buf = [0xFFu8; 16]; - // buf.buf should be aligned to 8, so this should always succeed + // `buf.buf` should be aligned to 8, so this should always succeed. let (lv, suffix) = LayoutVerified::<_, u64>::new_from_prefix_zeroed(&mut buf.buf[..]).unwrap(); - // assert that the suffix wasn't zeroed + // Assert that the suffix wasn't zeroed. assert_eq!(suffix, &[0xFFu8; 8]); test_new_helper(lv); } { buf.clear_buf(); - // buf.buf should be aligned to 8, so this should always succeed + // `buf.buf` should be aligned to 8, so this should always succeed. let (prefix, lv) = LayoutVerified::<_, u64>::new_from_suffix(&mut buf.buf[..]).unwrap(); assert_eq!(prefix.len(), 8); test_new_helper(lv); } { buf.buf = [0xFFu8; 16]; - // buf.buf should be aligned to 8, so this should always succeed + // `buf.buf` should be aligned to 8, so this should always succeed. let (prefix, lv) = LayoutVerified::<_, u64>::new_from_suffix_zeroed(&mut buf.buf[..]).unwrap(); - // assert that the prefix wasn't zeroed + // Assert that the prefix wasn't zeroed. assert_eq!(prefix, &[0xFFu8; 8]); test_new_helper(lv); } @@ -1407,13 +2649,13 @@ mod tests { #[test] fn test_new_unaligned_oversized() { // Test than an unaligned, overly-sized buffer works for - // new_unaligned_from_prefix and new_unaligned_from_suffix, and that + // `new_unaligned_from_prefix` and `new_unaligned_from_suffix`, and that // they return the remainder and prefix of the slice respectively. Test - // that xxx_zeroed behaves the same, and zeroes the memory. + // that `xxx_zeroed` behaves the same, and zeroes the memory. let mut buf = [0u8; 16]; { - // in a block so that lv and suffix don't live too long + // In a block so that `lv` and `suffix` don't live too long. let (lv, suffix) = LayoutVerified::<_, [u8; 8]>::new_unaligned_from_prefix(&mut buf[..]).unwrap(); assert_eq!(suffix.len(), 8); @@ -1424,7 +2666,7 @@ mod tests { let (lv, suffix) = LayoutVerified::<_, [u8; 8]>::new_unaligned_from_prefix_zeroed(&mut buf[..]) .unwrap(); - // assert that the suffix wasn't zeroed + // Assert that the suffix wasn't zeroed. assert_eq!(suffix, &[0xFF; 8]); test_new_helper_unaligned(lv); } @@ -1440,30 +2682,32 @@ mod tests { let (prefix, lv) = LayoutVerified::<_, [u8; 8]>::new_unaligned_from_suffix_zeroed(&mut buf[..]) .unwrap(); - // assert that the prefix wasn't zeroed + // Assert that the prefix wasn't zeroed. assert_eq!(prefix, &[0xFF; 8]); test_new_helper_unaligned(lv); } } #[test] - #[allow(clippy::cyclomatic_complexity)] + #[allow(clippy::cognitive_complexity)] fn test_new_error() { - // fail because the buffer is too large + // Fail because the buffer is too large. - // a buffer with an alignment of 8 + // A buffer with an alignment of 8. let mut buf = AlignedBuffer::::default(); - // buf.buf should be aligned to 8, so only the length check should fail + // `buf.buf` should be aligned to 8, so only the length check should + // fail. assert!(LayoutVerified::<_, u64>::new(&buf.buf[..]).is_none()); assert!(LayoutVerified::<_, u64>::new_zeroed(&mut buf.buf[..]).is_none()); assert!(LayoutVerified::<_, [u8; 8]>::new_unaligned(&buf.buf[..]).is_none()); assert!(LayoutVerified::<_, [u8; 8]>::new_unaligned_zeroed(&mut buf.buf[..]).is_none()); - // fail because the buffer is too small + // Fail because the buffer is too small. - // a buffer with an alignment of 8 + // A buffer with an alignment of 8. let mut buf = AlignedBuffer::::default(); - // buf.buf should be aligned to 8, so only the length check should fail + // `buf.buf` should be aligned to 8, so only the length check should + // fail. assert!(LayoutVerified::<_, u64>::new(&buf.buf[..]).is_none()); assert!(LayoutVerified::<_, u64>::new_zeroed(&mut buf.buf[..]).is_none()); assert!(LayoutVerified::<_, [u8; 8]>::new_unaligned(&buf.buf[..]).is_none()); @@ -1479,10 +2723,10 @@ mod tests { assert!(LayoutVerified::<_, [u8; 8]>::new_unaligned_from_suffix_zeroed(&mut buf.buf[..]) .is_none()); - // fail because the length is not a multiple of the element size + // Fail because the length is not a multiple of the element size. let mut buf = AlignedBuffer::::default(); - // buf.buf has length 12, but element size is 8 + // `buf.buf` has length 12, but element size is 8. assert!(LayoutVerified::<_, [u64]>::new_slice(&buf.buf[..]).is_none()); assert!(LayoutVerified::<_, [u64]>::new_slice_zeroed(&mut buf.buf[..]).is_none()); assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned(&buf.buf[..]).is_none()); @@ -1490,46 +2734,126 @@ mod tests { LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_zeroed(&mut buf.buf[..]).is_none() ); - // fail because the alignment is insufficient - - // a buffer with an alignment of 8 + // Fail beacuse the buffer is too short. let mut buf = AlignedBuffer::::default(); - // slicing from 4, we get a buffer with size 8 (so the length check - // should succeed) but an alignment of only 4, which is insufficient - assert!(LayoutVerified::<_, u64>::new(&buf.buf[4..]).is_none()); - assert!(LayoutVerified::<_, u64>::new_zeroed(&mut buf.buf[4..]).is_none()); - assert!(LayoutVerified::<_, u64>::new_from_prefix(&buf.buf[4..]).is_none()); - assert!(LayoutVerified::<_, u64>::new_from_prefix_zeroed(&mut buf.buf[4..]).is_none()); - assert!(LayoutVerified::<_, [u64]>::new_slice(&buf.buf[4..]).is_none()); - assert!(LayoutVerified::<_, [u64]>::new_slice_zeroed(&mut buf.buf[4..]).is_none()); - // slicing from 4 should be unnecessary because new_from_suffix[_zeroed] - // use the suffix of the slice + // `buf.buf` has length 12, but the element size is 8 (and we're + // expecting two of them). + assert!(LayoutVerified::<_, [u64]>::new_slice_from_prefix(&buf.buf[..], 2).is_none()); + assert!( + LayoutVerified::<_, [u64]>::new_slice_from_prefix_zeroed(&mut buf.buf[..], 2).is_none() + ); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_suffix(&buf.buf[..], 2).is_none()); + assert!( + LayoutVerified::<_, [u64]>::new_slice_from_suffix_zeroed(&mut buf.buf[..], 2).is_none() + ); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_prefix(&buf.buf[..], 2) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_prefix_zeroed( + &mut buf.buf[..], + 2 + ) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_suffix(&buf.buf[..], 2) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_suffix_zeroed( + &mut buf.buf[..], + 2 + ) + .is_none()); + + // Fail because the alignment is insufficient. + + // A buffer with an alignment of 8. An odd buffer size is chosen so that + // the last byte of the buffer has odd alignment. + let mut buf = AlignedBuffer::::default(); + // Slicing from 1, we get a buffer with size 12 (so the length check + // should succeed) but an alignment of only 1, which is insufficient. + assert!(LayoutVerified::<_, u64>::new(&buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, u64>::new_zeroed(&mut buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, u64>::new_from_prefix(&buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, u64>::new_from_prefix_zeroed(&mut buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice(&buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_zeroed(&mut buf.buf[1..]).is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_prefix(&buf.buf[1..], 1).is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_prefix_zeroed(&mut buf.buf[1..], 1) + .is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_suffix(&buf.buf[1..], 1).is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_suffix_zeroed(&mut buf.buf[1..], 1) + .is_none()); + // Slicing is unnecessary here because `new_from_suffix[_zeroed]` use + // the suffix of the slice, which has odd alignment. assert!(LayoutVerified::<_, u64>::new_from_suffix(&buf.buf[..]).is_none()); assert!(LayoutVerified::<_, u64>::new_from_suffix_zeroed(&mut buf.buf[..]).is_none()); - } - #[test] - #[should_panic] - fn test_new_slice_zst_panics() { - LayoutVerified::<_, [()]>::new_slice(&[0u8][..]); - } - - #[test] - #[should_panic] - fn test_new_slice_zeroed_zst_panics() { - LayoutVerified::<_, [()]>::new_slice_zeroed(&mut [0u8][..]); - } - - #[test] - #[should_panic] - fn test_new_slice_unaligned_zst_panics() { - LayoutVerified::<_, [()]>::new_slice_unaligned(&[0u8][..]); - } + // Fail due to arithmetic overflow. - #[test] - #[should_panic] - fn test_new_slice_unaligned_zeroed_zst_panics() { - LayoutVerified::<_, [()]>::new_slice_unaligned_zeroed(&mut [0u8][..]); + let mut buf = AlignedBuffer::::default(); + let unreasonable_len = std::usize::MAX / mem::size_of::() + 1; + assert!(LayoutVerified::<_, [u64]>::new_slice_from_prefix(&buf.buf[..], unreasonable_len) + .is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_prefix_zeroed( + &mut buf.buf[..], + unreasonable_len + ) + .is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_suffix(&buf.buf[..], unreasonable_len) + .is_none()); + assert!(LayoutVerified::<_, [u64]>::new_slice_from_suffix_zeroed( + &mut buf.buf[..], + unreasonable_len + ) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_prefix( + &buf.buf[..], + unreasonable_len + ) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_prefix_zeroed( + &mut buf.buf[..], + unreasonable_len + ) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_suffix( + &buf.buf[..], + unreasonable_len + ) + .is_none()); + assert!(LayoutVerified::<_, [[u8; 8]]>::new_slice_unaligned_from_suffix_zeroed( + &mut buf.buf[..], + unreasonable_len + ) + .is_none()); + } + + // Tests for ensuring that, if a ZST is passed into a slice-like function, + // we always panic. Since these tests need to be separate per-function, and + // they tend to take up a lot of space, we generate them using a macro in a + // submodule instead. The submodule ensures that we can just re-use the name + // of the function under test for the name of the test itself. + mod test_zst_panics { + macro_rules! zst_test { + ($name:ident($($tt:tt)*)) => { + #[test] + #[should_panic = "assertion failed"] + fn $name() { + let mut buffer = [0u8]; + let lv = $crate::LayoutVerified::<_, [()]>::$name(&mut buffer[..], $($tt)*); + unreachable!("should have panicked, got {:?}", lv); + } + } + } + zst_test!(new_slice()); + zst_test!(new_slice_zeroed()); + zst_test!(new_slice_from_prefix(1)); + zst_test!(new_slice_from_prefix_zeroed(1)); + zst_test!(new_slice_from_suffix(1)); + zst_test!(new_slice_from_suffix_zeroed(1)); + zst_test!(new_slice_unaligned()); + zst_test!(new_slice_unaligned_zeroed()); + zst_test!(new_slice_unaligned_from_prefix(1)); + zst_test!(new_slice_unaligned_from_prefix_zeroed(1)); + zst_test!(new_slice_unaligned_from_suffix(1)); + zst_test!(new_slice_unaligned_from_suffix_zeroed(1)); } #[test] @@ -1538,32 +2862,60 @@ mod tests { #[repr(C)] struct Foo { a: u32, - b: u32, + b: Wrapping, + c: Option, } - let mut foo = Foo { a: 1, b: 2 }; + let mut foo = Foo { a: 1, b: Wrapping(2), c: None }; + // Test that we can access the underlying bytes, and that we get the // right bytes and the right number of bytes. - assert_eq!(foo.as_bytes(), [1, 0, 0, 0, 2, 0, 0, 0]); + let expected: Vec = if cfg!(target_endian = "little") { + vec![1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] + } else { + vec![0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0] + }; + assert_eq!(foo.as_bytes(), expected.as_bytes()); + // Test that changes to the underlying byte slices are reflected in the // original object. foo.as_bytes_mut()[0] = 3; - assert_eq!(foo, Foo { a: 3, b: 2 }); + let expected_a = if cfg!(target_endian = "little") { 0x00_00_00_03 } else { 0x03_00_00_01 }; + assert_eq!(foo, Foo { a: expected_a, b: Wrapping(2), c: None }); // Do the same tests for a slice, which ensures that this logic works // for unsized types as well. - let foo = &mut [Foo { a: 1, b: 2 }, Foo { a: 3, b: 4 }]; - assert_eq!(foo.as_bytes(), [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]); + let foo = &mut [ + Foo { a: 1, b: Wrapping(2), c: None }, + Foo { a: 3, b: Wrapping(4), c: NonZeroU32::new(1) }, + ]; + let expected = if cfg!(target_endian = "little") { + vec![1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0] + } else { + vec![0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 1] + }; + assert_eq!(foo.as_bytes(), expected); + foo.as_bytes_mut()[8] = 5; - assert_eq!(foo, &mut [Foo { a: 1, b: 2 }, Foo { a: 5, b: 4 }]); + foo.as_bytes_mut()[16] = 6; + let expected_c_1 = NonZeroU32::new(if cfg!(target_endian = "little") { + 0x00_00_00_05 + } else { + 0x05_00_00_00 + }); + let expected_b_2 = + Wrapping(if cfg!(target_endian = "little") { 0x00_00_00_06 } else { 0x06_00_00_04 }); + assert_eq!( + foo, + &[ + Foo { a: 1, b: Wrapping(2), c: expected_c_1 }, + Foo { a: 3, b: expected_b_2, c: NonZeroU32::new(1) }, + ] + ); } #[test] fn test_array() { - // This is a hack, as per above in `test_as_bytes_methods`. - mod zerocopy { - pub use crate::*; - } #[derive(FromBytes, AsBytes)] #[repr(C)] struct Foo { @@ -1586,4 +2938,42 @@ mod tests { let lv = LayoutVerified::<_, [u64]>::new_slice(&buf.buf[..]).unwrap(); assert_eq!(format!("{:?}", lv), "LayoutVerified([0])"); } + + #[test] + fn test_eq() { + let buf1 = 0_u64; + let lv1 = LayoutVerified::<_, u64>::new(buf1.as_bytes()).unwrap(); + let buf2 = 0_u64; + let lv2 = LayoutVerified::<_, u64>::new(buf2.as_bytes()).unwrap(); + assert_eq!(lv1, lv2); + } + + #[test] + fn test_ne() { + let buf1 = 0_u64; + let lv1 = LayoutVerified::<_, u64>::new(buf1.as_bytes()).unwrap(); + let buf2 = 1_u64; + let lv2 = LayoutVerified::<_, u64>::new(buf2.as_bytes()).unwrap(); + assert_ne!(lv1, lv2); + } + + #[test] + fn test_ord() { + let buf1 = 0_u64; + let lv1 = LayoutVerified::<_, u64>::new(buf1.as_bytes()).unwrap(); + let buf2 = 1_u64; + let lv2 = LayoutVerified::<_, u64>::new(buf2.as_bytes()).unwrap(); + assert!(lv1 < lv2); + } + + #[test] + fn test_new_zeroed() { + assert_eq!(u64::new_zeroed(), 0); + // This test exists in order to exercise unsafe code, especially when + // running under Miri. + #[allow(clippy::unit_cmp)] + { + assert_eq!(<()>::new_zeroed(), ()); + } + } } diff --git a/tests/trybuild.rs b/tests/trybuild.rs new file mode 100644 index 0000000000..caaa15bbe6 --- /dev/null +++ b/tests/trybuild.rs @@ -0,0 +1,9 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/zerocopy-derive/BUILD.gn b/zerocopy-derive/BUILD.gn deleted file mode 100644 index a2723bac18..0000000000 --- a/zerocopy-derive/BUILD.gn +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2019 The Fuchsia Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//build/rust/rustc_macro.gni") - -rustc_macro("zerocopy-derive") { - version = "0.1.0" - edition = "2018" - - deps = [ - "//third_party/rust_crates:proc-macro2", - "//third_party/rust_crates:syn", - "//third_party/rust_crates:synstructure", - ] -} diff --git a/zerocopy-derive/Cargo.toml.crates-io b/zerocopy-derive/Cargo.toml similarity index 65% rename from zerocopy-derive/Cargo.toml.crates-io rename to zerocopy-derive/Cargo.toml index db7d54d166..1ba3b12d1e 100644 --- a/zerocopy-derive/Cargo.toml.crates-io +++ b/zerocopy-derive/Cargo.toml @@ -7,22 +7,23 @@ [package] edition = "2018" name = "zerocopy-derive" -version = "0.2.0" +version = "0.3.2" authors = ["Joshua Liebow-Feeser "] description = "Custom derive for traits from the zerocopy crate" -license = "BSD-3-Clause" -repository = "https://fuchsia.googlesource.com/fuchsia/+/master/src/lib/zerocopy/zerocopy-derive" +license-file = "../LICENSE" +repository = "https://github.com/google/zerocopy" -include = ["src/*", "tests/*", "Cargo.toml", "LICENSE"] +exclude = [".*"] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.1" -syn = "1.0.5" -synstructure = "0.12.1" +quote = "1.0.10" +syn = { version = "1.0.5", features = ["visit"] } [dev-dependencies] +rustversion = "1.0" +trybuild = "1.0" zerocopy = { path = "../" } -compiletest_rs = "=0.3.22" diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 6d93379115..f6f51c282e 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -7,19 +7,20 @@ mod ext; mod repr; -use proc_macro2::Span; -use syn::visit::{self, Visit}; -use syn::{ - parse_quote, punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, - Error, GenericParam, Ident, Lifetime, Type, TypePath, +use { + proc_macro2::Span, + quote::quote, + syn::visit::{self, Visit}, + syn::{ + parse_quote, punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DataUnion, + DeriveInput, Error, GenericParam, Ident, Lifetime, Type, TypePath, + }, }; -use synstructure::{decl_derive, quote, Structure}; -use ext::*; -use repr::*; +use {crate::ext::*, crate::repr::*}; -// TODO(joshlf): Some errors could be made better if we could add multiple lines -// of error output like this: +// TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be +// made better if we could add multiple lines of error output like this: // // error: unsupported representation // --> enum.rs:28:8 @@ -35,36 +36,41 @@ use repr::*; // (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error), // which is currently unstable. Revisit this once it's stable. -decl_derive!([FromBytes] => derive_from_bytes); -decl_derive!([AsBytes] => derive_as_bytes); -decl_derive!([Unaligned] => derive_unaligned); - -fn derive_from_bytes(s: Structure<'_>) -> proc_macro2::TokenStream { - match &s.ast().data { - Data::Struct(strct) => derive_from_bytes_struct(&s, strct), - Data::Enum(enm) => derive_from_bytes_enum(&s, enm), - Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(), +#[proc_macro_derive(FromBytes)] +pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_from_bytes_struct(&ast, strct), + Data::Enum(enm) => derive_from_bytes_enum(&ast, enm), + Data::Union(unn) => derive_from_bytes_union(&ast, unn), } + .into() } -fn derive_as_bytes(s: Structure<'_>) -> proc_macro2::TokenStream { - match &s.ast().data { - Data::Struct(strct) => derive_as_bytes_struct(&s, strct), - Data::Enum(enm) => derive_as_bytes_enum(&s, enm), - Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(), +#[proc_macro_derive(AsBytes)] +pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_as_bytes_struct(&ast, strct), + Data::Enum(enm) => derive_as_bytes_enum(&ast, enm), + Data::Union(unn) => derive_as_bytes_union(&ast, unn), } + .into() } -fn derive_unaligned(s: Structure<'_>) -> proc_macro2::TokenStream { - match &s.ast().data { - Data::Struct(strct) => derive_unaligned_struct(&s, strct), - Data::Enum(enm) => derive_unaligned_enum(&s, enm), - Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(), +#[proc_macro_derive(Unaligned)] +pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + match &ast.data { + Data::Struct(strct) => derive_unaligned_struct(&ast, strct), + Data::Enum(enm) => derive_unaligned_enum(&ast, enm), + Data::Union(unn) => derive_unaligned_union(&ast, unn), } + .into() } -// Unwrap a Result<_, Vec>, converting any Err value into a TokenStream -// and returning it. +// Unwraps a `Result<_, Vec>`, converting any `Err` value into a +// `TokenStream` and returning it. macro_rules! try_or_print { ($e:expr) => { match $e { @@ -74,45 +80,52 @@ macro_rules! try_or_print { }; } -// A struct is FromBytes if: -// - all fields are FromBytes +const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ + &[StructRepr::C], + &[StructRepr::Transparent], + &[StructRepr::Packed], + &[StructRepr::C, StructRepr::Packed], +]; + +// A struct is `FromBytes` if: +// - all fields are `FromBytes` -fn derive_from_bytes_struct(s: &Structure<'_>, strct: &DataStruct) -> proc_macro2::TokenStream { - impl_block(s.ast(), strct, "FromBytes", true, false) +fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + impl_block(ast, strct, "FromBytes", true, PaddingCheck::None) } -// An enum is FromBytes if: +// An enum is `FromBytes` if: // - Every possible bit pattern must be valid, which means that every bit // pattern must correspond to a different enum variant. Thus, for an enum // whose layout takes up N bytes, there must be 2^N variants. // - Since we must know N, only representations which guarantee the layout's -// size are allowed. These are repr(uN) and repr(iN) (repr(C) implies an -// implementation-defined size). size and isize technically guarantee the +// size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an +// implementation-defined size). `usize` and `isize` technically guarantee the // layout's size, but would require us to know how large those are on the // target platform. This isn't terribly difficult - we could emit a const -// expression that could call core::mem::size_of in order to determine the +// expression that could call `core::mem::size_of` in order to determine the // size and check against the number of enum variants, but a) this would be // platform-specific and, b) even on Rust's smallest bit width platform (32), // this would require ~4 billion enum variants, which obviously isn't a thing. -fn derive_from_bytes_enum(s: &Structure<'_>, enm: &DataEnum) -> proc_macro2::TokenStream { +fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { if !enm.is_c_like() { - return Error::new_spanned(s.ast(), "only C-like enums can implement FromBytes") + return Error::new_spanned(ast, "only C-like enums can implement FromBytes") .to_compile_error(); } - let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(s.ast())); + let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast)); let variants_required = match reprs.as_slice() { [EnumRepr::U8] | [EnumRepr::I8] => 1usize << 8, [EnumRepr::U16] | [EnumRepr::I16] => 1usize << 16, - // validate_reprs has already validated that it's one of the preceding - // patterns + // `validate_reprs` has already validated that it's one of the preceding + // patterns. _ => unreachable!(), }; if enm.variants.len() != variants_required { return Error::new_spanned( - s.ast(), + ast, format!( "FromBytes only supported on {} enum with {} variants", reprs[0], variants_required @@ -121,7 +134,7 @@ fn derive_from_bytes_enum(s: &Structure<'_>, enm: &DataEnum) -> proc_macro2::Tok .to_compile_error(); } - impl_block(s.ast(), enm, "FromBytes", true, false) + impl_block(ast, enm, "FromBytes", true, PaddingCheck::None) } #[rustfmt::skip] @@ -148,70 +161,62 @@ const ENUM_FROM_BYTES_CFG: Config = { } }; -// A struct is AsBytes if: -// - all fields are AsBytes -// - repr(C) or repr(transparent) and +// Like structs, unions are `FromBytes` if +// - all fields are `FromBytes` + +fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + impl_block(ast, unn, "FromBytes", true, PaddingCheck::None) +} + +// A struct is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)` or `repr(transparent)` and // - no padding (size of struct equals sum of size of field types) -// - repr(packed) +// - `repr(packed)` -fn derive_as_bytes_struct(s: &Structure<'_>, strct: &DataStruct) -> proc_macro2::TokenStream { - // TODO(joshlf): Support type parameters. - if !s.ast().generics.params.is_empty() { +fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + // TODO(#10): Support type parameters. + if !ast.generics.params.is_empty() { return Error::new(Span::call_site(), "unsupported on types with type parameters") .to_compile_error(); } - let reprs = try_or_print!(STRUCT_AS_BYTES_CFG.validate_reprs(s.ast())); - - let require_size_check = match reprs.as_slice() { - [StructRepr::C] | [StructRepr::Transparent] => true, - [StructRepr::Packed] | [StructRepr::C, StructRepr::Packed] => false, - // validate_reprs has already validated that it's one of the preceding - // patterns - _ => unreachable!(), - }; + let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); + let padding_check = + if reprs.contains(&StructRepr::Packed) { PaddingCheck::None } else { PaddingCheck::Struct }; - impl_block(s.ast(), strct, "AsBytes", true, require_size_check) + impl_block(ast, strct, "AsBytes", true, padding_check) } -#[rustfmt::skip] -const STRUCT_AS_BYTES_CFG: Config = { - use StructRepr::*; - Config { - // NOTE: Since disallowed_but_legal_combinations is empty, this message - // will never actually be emitted. - allowed_combinations_message: r#"AsBytes requires repr of "C", "transparent", or "packed""#, - derive_unaligned: false, - allowed_combinations: &[ - &[C], - &[Transparent], - &[C, Packed], - &[Packed], - ], - disallowed_but_legal_combinations: &[], - } +const STRUCT_UNION_AS_BYTES_CFG: Config = Config { + // Since `disallowed_but_legal_combinations` is empty, this message will + // never actually be emitted. + allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""#, + derive_unaligned: false, + allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, + disallowed_but_legal_combinations: &[], }; -// An enum is AsBytes if it is C-like and has a defined repr +// An enum is `AsBytes` if it is C-like and has a defined repr. -fn derive_as_bytes_enum(s: &Structure<'_>, enm: &DataEnum) -> proc_macro2::TokenStream { +fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { if !enm.is_c_like() { - return Error::new_spanned(s.ast(), "only C-like enums can implement AsBytes") + return Error::new_spanned(ast, "only C-like enums can implement AsBytes") .to_compile_error(); } // We don't care what the repr is; we only care that it is one of the // allowed ones. - try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(s.ast())); - impl_block(s.ast(), enm, "AsBytes", false, false) + let _: Vec = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast)); + impl_block(ast, enm, "AsBytes", false, PaddingCheck::None) } #[rustfmt::skip] const ENUM_AS_BYTES_CFG: Config = { use EnumRepr::*; Config { - // NOTE: Since disallowed_but_legal_combinations is empty, this message - // will never actually be emitted. + // Since `disallowed_but_legal_combinations` is empty, this message will + // never actually be emitted. allowed_combinations_message: r#"AsBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", or "isize""#, derive_unaligned: false, allowed_combinations: &[ @@ -231,65 +236,65 @@ const ENUM_AS_BYTES_CFG: Config = { } }; -// A struct is Unaligned if: -// - repr(align) is no more than 1 and either -// - repr(C) or repr(transparent) and -// - all fields Unaligned -// - repr(packed) +// A union is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)`, `repr(transparent)`, or `repr(packed)` +// - no padding (size of union equals size of each field type) -fn derive_unaligned_struct(s: &Structure<'_>, strct: &DataStruct) -> proc_macro2::TokenStream { - let reprs = try_or_print!(STRUCT_UNALIGNED_CFG.validate_reprs(s.ast())); +fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + // TODO(#10): Support type parameters. + if !ast.generics.params.is_empty() { + return Error::new(Span::call_site(), "unsupported on types with type parameters") + .to_compile_error(); + } - let require_trait_bound = match reprs.as_slice() { - [StructRepr::C] | [StructRepr::Transparent] => true, - [StructRepr::Packed] | [StructRepr::C, StructRepr::Packed] => false, - // validate_reprs has already validated that it's one of the preceding - // patterns - _ => unreachable!(), - }; + try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); - impl_block(s.ast(), strct, "Unaligned", require_trait_bound, false) + impl_block(ast, unn, "AsBytes", true, PaddingCheck::Union) } -#[rustfmt::skip] -const STRUCT_UNALIGNED_CFG: Config = { - use StructRepr::*; - Config { - // NOTE: Since disallowed_but_legal_combinations is empty, this message - // will never actually be emitted. - allowed_combinations_message: - r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#, - derive_unaligned: true, - allowed_combinations: &[ - &[C], - &[Transparent], - &[Packed], - &[C, Packed], - ], - disallowed_but_legal_combinations: &[], - } +// A struct is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and +// - all fields `Unaligned` +// - `repr(packed)` + +fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); + let require_trait_bound = !reprs.contains(&StructRepr::Packed); + + impl_block(ast, strct, "Unaligned", require_trait_bound, PaddingCheck::None) +} + +const STRUCT_UNION_UNALIGNED_CFG: Config = Config { + // Since `disallowed_but_legal_combinations` is empty, this message will + // never actually be emitted. + allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#, + derive_unaligned: true, + allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, + disallowed_but_legal_combinations: &[], }; -// An enum is Unaligned if: -// - No repr(align(N > 1)) -// - repr(u8) or repr(i8) +// An enum is `Unaligned` if: +// - No `repr(align(N > 1))` +// - `repr(u8)` or `repr(i8)` -fn derive_unaligned_enum(s: &Structure<'_>, enm: &DataEnum) -> proc_macro2::TokenStream { +fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { if !enm.is_c_like() { - return Error::new_spanned(s.ast(), "only C-like enums can implement Unaligned") + return Error::new_spanned(ast, "only C-like enums can implement Unaligned") .to_compile_error(); } - // The only valid reprs are u8 and i8, and optionally align(1). We don't - // actually care what the reprs are so long as they satisfy that + // The only valid reprs are `u8` and `i8`, and optionally `align(1)`. We + // don't actually care what the reprs are so long as they satisfy that // requirement. - try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(s.ast())); + let _: Vec = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast)); - // NOTE: C-like enums cannot currently have type parameters, so this value - // of true for require_trait_bounds doesn't really do anything. But it's + // C-like enums cannot currently have type parameters, so this value of true + // for `require_trait_bounds` doesn't really do anything. But it's // marginally more future-proof in case that restriction is lifted in the // future. - impl_block(s.ast(), enm, "Unaligned", true, false) + impl_block(ast, enm, "Unaligned", true, PaddingCheck::None) } #[rustfmt::skip] @@ -317,12 +322,36 @@ const ENUM_UNALIGNED_CFG: Config = { } }; +// Like structs, a union is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and +// - all fields `Unaligned` +// - `repr(packed)` + +fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { + let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); + let require_trait_bound = !reprs.contains(&StructRepr::Packed); + + impl_block(ast, unn, "Unaligned", require_trait_bound, PaddingCheck::None) +} + +// This enum describes what kind of padding check needs to be generated for the +// associated impl. +enum PaddingCheck { + // No additional padding check is required. + None, + // Check that the sum of the fields' sizes exactly equals the struct's size. + Struct, + // Check that the size of each field exactly equals the union's size. + Union, +} + fn impl_block( input: &DeriveInput, data: &D, trait_name: &str, require_trait_bound: bool, - require_size_check: bool, + padding_check: PaddingCheck, ) -> proc_macro2::TokenStream { // In this documentation, we will refer to this hypothetical struct: // @@ -338,11 +367,11 @@ fn impl_block( // c: I::Item, // } // - // First, we extract the field types, which in this case are u8, T, and - // I::Item. We use the names of the type parameters to split the field types - // into two sets - a set of types which are based on the type parameters, - // and a set of types which are not. First, we re-use the existing - // parameters and where clauses, generating an impl block like: + // First, we extract the field types, which in this case are `u8`, `T`, and + // `I::Item`. We use the names of the type parameters to split the field + // types into two sets - a set of types which are based on the type + // parameters, and a set of types which are not. First, we re-use the + // existing parameters and where clauses, generating an `impl` block like: // // impl FromBytes for Foo // where @@ -353,7 +382,7 @@ fn impl_block( // } // // Then, we use the list of types which are based on the type parameters to - // generate new entries in the where clause: + // generate new entries in the `where` clause: // // impl FromBytes for Foo // where @@ -365,8 +394,8 @@ fn impl_block( // { // } // - // Finally, we use a different technique to generate the bounds for the types - // which are not based on type parameters: + // Finally, we use a different technique to generate the bounds for the + // types which are not based on type parameters: // // // fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized { @@ -434,11 +463,11 @@ fn impl_block( struct FromTypeParamVisit<'a, 'b>(&'a Punctuated, &'b mut bool); impl<'a, 'b> Visit<'a> for FromTypeParamVisit<'a, 'b> { - fn visit_type_path(&mut self, i: &'a TypePath) { - visit::visit_type_path(self, i); + fn visit_lifetime(&mut self, i: &'a Lifetime) { + visit::visit_lifetime(self, i); if self.0.iter().any(|param| { - if let GenericParam::Type(param) = param { - i.path.segments.first().unwrap().ident == param.ident + if let GenericParam::Lifetime(param) = param { + param.lifetime.ident == i.ident } else { false } @@ -447,11 +476,11 @@ fn impl_block( } } - fn visit_lifetime(&mut self, i: &'a Lifetime) { - visit::visit_lifetime(self, i); + fn visit_type_path(&mut self, i: &'a TypePath) { + visit::visit_type_path(self, i); if self.0.iter().any(|param| { - if let GenericParam::Lifetime(param) = param { - param.lifetime.ident == i.ident + if let GenericParam::Type(param) = param { + i.path.segments.first().unwrap().ident == param.ident } else { false } @@ -515,23 +544,42 @@ fn impl_block( let implements_type_tokens = quote!(#implements_type_ident); let types = non_type_param_field_types.map(|ty| quote!(#implements_type_tokens<#ty>)); quote!( - // A type with a type parameter that must implement #trait_ident + // A type with a type parameter that must implement `#trait_ident`. struct #implements_type_ident(::core::marker::PhantomData); // For each field type, an instantiation that won't type check if - // that type doesn't implement #trait_ident + // that type doesn't implement `#trait_ident`. #(let _: #types;)* ) } else { quote!() }; - let size_check_body = if require_size_check && !field_types.is_empty() { - quote!( - const HAS_PADDING: bool = core::mem::size_of::<#type_ident>() != #(core::mem::size_of::<#field_types>())+*; - let _: [(); 1/(1 - HAS_PADDING as usize)]; - ) - } else { - quote!() + let size_check_body = match (field_types.is_empty(), padding_check) { + (true, _) | (false, PaddingCheck::None) => quote!(), + (false, PaddingCheck::Struct) => quote!( + const _: () = { + trait HasPadding {} + fn assert_no_padding>() {} + + const COMPOSITE_TYPE_SIZE: usize = ::core::mem::size_of::<#type_ident>(); + const SUM_FIELD_SIZES: usize = 0 #(+ ::core::mem::size_of::<#field_types>())*; + const HAS_PADDING: bool = COMPOSITE_TYPE_SIZE > SUM_FIELD_SIZES; + impl HasPadding for #type_ident {} + let _ = assert_no_padding::<#type_ident>; + }; + ), + (false, PaddingCheck::Union) => quote!( + const _: () = { + trait FieldsAreSameSize {} + fn assert_fields_are_same_size>() {} + + const COMPOSITE_TYPE_SIZE: usize = ::core::mem::size_of::<#type_ident>(); + const FIELDS_ARE_SAME_SIZE: bool = true + #(&& (::core::mem::size_of::<#field_types>() == COMPOSITE_TYPE_SIZE))*; + impl FieldsAreSameSize for #type_ident {} + let _ = assert_fields_are_same_size::<#type_ident>; + }; + ), }; quote! { @@ -558,8 +606,8 @@ mod tests { // canonical order. If they aren't, then our algorithm to look up in // those lists won't work. - // TODO(joshlf): Remove once the is_sorted method is stabilized - // (issue #53485). + // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once + // `Vec::is_sorted` is stabilized. fn is_sorted_and_deduped(ts: &[T]) -> bool { let mut sorted = ts.to_vec(); sorted.sort(); @@ -576,15 +624,15 @@ mod tests { && elements_are_sorted_and_deduped(&config.disallowed_but_legal_combinations) } - assert!(config_is_sorted(&STRUCT_UNALIGNED_CFG)); + assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG)); assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG)); assert!(config_is_sorted(&ENUM_UNALIGNED_CFG)); } #[test] fn test_config_repr_no_overlap() { - // Validate that no set of reprs appears in both th allowed_combinations - // and disallowed_but_legal_combinations lists. + // Validate that no set of reprs appears in both the + // `allowed_combinations` and `disallowed_but_legal_combinations` lists. fn overlap(a: &[T], b: &[T]) -> bool { a.iter().any(|elem| b.contains(elem)) @@ -594,7 +642,7 @@ mod tests { overlap(config.allowed_combinations, config.disallowed_but_legal_combinations) } - assert!(!config_overlaps(&STRUCT_UNALIGNED_CFG)); + assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG)); assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG)); assert!(!config_overlaps(&ENUM_UNALIGNED_CFG)); } diff --git a/zerocopy-derive/src/repr.rs b/zerocopy-derive/src/repr.rs index 9dff0523ab..6ded70b4a5 100644 --- a/zerocopy-derive/src/repr.rs +++ b/zerocopy-derive/src/repr.rs @@ -4,19 +4,23 @@ use core::fmt::{self, Display, Formatter}; -use proc_macro2::Span; -use syn::{Attribute, DeriveInput, Error, Lit, Meta, NestedMeta}; +use { + proc_macro2::Span, + syn::spanned::Spanned, + syn::{Attribute, DeriveInput, Error, Lit, Meta, NestedMeta}, +}; pub struct Config { // A human-readable message describing what combinations of representations // are allowed. This will be printed to the user if they use an invalid // combination. pub allowed_combinations_message: &'static str, - // Whether we're checking as part of derive(Unaligned). If not, we can - // ignore repr(align), which makes the code (and the list of valid repr + // Whether we're checking as part of `derive(Unaligned)`. If not, we can + // ignore `repr(align)`, which makes the code (and the list of valid repr // combinations we have to enumerate) somewhat simpler. If we're checking - // for Unaligned, then in addition to checking against illegal combinations, - // we also check to see if there exists a repr(align(N > 1)) attribute. + // for `Unaligned`, then in addition to checking against illegal + // combinations, we also check to see if there exists a `repr(align(N > 1))` + // attribute. pub derive_unaligned: bool, // Combinations which are valid for the trait. pub allowed_combinations: &'static [&'static [Repr]], @@ -39,33 +43,50 @@ impl Config { /// whether `align` attributes are considered during validation, they are /// stripped out of the returned value since no callers care about them. pub fn validate_reprs(&self, input: &DeriveInput) -> Result, Vec> { - let mut reprs = reprs(&input.attrs)?; - reprs.sort(); + let mut metas_reprs = reprs(&input.attrs)?; + metas_reprs.sort_by(|a: &(NestedMeta, R), b| a.1.partial_cmp(&b.1).unwrap()); - if self.derive_unaligned && reprs.iter().any(KindRepr::is_align_gt_one) { - // TODO(joshlf): Have the span correspond just to the attributes - // instead of the entire input. - return Err(vec![Error::new_spanned( - input, - "cannot derive Unaligned with repr(align(N > 1))", - )]); + if self.derive_unaligned { + if let Some((meta, _)) = + metas_reprs.iter().find(|&repr: &&(NestedMeta, R)| repr.1.is_align_gt_one()) + { + return Err(vec![Error::new_spanned( + meta, + "cannot derive Unaligned with repr(align(N > 1))", + )]); + } } - reprs.retain(|repr: &R| !repr.is_align()); + + let mut metas = Vec::new(); + let mut reprs = Vec::new(); + metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| { + metas.push(meta); + reprs.push(repr) + }); if reprs.is_empty() { - // Use Span::call_site to report this error on the #[derive(...)] - // itself. - Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]) - } else if self.allowed_combinations.contains(&reprs.as_slice()) { + // Use `Span::call_site` to report this error on the + // `#[derive(...)]` itself. + return Err(vec![Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout")]); + } + + let initial_sp = metas[0].span(); + let err_span = metas.iter().skip(1).fold(Some(initial_sp), |sp_option, meta| { + sp_option.and_then(|sp| sp.join(meta.span())) + }); + + if self.allowed_combinations.contains(&reprs.as_slice()) { Ok(reprs) } else if self.disallowed_but_legal_combinations.contains(&reprs.as_slice()) { - // TODO(joshlf): Have the span correspond just to the attributes - // instead of the entire input. - Err(vec![Error::new_spanned(input, self.allowed_combinations_message)]) + Err(vec![Error::new( + err_span.unwrap_or_else(|| input.span()), + self.allowed_combinations_message, + )]) } else { - // TODO(joshlf): Have the span correspond just to the attributes - // instead of the entire input. - Err(vec![Error::new_spanned(input, "conflicting representation hints")]) + Err(vec![Error::new( + err_span.unwrap_or_else(|| input.span()), + "conflicting representation hints", + )]) } } } @@ -77,9 +98,9 @@ pub trait KindRepr: 'static + Sized + Ord { fn parse(meta: &NestedMeta) -> syn::Result; } -// Define an enum for reprs which are valid for a given kind (structs, enums, -// etc), and provide implementations of KindRepr, Ord, and Display, and those -// traits' super-traits. +// Defines an enum for reprs which are valid for a given kind (structs, enums, +// etc), and provide implementations of `KindRepr`, `Ord`, and `Display`, and +// those traits' super-traits. macro_rules! define_kind_specific_repr { ($type_name:expr, $repr_name:ident, $($repr_variant:ident),*) => { #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -167,7 +188,7 @@ impl Repr { NestedMeta::Meta(Meta::Path(path)) => { let ident = path .get_ident() - .ok_or(Error::new_spanned(meta, "unrecognized representation hint"))?; + .ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?; match format!("{}", ident).as_str() { "u8" => return Ok(Repr::U8), "u16" => return Ok(Repr::U16), @@ -227,11 +248,11 @@ impl Display for Repr { } } -fn reprs(attrs: &[Attribute]) -> Result, Vec> { +fn reprs(attrs: &[Attribute]) -> Result, Vec> { let mut reprs = Vec::new(); let mut errors = Vec::new(); for attr in attrs { - // ignore documentation attributes + // Ignore documentation attributes. if attr.path.is_ident("doc") { continue; } @@ -240,7 +261,7 @@ fn reprs(attrs: &[Attribute]) -> Result, Vec> { if meta_list.path.is_ident("repr") { for nested_meta in &meta_list.nested { match R::parse(nested_meta) { - Ok(repr) => reprs.push(repr), + Ok(repr) => reprs.push((nested_meta.clone(), repr)), Err(err) => errors.push(err), } } diff --git a/zerocopy-derive/tests/compiletest.rs b/zerocopy-derive/tests/compiletest.rs deleted file mode 100644 index 2afd1a1142..0000000000 --- a/zerocopy-derive/tests/compiletest.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 The Fuchsia Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -use std::path::PathBuf; - -use compiletest_rs::{common::Mode, Config}; - -#[test] -fn ui() { - let mut config = Config { - mode: Mode::Ui, - src_base: PathBuf::from("tests/ui"), - target_rustcflags: Some("-L target/debug -L target/debug/deps".to_string()), - build_base: PathBuf::from("target/ui"), - ..Default::default() - }; - - config.link_deps(); - config.clean_rmeta(); - - compiletest_rs::run_tests(&config); -} - -// extern crate compiletest_rs as compiletest; - -// use std::path::PathBuf; - -// fn run_mode(mode: &'static str) { -// let mut config = compiletest::Config::default(); - -// config.filter = std::env::var("COMPILETEST_FILTER").ok(); -// config.mode = mode.parse().expect("Invalid mode"); -// config.src_base = PathBuf::from(format!("tests/{}", mode)); -// config.target_rustcflags = Some("-L target/debug -L target/debug/deps".to_string()); -// config.link_deps(); // Populate config.target_rustcflags with dependencies on the path -// config.clean_rmeta(); // If your tests import the parent crate, this helps with E0464 - -// compiletest::run_tests(&config); -// } - -// #[test] -// fn compile_error() { -// run_mode("compile-fail"); -// } diff --git a/zerocopy-derive/tests/enum_as_bytes.rs b/zerocopy-derive/tests/enum_as_bytes.rs index 02c8f7adb4..be5c53d0da 100644 --- a/zerocopy-derive/tests/enum_as_bytes.rs +++ b/zerocopy-derive/tests/enum_as_bytes.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#![feature(repr_align_enum)] #![allow(warnings)] use zerocopy::AsBytes; @@ -18,7 +17,7 @@ macro_rules! is_as_bytes { }; } -// An enum is AsBytes if if has a defined repr +// An enum is `AsBytes` if if has a defined repr. #[derive(AsBytes)] #[repr(C)] diff --git a/zerocopy-derive/tests/enum_from_bytes.rs b/zerocopy-derive/tests/enum_from_bytes.rs index 6a4f39d07c..671878d1c0 100644 --- a/zerocopy-derive/tests/enum_from_bytes.rs +++ b/zerocopy-derive/tests/enum_from_bytes.rs @@ -2,27 +2,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#![feature(repr_align_enum)] #![allow(warnings)] use zerocopy::FromBytes; -// An enum is FromBytes if: -// - repr(uN) or repr(iN) +// An enum is `FromBytes` if: +// - `repr(uN)` or `repr(iN)` // - 2^N variants // Summary since it's hard to scan this file quickly: -// - An enum with repr(u8) and 256 variants -// - An enum with repr(i8) and 256 variants -// - An enum with repr(u8), 256 variants, and repr(align(2)) -// - An enum with repr(i8), 256 variants, and repr(align(2)) -// - An enum with repr(u16) and 65536 variants -// - An enum with repr(i16) and 65536 variants +// - An enum with `repr(u8)` and 256 variants +// - An enum with `repr(i8)` and 256 variants +// - An enum with `repr(u8)`, 256 variants, and `repr(align(2))` +// - An enum with `repr(i8)`, 256 variants, and `repr(align(2))` +// - An enum with `repr(u16)` and 65536 variants +// - An enum with `repr(i16)` and 65536 variants // -// For the i8 and i16 enums, we have to explicitly set the descriminant of the -// first variant whose discriminant needs to be negative (e.g., FooI8's -// Variant128 has a discriminant of -128) since Rust won't automatically wrap a -// signed discriminant around without you explicitly telling it to. +// For the `i8` and `i16` enums, we have to explicitly set the descriminant of +// the first variant whose discriminant needs to be negative (e.g., `FooI8`'s +// `Variant128` has a discriminant of -128) since Rust won't automatically wrap +// a signed discriminant around without you explicitly telling it to. struct IsFromBytes(T); diff --git a/zerocopy-derive/tests/enum_unaligned.rs b/zerocopy-derive/tests/enum_unaligned.rs index a50eeb82cb..54381357d9 100644 --- a/zerocopy-derive/tests/enum_unaligned.rs +++ b/zerocopy-derive/tests/enum_unaligned.rs @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#![feature(repr_align_enum)] #![allow(warnings)] use zerocopy::Unaligned; @@ -18,9 +17,9 @@ macro_rules! is_unaligned { }; } -// An enum is Unaligned if: -// - No repr(align(N > 1)) -// - repr(u8) or repr(i8) +// An enum is `Unaligned` if: +// - No `repr(align(N > 1))` +// - `repr(u8)` or `repr(i8)` #[derive(Unaligned)] #[repr(u8)] diff --git a/zerocopy-derive/tests/hygiene.rs b/zerocopy-derive/tests/hygiene.rs index 138091b838..42bbae9d4a 100644 --- a/zerocopy-derive/tests/hygiene.rs +++ b/zerocopy-derive/tests/hygiene.rs @@ -9,8 +9,7 @@ extern crate zerocopy as _zerocopy; -use std::marker::PhantomData; -use std::option::IntoIter; +use std::{marker::PhantomData, option::IntoIter}; use _zerocopy::FromBytes; diff --git a/zerocopy-derive/tests/struct_as_bytes.rs b/zerocopy-derive/tests/struct_as_bytes.rs index b7365da5e9..fd464ced1a 100644 --- a/zerocopy-derive/tests/struct_as_bytes.rs +++ b/zerocopy-derive/tests/struct_as_bytes.rs @@ -4,8 +4,7 @@ #![allow(warnings)] -use std::marker::PhantomData; -use std::option::IntoIter; +use std::{marker::PhantomData, option::IntoIter}; use zerocopy::AsBytes; @@ -20,11 +19,11 @@ macro_rules! is_as_bytes { }; } -// A struct is AsBytes if: -// - all fields are AsBytes -// - repr(C) or repr(transparent) and +// A struct is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)` or `repr(transparent)` and // - no padding (size of struct equals sum of size of field types) -// - repr(packed) +// - `repr(packed)` #[derive(AsBytes)] #[repr(C)] diff --git a/zerocopy-derive/tests/struct_from_bytes.rs b/zerocopy-derive/tests/struct_from_bytes.rs index 2b2766fcf9..7ebc300a40 100644 --- a/zerocopy-derive/tests/struct_from_bytes.rs +++ b/zerocopy-derive/tests/struct_from_bytes.rs @@ -4,8 +4,7 @@ #![allow(warnings)] -use std::marker::PhantomData; -use std::option::IntoIter; +use std::{marker::PhantomData, option::IntoIter}; use zerocopy::FromBytes; @@ -20,8 +19,8 @@ macro_rules! is_from_bytes { }; } -// A struct is FromBytes if: -// - all fields are FromBytes +// A struct is `FromBytes` if: +// - all fields are `FromBytes` #[derive(FromBytes)] struct Zst; diff --git a/zerocopy-derive/tests/struct_unaligned.rs b/zerocopy-derive/tests/struct_unaligned.rs index 923e292739..355c208ec0 100644 --- a/zerocopy-derive/tests/struct_unaligned.rs +++ b/zerocopy-derive/tests/struct_unaligned.rs @@ -4,8 +4,7 @@ #![allow(warnings)] -use std::marker::PhantomData; -use std::option::IntoIter; +use std::{marker::PhantomData, option::IntoIter}; use zerocopy::Unaligned; @@ -20,11 +19,11 @@ macro_rules! is_unaligned { }; } -// A struct is Unaligned if: -// - repr(align) is no more than 1 and either -// - repr(C) or repr(transparent) and +// A struct is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and // - all fields Unaligned -// - repr(packed) +// - `repr(packed)` #[derive(Unaligned)] #[repr(C)] diff --git a/zerocopy-derive/tests/trybuild.rs b/zerocopy-derive/tests/trybuild.rs new file mode 100644 index 0000000000..b95a716455 --- /dev/null +++ b/zerocopy-derive/tests/trybuild.rs @@ -0,0 +1,29 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// UI tests depend on the exact error messages emitted by rustc, but those error +// messages are not stable, and sometimes change between Rust versions. Thus, we +// maintain one set of UI tests for each Rust version that we test in CI, and we +// pin to specific versions in CI (a specific stable version, a specific date of +// the nightly compiler, and a specific MSRV). Updating those pinned versions +// may also require updating these tests. +// - `tests/ui` - Contains the source of truth for our UI test source files +// (`.rs`), and contains `.err` and `.out` files for nightly and beta +// - `tests/ui-stable` - Contains symlinks to the `.rs` files in `tests/ui`, and +// contains `.err` and `.out` files for stable +// - `tests/ui-msrv` - Contains symlinks to the `.rs` files in `tests/ui`, and +// contains `.err` and `.out` files for MSRV + +#[rustversion::any(nightly, beta)] +const SOURCE_FILES_GLOB: &str = "tests/ui/*.rs"; +#[rustversion::all(stable, not(stable(1.56.1)))] +const SOURCE_FILES_GLOB: &str = "tests/ui-stable/*.rs"; +#[rustversion::stable(1.56.1)] +const SOURCE_FILES_GLOB: &str = "tests/ui-msrv/*.rs"; + +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail(SOURCE_FILES_GLOB); +} diff --git a/zerocopy-derive/tests/ui-msrv/enum.rs b/zerocopy-derive/tests/ui-msrv/enum.rs new file mode 120000 index 0000000000..84502bdc2b --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/enum.rs @@ -0,0 +1 @@ +../ui/enum.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-msrv/enum.stderr b/zerocopy-derive/tests/ui-msrv/enum.stderr new file mode 100644 index 0000000000..14ddb0b4bb --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/enum.stderr @@ -0,0 +1,173 @@ +error: unrecognized representation hint + --> tests/ui-msrv/enum.rs:15:8 + | +15 | #[repr("foo")] + | ^^^^^ + +error: unrecognized representation hint + --> tests/ui-msrv/enum.rs:21:8 + | +21 | #[repr(foo)] + | ^^^ + +error: unsupported representation for deriving FromBytes, AsBytes, or Unaligned on an enum + --> tests/ui-msrv/enum.rs:27:8 + | +27 | #[repr(transparent)] + | ^^^^^^^^^^^ + +error: conflicting representation hints + --> tests/ui-msrv/enum.rs:33:1 + | +33 | #[repr(u8, u16)] + | ^ + +error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout + --> tests/ui-msrv/enum.rs:38:10 + | +38 | #[derive(FromBytes)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:48:8 + | +48 | #[repr(C)] + | ^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:54:8 + | +54 | #[repr(usize)] + | ^^^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:60:8 + | +60 | #[repr(isize)] + | ^^^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:66:8 + | +66 | #[repr(u32)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:72:8 + | +72 | #[repr(i32)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:78:8 + | +78 | #[repr(u64)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-msrv/enum.rs:84:8 + | +84 | #[repr(i64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:94:8 + | +94 | #[repr(C)] + | ^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:100:8 + | +100 | #[repr(u16)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:106:8 + | +106 | #[repr(i16)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:112:8 + | +112 | #[repr(u32)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:118:8 + | +118 | #[repr(i32)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:124:8 + | +124 | #[repr(u64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:130:8 + | +130 | #[repr(i64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:136:8 + | +136 | #[repr(usize)] + | ^^^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-msrv/enum.rs:142:8 + | +142 | #[repr(isize)] + | ^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/enum.rs:148:12 + | +148 | #[repr(u8, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/enum.rs:154:12 + | +154 | #[repr(i8, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/enum.rs:160:18 + | +160 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/enum.rs:166:8 + | +166 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0565]: meta item in `repr` must be an identifier + --> tests/ui-msrv/enum.rs:15:8 + | +15 | #[repr("foo")] + | ^^^^^ + +error[E0552]: unrecognized representation hint + --> tests/ui-msrv/enum.rs:21:8 + | +21 | #[repr(foo)] + | ^^^ + +error[E0566]: conflicting representation hints + --> tests/ui-msrv/enum.rs:33:8 + | +33 | #[repr(u8, u16)] + | ^^ ^^^ + | + = note: `#[deny(conflicting_repr_hints)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 diff --git a/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.rs b/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.rs new file mode 120000 index 0000000000..994bb5ebe4 --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.rs @@ -0,0 +1 @@ +../ui/enum_from_bytes_u8_too_few.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.stderr b/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.stderr new file mode 100644 index 0000000000..c4e1bc6369 --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/enum_from_bytes_u8_too_few.stderr @@ -0,0 +1,11 @@ +error: FromBytes only supported on repr(u8) enum with 256 variants + --> tests/ui-msrv/enum_from_bytes_u8_too_few.rs:11:1 + | +11 | / #[repr(u8)] +12 | | enum Foo { +13 | | Variant0, +14 | | Variant1, +... | +267 | | Variant254, +268 | | } + | |_^ diff --git a/zerocopy-derive/tests/ui-msrv/late_compile_pass.rs b/zerocopy-derive/tests/ui-msrv/late_compile_pass.rs new file mode 120000 index 0000000000..ce42bccc6a --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/late_compile_pass.rs @@ -0,0 +1 @@ +../ui/late_compile_pass.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-msrv/late_compile_pass.stderr b/zerocopy-derive/tests/ui-msrv/late_compile_pass.stderr new file mode 100644 index 0000000000..a692474f13 --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/late_compile_pass.stderr @@ -0,0 +1,66 @@ +error[E0277]: the trait bound `AsBytes1: HasPadding` is not satisfied + --> tests/ui-msrv/late_compile_pass.rs:27:10 + | +27 | #[derive(AsBytes)] + | ^^^^^^^ the trait `HasPadding` is not implemented for `AsBytes1` + | + = help: the following implementations were found: + > +note: required by a bound in `assert_no_padding` + --> tests/ui-msrv/late_compile_pass.rs:27:10 + | +27 | #[derive(AsBytes)] + | ^^^^^^^ required by this bound in `assert_no_padding` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `&'static str: FromBytes` is not satisfied + --> tests/ui-msrv/late_compile_pass.rs:18:10 + | +18 | #[derive(FromBytes)] + | ^^^^^^^^^ the trait `FromBytes` is not implemented for `&'static str` + | +note: required by a bound in `ImplementsFromBytes` + --> tests/ui-msrv/late_compile_pass.rs:18:10 + | +18 | #[derive(FromBytes)] + | ^^^^^^^^^ required by this bound in `ImplementsFromBytes` + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-msrv/late_compile_pass.rs:38:10 + | +38 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-msrv/late_compile_pass.rs:38:10 + | +38 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-msrv/late_compile_pass.rs:46:10 + | +46 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-msrv/late_compile_pass.rs:46:10 + | +46 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-msrv/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-msrv/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/zerocopy-derive/tests/ui-msrv/struct.rs b/zerocopy-derive/tests/ui-msrv/struct.rs new file mode 120000 index 0000000000..440d9d84c6 --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/struct.rs @@ -0,0 +1 @@ +../ui/struct.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-msrv/struct.stderr b/zerocopy-derive/tests/ui-msrv/struct.stderr new file mode 100644 index 0000000000..c3e91d4390 --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/struct.stderr @@ -0,0 +1,49 @@ +error: unsupported on types with type parameters + --> tests/ui-msrv/struct.rs:14:10 + | +14 | #[derive(AsBytes)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/struct.rs:23:11 + | +23 | #[repr(C, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/struct.rs:27:21 + | +27 | #[repr(transparent, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/struct.rs:33:16 + | +33 | #[repr(packed, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/struct.rs:37:18 + | +37 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/struct.rs:41:8 + | +41 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0692]: transparent struct cannot have other repr hints + --> tests/ui-msrv/struct.rs:27:8 + | +27 | #[repr(transparent, align(2))] + | ^^^^^^^^^^^ ^^^^^^^^ + +error[E0587]: type has conflicting packed and align representation hints + --> tests/ui-msrv/struct.rs:34:1 + | +34 | struct Unaligned3; + | ^^^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui-msrv/union.rs b/zerocopy-derive/tests/ui-msrv/union.rs new file mode 120000 index 0000000000..b1faf84e5a --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/union.rs @@ -0,0 +1 @@ +../ui/union.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-msrv/union.stderr b/zerocopy-derive/tests/ui-msrv/union.stderr new file mode 100644 index 0000000000..56b72bebcf --- /dev/null +++ b/zerocopy-derive/tests/ui-msrv/union.stderr @@ -0,0 +1,54 @@ +error: unsupported on types with type parameters + --> tests/ui-msrv/union.rs:16:10 + | +16 | #[derive(AsBytes)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/union.rs:34:11 + | +34 | #[repr(C, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/union.rs:50:16 + | +50 | #[repr(packed, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/union.rs:56:18 + | +56 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-msrv/union.rs:62:8 + | +62 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0277]: the trait bound `AsBytes: FieldsAreSameSize` is not satisfied + --> tests/ui-msrv/union.rs:22:10 + | +22 | #[derive(AsBytes)] + | ^^^^^^^ the trait `FieldsAreSameSize` is not implemented for `AsBytes` + | + = help: the following implementations were found: + > +note: required by a bound in `assert_fields_are_same_size` + --> tests/ui-msrv/union.rs:22:10 + | +22 | #[derive(AsBytes)] + | ^^^^^^^ required by this bound in `assert_fields_are_same_size` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0587]: type has conflicting packed and align representation hints + --> tests/ui-msrv/union.rs:51:1 + | +51 | / union Unaligned3 { +52 | | foo: u8, +53 | | } + | |_^ diff --git a/zerocopy-derive/tests/ui-stable/enum.rs b/zerocopy-derive/tests/ui-stable/enum.rs new file mode 120000 index 0000000000..84502bdc2b --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/enum.rs @@ -0,0 +1 @@ +../ui/enum.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-stable/enum.stderr b/zerocopy-derive/tests/ui-stable/enum.stderr new file mode 100644 index 0000000000..a72d2884db --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/enum.stderr @@ -0,0 +1,173 @@ +error: unrecognized representation hint + --> tests/ui-stable/enum.rs:15:8 + | +15 | #[repr("foo")] + | ^^^^^ + +error: unrecognized representation hint + --> tests/ui-stable/enum.rs:21:8 + | +21 | #[repr(foo)] + | ^^^ + +error: unsupported representation for deriving FromBytes, AsBytes, or Unaligned on an enum + --> tests/ui-stable/enum.rs:27:8 + | +27 | #[repr(transparent)] + | ^^^^^^^^^^^ + +error: conflicting representation hints + --> tests/ui-stable/enum.rs:33:1 + | +33 | #[repr(u8, u16)] + | ^ + +error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout + --> tests/ui-stable/enum.rs:38:10 + | +38 | #[derive(FromBytes)] + | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:48:8 + | +48 | #[repr(C)] + | ^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:54:8 + | +54 | #[repr(usize)] + | ^^^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:60:8 + | +60 | #[repr(isize)] + | ^^^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:66:8 + | +66 | #[repr(u32)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:72:8 + | +72 | #[repr(i32)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:78:8 + | +78 | #[repr(u64)] + | ^^^ + +error: FromBytes requires repr of "u8", "u16", "i8", or "i16" + --> tests/ui-stable/enum.rs:84:8 + | +84 | #[repr(i64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:94:8 + | +94 | #[repr(C)] + | ^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:100:8 + | +100 | #[repr(u16)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:106:8 + | +106 | #[repr(i16)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:112:8 + | +112 | #[repr(u32)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:118:8 + | +118 | #[repr(i32)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:124:8 + | +124 | #[repr(u64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:130:8 + | +130 | #[repr(i64)] + | ^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:136:8 + | +136 | #[repr(usize)] + | ^^^^^ + +error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) + --> tests/ui-stable/enum.rs:142:8 + | +142 | #[repr(isize)] + | ^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/enum.rs:148:12 + | +148 | #[repr(u8, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/enum.rs:154:12 + | +154 | #[repr(i8, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/enum.rs:160:18 + | +160 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/enum.rs:166:8 + | +166 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0565]: meta item in `repr` must be an identifier + --> tests/ui-stable/enum.rs:15:8 + | +15 | #[repr("foo")] + | ^^^^^ + +error[E0552]: unrecognized representation hint + --> tests/ui-stable/enum.rs:21:8 + | +21 | #[repr(foo)] + | ^^^ + +error[E0566]: conflicting representation hints + --> tests/ui-stable/enum.rs:33:8 + | +33 | #[repr(u8, u16)] + | ^^ ^^^ + | + = note: `#[deny(conflicting_repr_hints)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 diff --git a/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.rs b/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.rs new file mode 120000 index 0000000000..994bb5ebe4 --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.rs @@ -0,0 +1 @@ +../ui/enum_from_bytes_u8_too_few.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.stderr b/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.stderr new file mode 100644 index 0000000000..5b604235df --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/enum_from_bytes_u8_too_few.stderr @@ -0,0 +1,11 @@ +error: FromBytes only supported on repr(u8) enum with 256 variants + --> tests/ui-stable/enum_from_bytes_u8_too_few.rs:11:1 + | +11 | / #[repr(u8)] +12 | | enum Foo { +13 | | Variant0, +14 | | Variant1, +... | +267 | | Variant254, +268 | | } + | |_^ diff --git a/zerocopy-derive/tests/ui-stable/late_compile_pass.rs b/zerocopy-derive/tests/ui-stable/late_compile_pass.rs new file mode 120000 index 0000000000..ce42bccc6a --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/late_compile_pass.rs @@ -0,0 +1 @@ +../ui/late_compile_pass.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-stable/late_compile_pass.stderr b/zerocopy-derive/tests/ui-stable/late_compile_pass.stderr new file mode 100644 index 0000000000..6f3bdbe6bf --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/late_compile_pass.stderr @@ -0,0 +1,84 @@ +error[E0277]: the trait bound `AsBytes1: HasPadding` is not satisfied + --> tests/ui-stable/late_compile_pass.rs:27:10 + | +27 | #[derive(AsBytes)] + | ^^^^^^^ the trait `HasPadding` is not implemented for `AsBytes1` + | + = help: the trait `HasPadding` is implemented for `AsBytes1` +note: required by a bound in `assert_no_padding` + --> tests/ui-stable/late_compile_pass.rs:27:10 + | +27 | #[derive(AsBytes)] + | ^^^^^^^ required by this bound in `assert_no_padding` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `&'static str: FromBytes` is not satisfied + --> tests/ui-stable/late_compile_pass.rs:18:10 + | +18 | #[derive(FromBytes)] + | ^^^^^^^^^ the trait `FromBytes` is not implemented for `&'static str` + | + = help: the following other types implement trait `FromBytes`: + () + F32 + F64 + FromBytes1 + I128 + I16 + I32 + I64 + and 36 others +note: required by a bound in `ImplementsFromBytes` + --> tests/ui-stable/late_compile_pass.rs:18:10 + | +18 | #[derive(FromBytes)] + | ^^^^^^^^^ required by this bound in `ImplementsFromBytes` + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-stable/late_compile_pass.rs:38:10 + | +38 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-stable/late_compile_pass.rs:38:10 + | +38 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-stable/late_compile_pass.rs:46:10 + | +46 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-stable/late_compile_pass.rs:46:10 + | +46 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui-stable/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui-stable/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/zerocopy-derive/tests/ui-stable/struct.rs b/zerocopy-derive/tests/ui-stable/struct.rs new file mode 120000 index 0000000000..440d9d84c6 --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/struct.rs @@ -0,0 +1 @@ +../ui/struct.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-stable/struct.stderr b/zerocopy-derive/tests/ui-stable/struct.stderr new file mode 100644 index 0000000000..f3a5ccbb36 --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/struct.stderr @@ -0,0 +1,49 @@ +error: unsupported on types with type parameters + --> tests/ui-stable/struct.rs:14:10 + | +14 | #[derive(AsBytes)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/struct.rs:23:11 + | +23 | #[repr(C, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/struct.rs:27:21 + | +27 | #[repr(transparent, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/struct.rs:33:16 + | +33 | #[repr(packed, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/struct.rs:37:18 + | +37 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/struct.rs:41:8 + | +41 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0692]: transparent struct cannot have other repr hints + --> tests/ui-stable/struct.rs:27:8 + | +27 | #[repr(transparent, align(2))] + | ^^^^^^^^^^^ ^^^^^^^^ + +error[E0587]: type has conflicting packed and align representation hints + --> tests/ui-stable/struct.rs:34:1 + | +34 | struct Unaligned3; + | ^^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui-stable/union.rs b/zerocopy-derive/tests/ui-stable/union.rs new file mode 120000 index 0000000000..b1faf84e5a --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/union.rs @@ -0,0 +1 @@ +../ui/union.rs \ No newline at end of file diff --git a/zerocopy-derive/tests/ui-stable/union.stderr b/zerocopy-derive/tests/ui-stable/union.stderr new file mode 100644 index 0000000000..5726d9933a --- /dev/null +++ b/zerocopy-derive/tests/ui-stable/union.stderr @@ -0,0 +1,51 @@ +error: unsupported on types with type parameters + --> tests/ui-stable/union.rs:16:10 + | +16 | #[derive(AsBytes)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/union.rs:34:11 + | +34 | #[repr(C, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/union.rs:50:16 + | +50 | #[repr(packed, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/union.rs:56:18 + | +56 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui-stable/union.rs:62:8 + | +62 | #[repr(align(2), align(4))] + | ^^^^^^^^ + +error[E0277]: the trait bound `AsBytes: FieldsAreSameSize` is not satisfied + --> tests/ui-stable/union.rs:22:10 + | +22 | #[derive(AsBytes)] + | ^^^^^^^ the trait `FieldsAreSameSize` is not implemented for `AsBytes` + | + = help: the trait `FieldsAreSameSize` is implemented for `AsBytes` +note: required by a bound in `assert_fields_are_same_size` + --> tests/ui-stable/union.rs:22:10 + | +22 | #[derive(AsBytes)] + | ^^^^^^^ required by this bound in `assert_fields_are_same_size` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0587]: type has conflicting packed and align representation hints + --> tests/ui-stable/union.rs:51:1 + | +51 | union Unaligned3 { + | ^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui/enum.rs b/zerocopy-derive/tests/ui/enum.rs index dd5250fa3f..188b0a68d2 100644 --- a/zerocopy-derive/tests/ui/enum.rs +++ b/zerocopy-derive/tests/ui/enum.rs @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#![feature(repr_align_enum)] - #[macro_use] extern crate zerocopy; @@ -157,3 +155,15 @@ enum Unaligned10 { enum Unaligned11 { A, } + +#[derive(Unaligned)] +#[repr(align(1), align(2))] +enum Unaligned12 { + A, +} + +#[derive(Unaligned)] +#[repr(align(2), align(4))] +enum Unaligned13 { + A, +} diff --git a/zerocopy-derive/tests/ui/enum.stderr b/zerocopy-derive/tests/ui/enum.stderr index ff02dd7c9f..eeaee34aab 100644 --- a/zerocopy-derive/tests/ui/enum.stderr +++ b/zerocopy-derive/tests/ui/enum.stderr @@ -1,217 +1,175 @@ error: unrecognized representation hint - --> $DIR/enum.rs:17:8 + --> tests/ui/enum.rs:15:8 | -17 | #[repr("foo")] +15 | #[repr("foo")] | ^^^^^ error: unrecognized representation hint - --> $DIR/enum.rs:23:8 + --> tests/ui/enum.rs:21:8 | -23 | #[repr(foo)] +21 | #[repr(foo)] | ^^^ error: unsupported representation for deriving FromBytes, AsBytes, or Unaligned on an enum - --> $DIR/enum.rs:29:8 + --> tests/ui/enum.rs:27:8 | -29 | #[repr(transparent)] +27 | #[repr(transparent)] | ^^^^^^^^^^^ error: conflicting representation hints - --> $DIR/enum.rs:35:1 + --> tests/ui/enum.rs:33:8 | -35 | / #[repr(u8, u16)] -36 | | enum Generic4 { -37 | | A, -38 | | } - | |_^ +33 | #[repr(u8, u16)] + | ^^^^^^^ error: must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout - --> $DIR/enum.rs:40:10 + --> tests/ui/enum.rs:38:10 | -40 | #[derive(FromBytes)] +38 | #[derive(FromBytes)] | ^^^^^^^^^ + | + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:50:1 + --> tests/ui/enum.rs:48:8 | -50 | / #[repr(C)] -51 | | enum FromBytes1 { -52 | | A, -53 | | } - | |_^ +48 | #[repr(C)] + | ^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:56:1 + --> tests/ui/enum.rs:54:8 | -56 | / #[repr(usize)] -57 | | enum FromBytes2 { -58 | | A, -59 | | } - | |_^ +54 | #[repr(usize)] + | ^^^^^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:62:1 + --> tests/ui/enum.rs:60:8 | -62 | / #[repr(isize)] -63 | | enum FromBytes3 { -64 | | A, -65 | | } - | |_^ +60 | #[repr(isize)] + | ^^^^^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:68:1 + --> tests/ui/enum.rs:66:8 | -68 | / #[repr(u32)] -69 | | enum FromBytes4 { -70 | | A, -71 | | } - | |_^ +66 | #[repr(u32)] + | ^^^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:74:1 + --> tests/ui/enum.rs:72:8 | -74 | / #[repr(i32)] -75 | | enum FromBytes5 { -76 | | A, -77 | | } - | |_^ +72 | #[repr(i32)] + | ^^^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:80:1 + --> tests/ui/enum.rs:78:8 | -80 | / #[repr(u64)] -81 | | enum FromBytes6 { -82 | | A, -83 | | } - | |_^ +78 | #[repr(u64)] + | ^^^ error: FromBytes requires repr of "u8", "u16", "i8", or "i16" - --> $DIR/enum.rs:86:1 + --> tests/ui/enum.rs:84:8 | -86 | / #[repr(i64)] -87 | | enum FromBytes7 { -88 | | A, -89 | | } - | |_^ +84 | #[repr(i64)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:96:1 + --> tests/ui/enum.rs:94:8 | -96 | / #[repr(C)] -97 | | enum Unaligned1 { -98 | | A, -99 | | } - | |_^ +94 | #[repr(C)] + | ^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:102:1 + --> tests/ui/enum.rs:100:8 | -102 | / #[repr(u16)] -103 | | enum Unaligned2 { -104 | | A, -105 | | } - | |_^ +100 | #[repr(u16)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:108:1 + --> tests/ui/enum.rs:106:8 | -108 | / #[repr(i16)] -109 | | enum Unaligned3 { -110 | | A, -111 | | } - | |_^ +106 | #[repr(i16)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:114:1 + --> tests/ui/enum.rs:112:8 | -114 | / #[repr(u32)] -115 | | enum Unaligned4 { -116 | | A, -117 | | } - | |_^ +112 | #[repr(u32)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:120:1 + --> tests/ui/enum.rs:118:8 | -120 | / #[repr(i32)] -121 | | enum Unaligned5 { -122 | | A, -123 | | } - | |_^ +118 | #[repr(i32)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:126:1 + --> tests/ui/enum.rs:124:8 | -126 | / #[repr(u64)] -127 | | enum Unaligned6 { -128 | | A, -129 | | } - | |_^ +124 | #[repr(u64)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:132:1 + --> tests/ui/enum.rs:130:8 | -132 | / #[repr(i64)] -133 | | enum Unaligned7 { -134 | | A, -135 | | } - | |_^ +130 | #[repr(i64)] + | ^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:138:1 + --> tests/ui/enum.rs:136:8 | -138 | / #[repr(usize)] -139 | | enum Unaligned8 { -140 | | A, -141 | | } - | |_^ +136 | #[repr(usize)] + | ^^^^^ error: Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1))) - --> $DIR/enum.rs:144:1 + --> tests/ui/enum.rs:142:8 | -144 | / #[repr(isize)] -145 | | enum Unaligned9 { -146 | | A, -147 | | } - | |_^ +142 | #[repr(isize)] + | ^^^^^ error: cannot derive Unaligned with repr(align(N > 1)) - --> $DIR/enum.rs:150:1 + --> tests/ui/enum.rs:148:12 | -150 | / #[repr(u8, align(2))] -151 | | enum Unaligned10 { -152 | | A, -153 | | } - | |_^ +148 | #[repr(u8, align(2))] + | ^^^^^^^^ error: cannot derive Unaligned with repr(align(N > 1)) - --> $DIR/enum.rs:156:1 + --> tests/ui/enum.rs:154:12 | -156 | / #[repr(i8, align(2))] -157 | | enum Unaligned11 { -158 | | A, -159 | | } - | |_^ +154 | #[repr(i8, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/enum.rs:160:18 + | +160 | #[repr(align(1), align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/enum.rs:166:8 + | +166 | #[repr(align(2), align(4))] + | ^^^^^^^^ error[E0565]: meta item in `repr` must be an identifier - --> $DIR/enum.rs:17:8 + --> tests/ui/enum.rs:15:8 | -17 | #[repr("foo")] +15 | #[repr("foo")] | ^^^^^ error[E0552]: unrecognized representation hint - --> $DIR/enum.rs:23:8 + --> tests/ui/enum.rs:21:8 | -23 | #[repr(foo)] +21 | #[repr(foo)] | ^^^ + | + = help: valid reprs are `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` -warning[E0566]: conflicting representation hints - --> $DIR/enum.rs:35:8 +error[E0566]: conflicting representation hints + --> tests/ui/enum.rs:33:8 | -35 | #[repr(u8, u16)] +33 | #[repr(u8, u16)] | ^^ ^^^ - -error: aborting due to 25 previous errors - -Some errors have detailed explanations: E0552, E0565. -For more information about an error, try `rustc --explain E0552`. + | + = note: `#[deny(conflicting_repr_hints)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #68585 diff --git a/zerocopy-derive/tests/ui/enum_from_bytes_u8_too_few.stderr b/zerocopy-derive/tests/ui/enum_from_bytes_u8_too_few.stderr index b755fde949..b75f6c2075 100644 --- a/zerocopy-derive/tests/ui/enum_from_bytes_u8_too_few.stderr +++ b/zerocopy-derive/tests/ui/enum_from_bytes_u8_too_few.stderr @@ -1,5 +1,5 @@ error: FromBytes only supported on repr(u8) enum with 256 variants - --> $DIR/enum_from_bytes_u8_too_few.rs:11:1 + --> tests/ui/enum_from_bytes_u8_too_few.rs:11:1 | 11 | / #[repr(u8)] 12 | | enum Foo { @@ -9,6 +9,3 @@ error: FromBytes only supported on repr(u8) enum with 256 variants 267 | | Variant254, 268 | | } | |_^ - -error: aborting due to previous error - diff --git a/zerocopy-derive/tests/ui/late_compile_pass.rs b/zerocopy-derive/tests/ui/late_compile_pass.rs index 7a03dfc6ac..3a99c76b38 100644 --- a/zerocopy-derive/tests/ui/late_compile_pass.rs +++ b/zerocopy-derive/tests/ui/late_compile_pass.rs @@ -35,7 +35,6 @@ struct AsBytes1 { // Unaligned errors // - #[derive(Unaligned)] #[repr(C)] struct Unaligned1 { @@ -55,4 +54,4 @@ struct Unaligned2 { #[repr(transparent)] struct Unaligned3 { aligned: u16, -} \ No newline at end of file +} diff --git a/zerocopy-derive/tests/ui/late_compile_pass.stderr b/zerocopy-derive/tests/ui/late_compile_pass.stderr index ee4ae4583e..ce5d9b5759 100644 --- a/zerocopy-derive/tests/ui/late_compile_pass.stderr +++ b/zerocopy-derive/tests/ui/late_compile_pass.stderr @@ -1,58 +1,84 @@ -error[E0277]: the trait bound `&'static str: zerocopy::FromBytes` is not satisfied - --> $DIR/late_compile_pass.rs:18:10 +error[E0277]: the trait bound `AsBytes1: HasPadding` is not satisfied + --> tests/ui/late_compile_pass.rs:29:8 | -18 | #[derive(FromBytes)] - | ^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `&'static str` +29 | struct AsBytes1 { + | ^^^^^^^^ the trait `HasPadding` is not implemented for `AsBytes1` | -note: required by `::only_derive_is_allowed_to_implement_this_trait::ImplementsFromBytes` - --> $DIR/late_compile_pass.rs:18:10 - | -18 | #[derive(FromBytes)] - | ^^^^^^^^^ - -error[E0080]: evaluation of constant value failed - --> $DIR/late_compile_pass.rs:27:10 + = help: the trait `HasPadding` is implemented for `AsBytes1` +note: required by a bound in `assert_no_padding` + --> tests/ui/late_compile_pass.rs:27:10 | 27 | #[derive(AsBytes)] - | ^^^^^^^ attempt to divide by zero + | ^^^^^^^ required by this bound in `assert_no_padding` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `u16: zerocopy::Unaligned` is not satisfied - --> $DIR/late_compile_pass.rs:39:10 +error[E0277]: the trait bound `&'static str: FromBytes` is not satisfied + --> tests/ui/late_compile_pass.rs:18:10 | -39 | #[derive(Unaligned)] - | ^^^^^^^^^ the trait `zerocopy::Unaligned` is not implemented for `u16` +18 | #[derive(FromBytes)] + | ^^^^^^^^^ the trait `FromBytes` is not implemented for `&'static str` | -note: required by `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` - --> $DIR/late_compile_pass.rs:39:10 + = help: the following other types implement trait `FromBytes`: + () + F32 + F64 + FromBytes1 + I128 + I16 + I32 + I64 + and 36 others +note: required by a bound in `ImplementsFromBytes` + --> tests/ui/late_compile_pass.rs:18:10 | -39 | #[derive(Unaligned)] - | ^^^^^^^^^ +18 | #[derive(FromBytes)] + | ^^^^^^^^^ required by this bound in `ImplementsFromBytes` + = note: this error originates in the derive macro `FromBytes` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `u16: zerocopy::Unaligned` is not satisfied - --> $DIR/late_compile_pass.rs:47:10 +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui/late_compile_pass.rs:38:10 | -47 | #[derive(Unaligned)] - | ^^^^^^^^^ the trait `zerocopy::Unaligned` is not implemented for `u16` +38 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` | -note: required by `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` - --> $DIR/late_compile_pass.rs:47:10 + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui/late_compile_pass.rs:38:10 | -47 | #[derive(Unaligned)] - | ^^^^^^^^^ +38 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `u16: zerocopy::Unaligned` is not satisfied - --> $DIR/late_compile_pass.rs:54:10 +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui/late_compile_pass.rs:46:10 | -54 | #[derive(Unaligned)] - | ^^^^^^^^^ the trait `zerocopy::Unaligned` is not implemented for `u16` +46 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` | -note: required by `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` - --> $DIR/late_compile_pass.rs:54:10 + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui/late_compile_pass.rs:46:10 | -54 | #[derive(Unaligned)] - | ^^^^^^^^^ - -error: aborting due to 5 previous errors +46 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) -Some errors have detailed explanations: E0080, E0277. -For more information about an error, try `rustc --explain E0080`. +error[E0277]: the trait bound `u16: Unaligned` is not satisfied + --> tests/ui/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ the trait `Unaligned` is not implemented for `u16` + | + = help: the following other types implement trait `Unaligned`: + i8 + u8 +note: required by a bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + --> tests/ui/late_compile_pass.rs:53:10 + | +53 | #[derive(Unaligned)] + | ^^^^^^^^^ required by this bound in `::only_derive_is_allowed_to_implement_this_trait::ImplementsUnaligned` + = note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/zerocopy-derive/tests/ui/struct.rs b/zerocopy-derive/tests/ui/struct.rs index 331eacdeb6..ee8fd0637d 100644 --- a/zerocopy-derive/tests/ui/struct.rs +++ b/zerocopy-derive/tests/ui/struct.rs @@ -32,3 +32,11 @@ struct Unaligned2 { #[derive(Unaligned)] #[repr(packed, align(2))] struct Unaligned3; + +#[derive(Unaligned)] +#[repr(align(1), align(2))] +struct Unaligned4; + +#[derive(Unaligned)] +#[repr(align(2), align(4))] +struct Unaligned5; diff --git a/zerocopy-derive/tests/ui/struct.stderr b/zerocopy-derive/tests/ui/struct.stderr index 117ea3c986..b35739e400 100644 --- a/zerocopy-derive/tests/ui/struct.stderr +++ b/zerocopy-derive/tests/ui/struct.stderr @@ -1,44 +1,49 @@ error: unsupported on types with type parameters - --> $DIR/struct.rs:14:10 + --> tests/ui/struct.rs:14:10 | 14 | #[derive(AsBytes)] | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/struct.rs:23:11 + | +23 | #[repr(C, align(2))] + | ^^^^^^^^ + +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/struct.rs:27:21 + | +27 | #[repr(transparent, align(2))] + | ^^^^^^^^ error: cannot derive Unaligned with repr(align(N > 1)) - --> $DIR/struct.rs:23:1 + --> tests/ui/struct.rs:33:16 | -23 | / #[repr(C, align(2))] -24 | | struct Unaligned1; - | |__________________^ +33 | #[repr(packed, align(2))] + | ^^^^^^^^ error: cannot derive Unaligned with repr(align(N > 1)) - --> $DIR/struct.rs:27:1 + --> tests/ui/struct.rs:37:18 | -27 | / #[repr(transparent, align(2))] -28 | | struct Unaligned2 { -29 | | foo: u8, -30 | | } - | |_^ +37 | #[repr(align(1), align(2))] + | ^^^^^^^^ error: cannot derive Unaligned with repr(align(N > 1)) - --> $DIR/struct.rs:33:1 + --> tests/ui/struct.rs:41:8 | -33 | / #[repr(packed, align(2))] -34 | | struct Unaligned3; - | |__________________^ +41 | #[repr(align(2), align(4))] + | ^^^^^^^^ error[E0692]: transparent struct cannot have other repr hints - --> $DIR/struct.rs:27:8 + --> tests/ui/struct.rs:27:8 | 27 | #[repr(transparent, align(2))] | ^^^^^^^^^^^ ^^^^^^^^ error[E0587]: type has conflicting packed and align representation hints - --> $DIR/struct.rs:34:1 + --> tests/ui/struct.rs:34:1 | 34 | struct Unaligned3; - | ^^^^^^^^^^^^^^^^^^ - -error: aborting due to 6 previous errors - -For more information about this error, try `rustc --explain E0692`. + | ^^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui/union.rs b/zerocopy-derive/tests/ui/union.rs index 6adc76959a..0252cbe0b3 100644 --- a/zerocopy-derive/tests/ui/union.rs +++ b/zerocopy-derive/tests/ui/union.rs @@ -5,10 +5,61 @@ #[macro_use] extern crate zerocopy; +use std::mem::ManuallyDrop; + fn main() {} -#[derive(FromBytes)] -union Foo {} +// +// AsBytes errors +// + +#[derive(AsBytes)] +#[repr(C)] +union AsBytes1 { + foo: ManuallyDrop, +} + +#[derive(AsBytes)] +#[repr(C)] +union AsBytes { + foo: u8, + bar: [u8; 2], +} + +// +// Unaligned errors +// + +#[derive(Unaligned)] +#[repr(C, align(2))] +union Unaligned1 { + foo: i16, + bar: u16, +} + +// Transparent unions are unstable; see issue #60405 +// for more information. + +// #[derive(Unaligned)] +// #[repr(transparent, align(2))] +// union Unaligned2 { +// foo: u8, +// } + +#[derive(Unaligned)] +#[repr(packed, align(2))] +union Unaligned3 { + foo: u8, +} + +#[derive(Unaligned)] +#[repr(align(1), align(2))] +struct Unaligned4 { + foo: u8, +} #[derive(Unaligned)] -union Bar {} +#[repr(align(2), align(4))] +struct Unaligned5 { + foo: u8, +} diff --git a/zerocopy-derive/tests/ui/union.stderr b/zerocopy-derive/tests/ui/union.stderr index 7a8f3157b1..457563935f 100644 --- a/zerocopy-derive/tests/ui/union.stderr +++ b/zerocopy-derive/tests/ui/union.stderr @@ -1,26 +1,51 @@ -error: unexpected unsupported untagged union - --> $DIR/union.rs:11:1 +error: unsupported on types with type parameters + --> tests/ui/union.rs:16:10 | -11 | union Foo {} - | ^^^^^^^^^^^^ +16 | #[derive(AsBytes)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) -error: unexpected unsupported untagged union - --> $DIR/union.rs:14:1 +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/union.rs:34:11 | -14 | union Bar {} - | ^^^^^^^^^^^^ +34 | #[repr(C, align(2))] + | ^^^^^^^^ -error: unions cannot have zero fields - --> $DIR/union.rs:11:1 +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/union.rs:50:16 | -11 | union Foo {} - | ^^^^^^^^^^^^ +50 | #[repr(packed, align(2))] + | ^^^^^^^^ -error: unions cannot have zero fields - --> $DIR/union.rs:14:1 +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/union.rs:56:18 | -14 | union Bar {} - | ^^^^^^^^^^^^ +56 | #[repr(align(1), align(2))] + | ^^^^^^^^ -error: aborting due to 4 previous errors +error: cannot derive Unaligned with repr(align(N > 1)) + --> tests/ui/union.rs:62:8 + | +62 | #[repr(align(2), align(4))] + | ^^^^^^^^ +error[E0277]: the trait bound `AsBytes: FieldsAreSameSize` is not satisfied + --> tests/ui/union.rs:24:7 + | +24 | union AsBytes { + | ^^^^^^^ the trait `FieldsAreSameSize` is not implemented for `AsBytes` + | + = help: the trait `FieldsAreSameSize` is implemented for `AsBytes` +note: required by a bound in `assert_fields_are_same_size` + --> tests/ui/union.rs:22:10 + | +22 | #[derive(AsBytes)] + | ^^^^^^^ required by this bound in `assert_fields_are_same_size` + = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0587]: type has conflicting packed and align representation hints + --> tests/ui/union.rs:51:1 + | +51 | union Unaligned3 { + | ^^^^^^^^^^^^^^^^ diff --git a/zerocopy-derive/tests/ui/update-references.sh b/zerocopy-derive/tests/ui/update-references.sh deleted file mode 100755 index 73bdc21bea..0000000000 --- a/zerocopy-derive/tests/ui/update-references.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# -# Copyright 2015 The Rust Project Developers. See the COPYRIGHT -# file at the top-level directory of this distribution and at -# http://rust-lang.org/COPYRIGHT. -# -# Licensed under the Apache License, Version 2.0 or the MIT license -# , at your -# option. This file may not be copied, modified, or distributed -# except according to those terms. - -# A script to update the references for particular tests. The idea is -# that you do a run, which will generate files in the build directory -# containing the (normalized) actual output of the compiler. This -# script will then copy that output and replace the "expected output" -# files. You can then commit the changes. -# -# If you find yourself manually editing a foo.stderr file, you're -# doing it wrong. - -cd "$(dirname "${BASH_SOURCE[0]}")" -BUILD_DIR="../../target/ui" - -for testcase in *.rs; do - STDERR_NAME="${testcase/%.rs/.stderr}" - STDOUT_NAME="${testcase/%.rs/.stdout}" - if [ -f "$BUILD_DIR/$STDOUT_NAME" ] && \ - ! (diff "$BUILD_DIR/$STDOUT_NAME" "$STDOUT_NAME" >& /dev/null); then - echo "updating $STDOUT_NAME" - cp "$BUILD_DIR/$STDOUT_NAME" "$STDOUT_NAME" - fi - if [ -f "$BUILD_DIR/$STDERR_NAME" ] && \ - ! (diff "$BUILD_DIR/$STDERR_NAME" "$STDERR_NAME" >& /dev/null); then - echo "updating $STDERR_NAME" - cp "$BUILD_DIR/$STDERR_NAME" "$STDERR_NAME" - fi -done \ No newline at end of file diff --git a/zerocopy-derive/tests/union_as_bytes.rs b/zerocopy-derive/tests/union_as_bytes.rs new file mode 100644 index 0000000000..fd848cc0b5 --- /dev/null +++ b/zerocopy-derive/tests/union_as_bytes.rs @@ -0,0 +1,82 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![allow(warnings)] + +use std::{marker::PhantomData, option::IntoIter}; + +use zerocopy::AsBytes; + +struct IsAsBytes(T); + +// Fail compilation if `$ty: !AsBytes`. +macro_rules! is_as_bytes { + ($ty:ty) => { + const _: () = { + let _: IsAsBytes<$ty>; + }; + }; +} + +// A union is `AsBytes` if: +// - all fields are `AsBytes` +// - `repr(C)` or `repr(transparent)` and +// - no padding (size of union equals size of each field type) +// - `repr(packed)` + +#[derive(AsBytes, Clone, Copy)] +#[repr(C)] +union CZst { + a: (), +} + +is_as_bytes!(CZst); + +#[derive(AsBytes)] +#[repr(C)] +union C { + a: u8, + b: u8, +} + +is_as_bytes!(C); + +// Transparent unions are unstable; see issue #60405 +// for more information. + +// #[derive(AsBytes)] +// #[repr(transparent)] +// union Transparent { +// a: u8, +// b: CZst, +// } + +// is_as_bytes!(Transparent); + +#[derive(AsBytes)] +#[repr(C, packed)] +union CZstPacked { + a: (), +} + +is_as_bytes!(CZstPacked); + +#[derive(AsBytes)] +#[repr(C, packed)] +union CPacked { + a: u8, + b: i8, +} + +is_as_bytes!(CPacked); + +#[derive(AsBytes)] +#[repr(C, packed)] +union CMultibytePacked { + a: i32, + b: u32, + c: f32, +} + +is_as_bytes!(CMultibytePacked); diff --git a/zerocopy-derive/tests/union_from_bytes.rs b/zerocopy-derive/tests/union_from_bytes.rs new file mode 100644 index 0000000000..ef6d403f75 --- /dev/null +++ b/zerocopy-derive/tests/union_from_bytes.rs @@ -0,0 +1,60 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![allow(warnings)] + +use std::{marker::PhantomData, option::IntoIter}; + +use zerocopy::FromBytes; + +struct IsFromBytes(T); + +// Fail compilation if `$ty: !FromBytes`. +macro_rules! is_from_bytes { + ($ty:ty) => { + const _: () = { + let _: IsFromBytes<$ty>; + }; + }; +} + +// A union is `FromBytes` if: +// - all fields are `FromBytes` + +#[derive(Clone, Copy, FromBytes)] +union Zst { + a: (), +} + +is_from_bytes!(Zst); + +#[derive(FromBytes)] +union One { + a: u8, +} + +is_from_bytes!(One); + +#[derive(FromBytes)] +union Two { + a: u8, + b: Zst, +} + +is_from_bytes!(Two); + +#[derive(FromBytes)] +union TypeParams<'a, T: Copy, I: Iterator> +where + I::Item: Copy, +{ + a: T, + c: I::Item, + d: u8, + e: PhantomData<&'a [u8]>, + f: PhantomData<&'static str>, + g: PhantomData, +} + +is_from_bytes!(TypeParams<'static, (), IntoIter<()>>); diff --git a/zerocopy-derive/tests/union_unaligned.rs b/zerocopy-derive/tests/union_unaligned.rs new file mode 100644 index 0000000000..076595c5dd --- /dev/null +++ b/zerocopy-derive/tests/union_unaligned.rs @@ -0,0 +1,77 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![allow(warnings)] + +use std::{marker::PhantomData, option::IntoIter}; + +use zerocopy::Unaligned; + +struct IsUnaligned(T); + +// Fail compilation if `$ty: !Unaligned`. +macro_rules! is_unaligned { + ($ty:ty) => { + const _: () = { + let _: IsUnaligned<$ty>; + }; + }; +} + +// A union is `Unaligned` if: +// - `repr(align)` is no more than 1 and either +// - `repr(C)` or `repr(transparent)` and +// - all fields `Unaligned` +// - `repr(packed)` + +#[derive(Unaligned)] +#[repr(C)] +union Foo { + a: u8, +} + +is_unaligned!(Foo); + +// Transparent unions are unstable; see issue #60405 +// for more information. + +// #[derive(Unaligned)] +// #[repr(transparent)] +// union Bar { +// a: u8, +// } + +// is_unaligned!(Bar); + +#[derive(Unaligned)] +#[repr(packed)] +union Baz { + a: u16, +} + +is_unaligned!(Baz); + +#[derive(Unaligned)] +#[repr(C, align(1))] +union FooAlign { + a: u8, +} + +is_unaligned!(FooAlign); + +#[derive(Unaligned)] +#[repr(C)] +union TypeParams<'a, T: Copy, I: Iterator> +where + I::Item: Copy, +{ + a: T, + c: I::Item, + d: u8, + e: PhantomData<&'a [u8]>, + f: PhantomData<&'static str>, + g: PhantomData, +} + +is_unaligned!(TypeParams<'static, (), IntoIter<()>>);