Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ simd = []
simd-nightly = ["simd"]
float-nightly = []
std = ["alloc"]
embedded_io = []
# This feature depends on all other features that work on the stable compiler.
# We make no stability guarantees about this feature; it may be modified or
# removed at any time.
Expand All @@ -125,6 +126,7 @@ __internal_use_only_features_that_work_on_stable = [

[dependencies]
zerocopy-derive = { version = "=0.8.48", path = "zerocopy-derive", optional = true }
embedded-io = { version = "=0.5.0" }

# The "associated proc macro pattern" ensures that the versions of zerocopy and
# zerocopy-derive remain equal, even if the 'derive' feature isn't used.
Expand Down
147 changes: 147 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5331,6 +5331,50 @@ pub unsafe trait FromBytes: FromZeros {
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
let ptr = ptr.as_bytes();
src.read_exact(ptr.as_mut())?;

// SAFETY: `buf` entirely consists of initialized bytes, and `Self` is
// `FromBytes`.
Ok(unsafe { buf.assume_init() })
}

/// Reads a copy of `self` from an `embedded_io::Read`.
///
#[cfg(feature = "embedded_io")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "embedded_io")))]
#[inline(always)]
fn read_from_embedded_io<R>(
mut src: R,
) -> core::result::Result<Self, embedded_io::ReadExactError<R::Error>>
where
Self: Sized,
R: embedded_io::Read,
{
// Taken from [FromBytes::read_from_io]:
//
// NOTE(#2319, #2320): We do `buf.zero()` separately rather than
// constructing `let buf = CoreMaybeUninit::zeroed()` because, if `Self`
// contains padding bytes, then a typed copy of `CoreMaybeUninit<Self>`
// will not necessarily preserve zeros written to those padding byte
// locations, and so `buf` could contain uninitialized bytes.
let mut buf = CoreMaybeUninit::<Self>::uninit();
buf.zero();

let ptr = Ptr::from_mut(&mut buf);
// Taken from [FromBytes::read_from_io]:
//
// SAFETY: After `buf.zero()`, `buf` consists entirely of initialized,
// zeroed bytes. Since `MaybeUninit` has no validity requirements, `ptr`
// cannot be used to write values which will violate `buf`'s bit
// validity. Since `ptr` has `Exclusive` aliasing, nothing other than
// `ptr` may be used to mutate `ptr`'s referent, and so its bit validity
// cannot be violated even though `buf` may have more permissive bit
// validity than `ptr`.
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
let ptr = ptr.as_bytes();
src.read_exact(ptr.as_mut())?;

// Taken from [FromBytes::read_from_io]:
//
// SAFETY: `buf` entirely consists of initialized bytes, and `Self` is
// `FromBytes`.
Ok(unsafe { buf.assume_init() })
Expand Down Expand Up @@ -6129,6 +6173,59 @@ pub unsafe trait IntoBytes {
dst.write_all(self.as_bytes())
}

/// Writes a copy of `self` to an `embedded_io::Write`.
///
/// This is a shorthand for `dst.write_all(self.as_bytes())`.
///
/// # Examples
///
/// ```no_run
/// use zerocopy::{byteorder::big_endian::U16, FromBytes, IntoBytes};
/// use std::fs::File;
/// use embedded_io::Write;
/// # use zerocopy_derive::*;
///
/// #[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
/// #[repr(C, packed)]
/// struct GrayscaleImage {
/// height: U16,
/// width: U16,
/// pixels: [U16],
/// }
///
/// let image = GrayscaleImage::ref_from_bytes(&[0, 0, 0, 0][..]).unwrap();
/// let mut file = [0u8; 1024];
/// image.write_to_embedded_io(&mut file[..]).unwrap();
/// ```
///
/// If the write fails, `write_to_embedded_io` returns `Err` and a partial write may
/// have occurred; e.g.:
///
/// ```
/// # use zerocopy::IntoBytes;
///
/// let src = u128::MAX;
/// let mut dst = [0u8; 2];
///
/// let write_result = src.write_to_embedded_io(&mut dst[..]);
///
/// assert!(write_result.is_err());
/// assert_eq!(dst, [255, 255]);
/// ```
#[cfg(feature = "embedded_io")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "embedded_io")))]
#[inline(always)]
fn write_to_embedded_io<W>(
&self,
mut dst: W,
) -> core::result::Result<(), embedded_io::WriteAllError<W::Error>>
where
Self: Immutable,
W: embedded_io::Write,
{
dst.write_all(self.as_bytes())
}

#[deprecated(since = "0.8.0", note = "`IntoBytes::as_bytes_mut` was renamed to `as_mut_bytes`")]
#[doc(hidden)]
#[inline]
Expand Down Expand Up @@ -7030,6 +7127,56 @@ mod tests {
assert!(u32::read_from_io(&short_buffer[..]).is_err());
}

#[test]
#[cfg(feature = "embedded_io")]
fn test_read_write_embedded_io() {
let mut long_buffer = [0, 0, 0, 0];
assert!(matches!(u16::MAX.write_to_embedded_io(&mut long_buffer[..]), Ok(())));
assert_eq!(long_buffer, [255, 255, 0, 0]);
assert!(matches!(u16::read_from_embedded_io(&long_buffer[..]), Ok(u16::MAX)));

let mut short_buffer = [0, 0];
assert!(u32::MAX.write_to_embedded_io(&mut short_buffer[..]).is_err());
assert_eq!(short_buffer, [255, 255]);
assert!(u32::read_from_embedded_io(&short_buffer[..]).is_err());
}

#[test]
#[cfg(feature = "embedded_io")]
fn test_read_embedded_io_with_padding_soundness() {
// This test is designed to exhibit potential UB in
// `FromBytes::read_from_embedded_io`. (see #2319, #2320).

// On most platforms (where `align_of::<u16>() == 2`), `WithPadding`
// will have inter-field padding between `x` and `y`.
#[derive(FromBytes)]
#[repr(C)]
struct WithPadding {
x: u8,
y: u16,
}
struct ReadsInRead;
impl embedded_io::ErrorType for ReadsInRead {
type Error = embedded_io::ErrorKind;
}
impl embedded_io::Read for ReadsInRead {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
// This body branches on every byte of `buf`, ensuring that it
// exhibits UB if any byte of `buf` is uninitialized.
if buf.iter().all(|&x| x == 0) {
Ok(buf.len())
} else {
buf.iter_mut().for_each(|x| *x = 0);
Ok(buf.len())
}
}
}
assert!(matches!(
WithPadding::read_from_embedded_io(ReadsInRead),
Ok(WithPadding { x: 0, y: 0 })
));
}

#[test]
fn test_try_from_bytes_try_read_from() {
assert_eq!(<bool as TryFromBytes>::try_read_from_bytes(&[0]), Ok(false));
Expand Down
6 changes: 6 additions & 0 deletions vendor/aho-corasick/.cargo_vcs_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "7e231db4b4ac192ebc674078f2b03cd37b9ed5b9"
},
"path_in_vcs": ""
}
110 changes: 110 additions & 0 deletions vendor/aho-corasick/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: ci
on:
pull_request:
push:
branches:
- master
schedule:
- cron: '00 01 * * *'
jobs:
test:
name: test
env:
# For some builds, we use cross to test on 32-bit and big-endian
# systems.
CARGO: cargo
# When CARGO is set to CROSS, TARGET is set to `--target matrix.target`.
TARGET:
runs-on: ${{ matrix.os }}
strategy:
matrix:
build:
- pinned
- stable
- stable-32
- stable-mips
- beta
- nightly
- macos
- win-msvc
- win-gnu
include:
- build: pinned
os: ubuntu-18.04
rust: 1.41.1
- build: stable
os: ubuntu-18.04
rust: stable
- build: stable-32
os: ubuntu-18.04
rust: stable
target: i686-unknown-linux-gnu
- build: stable-mips
os: ubuntu-18.04
rust: stable
target: mips64-unknown-linux-gnuabi64
- build: beta
os: ubuntu-18.04
rust: beta
- build: nightly
os: ubuntu-18.04
rust: nightly
- build: macos
os: macos-latest
rust: stable
- build: win-msvc
os: windows-2019
rust: stable
- build: win-gnu
os: windows-2019
rust: stable-x86_64-gnu
steps:
- name: Checkout repository
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Use Cross
if: matrix.target != ''
run: |
# We used to install 'cross' from master, but it kept failing. So now
# we build from a known-good version until 'cross' becomes more stable
# or we find an alternative. Notably, between v0.2.1 and current
# master (2022-06-14), the number of Cross's dependencies has doubled.
cargo install --bins --git https://github.com/rust-embedded/cross --tag v0.2.1
echo "CARGO=cross" >> $GITHUB_ENV
echo "TARGET=--target ${{ matrix.target }}" >> $GITHUB_ENV
- name: Show command used for Cargo
run: |
echo "cargo command is: ${{ env.CARGO }}"
echo "target flag is: ${{ env.TARGET }}"
- name: Show CPU info for debugging
if: matrix.os == 'ubuntu-18.04'
run: lscpu
- run: ${{ env.CARGO }} build --verbose
- run: ${{ env.CARGO }} doc --verbose
- run: ${{ env.CARGO }} test --verbose
- if: matrix.build == 'nightly'
run: ${{ env.CARGO }} build --manifest-path aho-corasick-debug/Cargo.toml
- if: matrix.build == 'nightly'
run: ${{ env.CARGO }} bench --verbose --manifest-path bench/Cargo.toml -- --test

rustfmt:
name: rustfmt
runs-on: ubuntu-18.04
steps:
- name: Checkout repository
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- name: Check formatting
run: |
cargo fmt --all -- --check
38 changes: 38 additions & 0 deletions vendor/aho-corasick/Cargo.toml.orig

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions vendor/autocfg/.cargo_vcs_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d912169ed67977efe5a465269b0e73cb66060c49"
},
"path_in_vcs": ""
}
15 changes: 15 additions & 0 deletions vendor/autocfg/Cargo.toml.orig

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions vendor/bitflags/.cargo_vcs_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "7cc8595e93d04d180d39e2f25242dca85dd71228"
},
"path_in_vcs": ""
}
Loading
Loading