Repository: BurntSushi/byteorder (language: rust)

Issue:
Verify alignment requirements of `f32` vs `u32` (and `f64` vs `u64`).
Fixes https://github.com/BurntSushi/byteorder/issues/194

PTAL? :-)
Unsound assumption about the alignment of u32 and f32
An `unsafe` transmute between `&[f32]` and `&[u32]` is performed here: https://github.com/BurntSushi/byteorder/blob/8d9f0c0fb577e2d3aa0265a0b0c0b8af56780aec/src/lib.rs#L1202-L1208.

The transmute may lead to Undefined Behavior if `&[u32]` has stricter alignment requirements than `&[f32]`.  Primitive types (e.g. `u32` or `f32`) are usually aligned to their size, but this is [a platform specific behavior](https://doc.rust-lang.org/reference/type-layout.html#primitive-data-layout).  A platform _could_ in theory define `f32` to be aligned on a 2 byte boundary, while saying that `u32` must be aligned on 4 bytes. That would make `read_u32_into` an unaligned write.

One way to avoid the unsound assumptions is to enforce them at compile time as follows:

```
const _CHECK_ALIGN: () = assert!(align_of::<u32>() <= align_of::<f32>());
```

Many thanks to @kupiakos for finding this issue and suggesting the compile-time assertion above.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ef988f9..1aa3c3b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,11 +1,34 @@
 name: ci
 on:
   pull_request:
+    branches:
+    - master
   push:
     branches:
     - master
   schedule:
   - cron: '00 01 * * *'
+
+# The section is needed to drop write-all permissions that are granted on
+# `schedule` event. By specifying any permission explicitly all others are set
+# to none. By using the principle of least privilege the damage a compromised
+# workflow can do (because of an injection or compromised third party tool or
+# action) is restricted. Currently the worklow doesn't need any additional
+# permission except for pulling the code. Adding labels to issues, commenting
+# on pull-requests, etc. may need additional permissions:
+#
+# Syntax for this section:
+# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+#
+# Reference for how to assign permissions on a job-by-job basis:
+# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
+#
+# Reference for available permissions that we can enable if needed:
+# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
+permissions:
+  # to fetch code (actions/checkout)
+  contents: read
+
 jobs:
   test:
     name: test
@@ -14,35 +37,24 @@ jobs:
       # systems.
       CARGO: cargo
       # When CARGO is set to CROSS, TARGET is set to `--target matrix.target`.
+      # Note that we only use cross on Linux, so setting a target on a
+      # different OS will just use normal cargo.
       TARGET:
+      # Bump this as appropriate. We pin to a version to make sure CI
+      # continues to work as cross releases in the past have broken things
+      # in subtle ways.
+      CROSS_VERSION: v0.2.5
     runs-on: ${{ matrix.os }}
     strategy:
+      fail-fast: false
       matrix:
-        build:
-        - pinned
-        - stable
-        - stable-32
-        - stable-mips
-        - beta
-        - nightly
-        - macos
-        - win-msvc
-        - win-gnu
         include:
         - build: pinned
           os: ubuntu-latest
-          rust: 1.41.1
+          rust: 1.60.0
         - build: stable
           os: ubuntu-latest
           rust: stable
-        - build: stable-32
-          os: ubuntu-latest
-          rust: stable
-          target: i686-unknown-linux-gnu
-        - build: stable-mips
-          os: ubuntu-latest
-          rust: stable
-          target: mips64-unknown-linux-gnuabi64
         - build: beta
           os: ubuntu-latest
           rust: beta
@@ -58,41 +70,54 @@ jobs:
         - build: win-gnu
           os: windows-latest
           rust: stable-x86_64-gnu
+        - build: stable-x86
+          os: ubuntu-latest
+          rust: stable
+          target: i686-unknown-linux-gnu
+        - build: stable-aarch64
+          os: ubuntu-latest
+          rust: stable
+          target: aarch64-unknown-linux-gnu
+        - build: stable-powerpc64
+          os: ubuntu-latest
+          rust: stable
+          target: powerpc64-unknown-linux-gnu
+        - build: stable-s390x
+          os: ubuntu-latest
+          rust: stable
+          target: s390x-unknown-linux-gnu
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
-
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@master
       with:
         toolchain: ${{ matrix.rust }}
-
     - name: Use Cross
-      if: matrix.target != ''
+      if: matrix.os == 'ubuntu-latest' && 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/cross-rs/cross --tag v0.2.1
+        # In the past, new releases of 'cross' have broken CI. So for now, we
+        # pin it. We also use their pre-compiled binary releases because cross
+        # has over 100 dependencies and takes a bit to compile.
+        dir="$RUNNER_TEMP/cross-download"
+        mkdir "$dir"
+        echo "$dir" >> $GITHUB_PATH
+        cd "$dir"
+        curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
+        tar xf cross-x86_64-unknown-linux-musl.tar.gz
         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-latest'
       run: lscpu
-
     - name: Build
       run: ${{ env.CARGO }} build --verbose $TARGET
-
     - name: Build (no default)
       run: ${{ env.CARGO }} build --verbose $TARGET --no-default-features
-
     - name: Build docs
       run: ${{ env.CARGO }} doc --verbose $TARGET
 
@@ -130,12 +155,25 @@ jobs:
       if: matrix.build == 'nightly'
       run: cargo bench --verbose --no-run --no-default-features --features i128 $TARGET
 
+  miri:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+    - name: Install Rust
+      uses: dtolnay/rust-toolchain@master
+      with:
+        # We use nightly here so that we can use miri I guess?
+        toolchain: nightly
+        components: miri
+    - name: Run full test suite
+      run: cargo miri test --verbose
+
   rustfmt:
-    name: rustfmt
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Install Rust
       uses: dtolnay/rust-toolchain@master
       with:
diff --git a/Cargo.toml b/Cargo.toml
index 728f668..a93c9f5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,8 @@ categories = ["encoding", "parsing", "no-std"]
 keywords = ["byte", "endian", "big-endian", "little-endian", "binary"]
 license = "Unlicense OR MIT"
 exclude = ["/ci/*"]
-edition = "2018"
+edition = "2021"
+rust-version = "1.60"
 
 [lib]
 name = "byteorder"
diff --git a/README.md b/README.md
index 6069899..75394f2 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ byteorder = { version = "1", default-features = false }
 
 ### Minimum Rust version policy
 
-This crate's minimum supported `rustc` version is `1.41.1`.
+This crate's minimum supported `rustc` version is `1.60.0`.
 
 The current policy is that the minimum Rust version required to use this crate
 can be increased in minor version updates. For example, if `crate 1.0` requires
diff --git a/benches/bench.rs b/benches/bench.rs
index bb00422..963251c 100644
--- a/benches/bench.rs
+++ b/benches/bench.rs
@@ -321,4 +321,6 @@ macro_rules! bench_slice {
     };
 }
 
+bench_slice!(slice_u16, u16, read_u16_into, write_u16_into);
 bench_slice!(slice_u64, u64, read_u64_into, write_u64_into);
+bench_slice!(slice_i64, i64, read_i64_into, write_i64_into);
diff --git a/src/lib.rs b/src/lib.rs
index cc37cca..cfd53c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -69,9 +69,13 @@ cases.
 
 #![deny(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
+// When testing under miri, we disable tests that take too long. But this
+// provokes lots of dead code warnings. So we just squash them.
+#![cfg_attr(miri, allow(dead_code, unused_macros))]
 
 use core::{
-    convert::TryInto, fmt::Debug, hash::Hash, ptr::copy_nonoverlapping, slice,
+    convert::TryInto, fmt::Debug, hash::Hash, mem::align_of,
+    ptr::copy_nonoverlapping, slice,
 };
 
 #[cfg(feature = "std")]
@@ -1202,6 +1206,7 @@ pub trait ByteOrder:
     #[inline]
     fn read_f32_into(src: &[u8], dst: &mut [f32]) {
         let dst = unsafe {
+            const _: () = assert!(align_of::<u32>() <= align_of::<f32>());
             slice::from_raw_parts_mut(dst.as_mut_ptr() as *mut u32, dst.len())
         };
         Self::read_u32_into(src, dst);
@@ -1263,6 +1268,7 @@ pub trait ByteOrder:
     #[inline]
     fn read_f64_into(src: &[u8], dst: &mut [f64]) {
         let dst = unsafe {
+            const _: () = assert!(align_of::<u64>() <= align_of::<f64>());
             slice::from_raw_parts_mut(dst.as_mut_ptr() as *mut u64, dst.len())
         };
         Self::read_u64_into(src, dst);
@@ -1895,74 +1901,36 @@ pub type NativeEndian = LittleEndian;
 #[cfg(target_endian = "big")]
 pub type NativeEndian = BigEndian;
 
-/// Copies $size bytes from a number $n to a &mut [u8] $dst. $ty represents the
-/// numeric type of $n and $which must be either to_be or to_le, depending on
-/// which endianness one wants to use when writing to $dst.
+/// Copies a &[u8] $src into a &mut [$ty] $dst for the endianness given by
+/// $from_bytes (must be either from_be_bytes or from_le_bytes).
 ///
-/// This macro is only safe to call when $ty is a numeric type and $size ==
-/// size_of::<$ty>() and where $dst is a &mut [u8].
-macro_rules! unsafe_write_num_bytes {
-    ($ty:ty, $size:expr, $n:expr, $dst:expr, $which:ident) => {{
-        assert!($size <= $dst.len());
-        unsafe {
-            // N.B. https://github.com/rust-lang/rust/issues/22776
-            let bytes = *(&$n.$which() as *const _ as *const [u8; $size]);
-            copy_nonoverlapping((&bytes).as_ptr(), $dst.as_mut_ptr(), $size);
+/// Panics if $src.len() != $dst.len() * size_of::<$ty>().
+macro_rules! read_slice {
+    ($src:expr, $dst:expr, $ty:ty, $from_bytes:ident) => {{
+        const SIZE: usize = core::mem::size_of::<$ty>();
+        // Check types:
+        let src: &[u8] = $src;
+        let dst: &mut [$ty] = $dst;
+        assert_eq!(src.len(), dst.len() * SIZE);
+        for (src, dst) in src.chunks_exact(SIZE).zip(dst.iter_mut()) {
+            *dst = <$ty>::$from_bytes(src.try_into().unwrap());
         }
     }};
 }
 
-/// Copies a &[u8] $src into a &mut [<numeric>] $dst for the endianness given
-/// by $which (must be either to_be or to_le).
+/// Copies a &[$ty] $src into a &mut [u8] $dst for the endianness given by
+/// $from_bytes (must be either from_be_bytes or from_le_bytes).
 ///
-/// This macro is only safe to call when $src and $dst are &[u8] and &mut [u8],
-/// respectively. The macro will panic if $src.len() != $size * $dst.len(),
-/// where $size represents the size of the integers encoded in $src.
-macro_rules! unsafe_read_slice {
-    ($src:expr, $dst:expr, $size:expr, $which:ident) => {{
-        assert_eq!($src.len(), $size * $dst.len());
-
-        unsafe {
-            copy_nonoverlapping(
-                $src.as_ptr(),
-                $dst.as_mut_ptr() as *mut u8,
-                $src.len(),
-            );
-        }
-        for v in $dst.iter_mut() {
-            *v = v.$which();
-        }
-    }};
-}
-
-/// Copies a &[$ty] $src into a &mut [u8] $dst, where $ty must be a numeric
-/// type. This panics if size_of::<$ty>() * $src.len() != $dst.len().
-///
-/// This macro is only safe to call when $src is a slice of numeric types and
-/// $dst is a &mut [u8] and where $ty represents the type of the integers in
-/// $src.
-macro_rules! unsafe_write_slice_native {
-    ($src:expr, $dst:expr, $ty:ty) => {{
-        let size = core::mem::size_of::<$ty>();
-        assert_eq!(size * $src.len(), $dst.len());
-
-        unsafe {
-            copy_nonoverlapping(
-                $src.as_ptr() as *const u8,
-                $dst.as_mut_ptr(),
-                $dst.len(),
-            );
-        }
-    }};
-}
-
+/// Panics if $src.len() * size_of::<$ty>() != $dst.len().
 macro_rules! write_slice {
-    ($src:expr, $dst:expr, $ty:ty, $size:expr, $write:expr) => {{
-        assert!($size == ::core::mem::size_of::<$ty>());
-        assert_eq!($size * $src.len(), $dst.len());
-
-        for (&n, chunk) in $src.iter().zip($dst.chunks_mut($size)) {
-            $write(chunk, n);
+    ($src:expr, $dst:expr, $ty:ty, $to_bytes:ident) => {{
+        const SIZE: usize = core::mem::size_of::<$ty>();
+        // Check types:
+        let src: &[$ty] = $src;
+        let dst: &mut [u8] = $dst;
+        assert_eq!(src.len() * SIZE, dst.len());
+        for (src, dst) in src.iter().zip(dst.chunks_exact_mut(SIZE)) {
+            dst.copy_from_slice(&src.$to_bytes());
         }
     }};
 }
@@ -1990,52 +1958,40 @@ impl ByteOrder for BigEndian {
 
     #[inline]
     fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
-        assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
-        let mut out = 0u64;
-        let ptr_out = &mut out as *mut u64 as *mut u8;
-        unsafe {
-            copy_nonoverlapping(
-                buf.as_ptr(),
-                ptr_out.offset((8 - nbytes) as isize),
-                nbytes,
-            );
-        }
-        out.to_be()
+        let mut out = [0; 8];
+        assert!(1 <= nbytes && nbytes <= out.len() && nbytes <= buf.len());
+        let start = out.len() - nbytes;
+        out[start..].copy_from_slice(&buf[..nbytes]);
+        u64::from_be_bytes(out)
     }
 
     #[inline]
     fn read_uint128(buf: &[u8], nbytes: usize) -> u128 {
-        assert!(1 <= nbytes && nbytes <= 16 && nbytes <= buf.len());
-        let mut out: u128 = 0;
-        let ptr_out = &mut out as *mut u128 as *mut u8;
-        unsafe {
-            copy_nonoverlapping(
-                buf.as_ptr(),
-                ptr_out.offset((16 - nbytes) as isize),
-                nbytes,
-            );
-        }
-        out.to_be()
+        let mut out = [0; 16];
+        assert!(1 <= nbytes && nbytes <= out.len() && nbytes <= buf.len());
+        let start = out.len() - nbytes;
+        out[start..].copy_from_slice(&buf[..nbytes]);
+        u128::from_be_bytes(out)
     }
 
     #[inline]
     fn write_u16(buf: &mut [u8], n: u16) {
-        unsafe_write_num_bytes!(u16, 2, n, buf, to_be);
+        buf[..2].copy_from_slice(&n.to_be_bytes());
     }
 
     #[inline]
     fn write_u32(buf: &mut [u8], n: u32) {
-        unsafe_write_num_bytes!(u32, 4, n, buf, to_be);
+        buf[..4].copy_from_slice(&n.to_be_bytes());
     }
 
     #[inline]
     fn write_u64(buf: &mut [u8], n: u64) {
-        unsafe_write_num_bytes!(u64, 8, n, buf, to_be);
+        buf[..8].copy_from_slice(&n.to_be_bytes());
     }
 
     #[inline]
     fn write_u128(buf: &mut [u8], n: u128) {
-        unsafe_write_num_bytes!(u128, 16, n, buf, to_be);
+        buf[..16].copy_from_slice(&n.to_be_bytes());
     }
 
     #[inline]
@@ -2068,58 +2024,42 @@ impl ByteOrder for BigEndian {
 
     #[inline]
     fn read_u16_into(src: &[u8], dst: &mut [u16]) {
-        unsafe_read_slice!(src, dst, 2, to_be);
+        read_slice!(src, dst, u16, from_be_bytes);
     }
 
     #[inline]
     fn read_u32_into(src: &[u8], dst: &mut [u32]) {
-        unsafe_read_slice!(src, dst, 4, to_be);
+        read_slice!(src, dst, u32, from_be_bytes);
     }
 
     #[inline]
     fn read_u64_into(src: &[u8], dst: &mut [u64]) {
-        unsafe_read_slice!(src, dst, 8, to_be);
+        read_slice!(src, dst, u64, from_be_bytes);
     }
 
     #[inline]
     fn read_u128_into(src: &[u8], dst: &mut [u128]) {
-        unsafe_read_slice!(src, dst, 16, to_be);
+        read_slice!(src, dst, u128, from_be_bytes);
     }
 
     #[inline]
     fn write_u16_into(src: &[u16], dst: &mut [u8]) {
-        if cfg!(target_endian = "big") {
-            unsafe_write_slice_native!(src, dst, u16);
-        } else {
-            write_slice!(src, dst, u16, 2, Self::write_u16);
-        }
+        write_slice!(src, dst, u16, to_be_bytes);
     }
 
     #[inline]
     fn write_u32_into(src: &[u32], dst: &mut [u8]) {
-        if cfg!(target_endian = "big") {
-            unsafe_write_slice_native!(src, dst, u32);
-        } else {
-            write_slice!(src, dst, u32, 4, Self::write_u32);
-        }
+        write_slice!(src, dst, u32, to_be_bytes);
     }
 
     #[inline]
     fn write_u64_into(src: &[u64], dst: &mut [u8]) {
-        if cfg!(target_endian = "big") {
-            unsafe_write_slice_native!(src, dst, u64);
-        } else {
-            write_slice!(src, dst, u64, 8, Self::write_u64);
-        }
+        write_slice!(src, dst, u64, to_be_bytes);
     }
 
     #[inline]
     fn write_u128_into(src: &[u128], dst: &mut [u8]) {
-        if cfg!(target_endian = "big") {
-            unsafe_write_slice_native!(src, dst, u128);
-        } else {
-            write_slice!(src, dst, u128, 16, Self::write_u128);
-        }
+        write_slice!(src, dst, u128, to_be_bytes);
     }
 
     #[inline]
@@ -2206,44 +2146,38 @@ impl ByteOrder for LittleEndian {
 
     #[inline]
     fn read_uint(buf: &[u8], nbytes: usize) -> u64 {
-        assert!(1 <= nbytes && nbytes <= 8 && nbytes <= buf.len());
-        let mut out = 0u64;
-        let ptr_out = &mut out as *mut u64 as *mut u8;
-        unsafe {
-            copy_nonoverlapping(buf.as_ptr(), ptr_out, nbytes);
-        }
-        out.to_le()
+        let mut out = [0; 8];
+        assert!(1 <= nbytes && nbytes <= out.len() && nbytes <= buf.len());
+        out[..nbytes].copy_from_slice(&buf[..nbytes]);
+        u64::from_le_bytes(out)
     }
 
     #[inline]
     fn read_uint128(buf: &[u8], nbytes: usize) -> u128 {
-        assert!(1 <= nbytes && nbytes <= 16 && nbytes <= buf.len());
-        let mut out: u128 = 0;
-        let ptr_out = &mut out as *mut u128 as *mut u8;
-        unsafe {
-            copy_nonoverlapping(buf.as_ptr(), ptr_out, nbytes);
-        }
-        out.to_le()
+        let mut out = [0; 16];
+        assert!(1 <= nbytes && nbytes <= out.len() && nbytes <= buf.len());
+        out[..nbytes].copy_from_slice(&buf[..nbytes]);
+        u128::from_le_bytes(out)
     }
 
     #[inline]
     fn write_u16(buf: &mut [u8], n: u16) {
-        unsafe_write_num_bytes!(u16, 2, n, buf, to_le);
+        buf[..2].copy_from_slice(&n.to_le_bytes());
     }
 
     #[inline]
     fn write_u32(buf: &mut [u8], n: u32) {
-        unsafe_write_num_bytes!(u32, 4, n, buf, to_le);
+        buf[..4].copy_from_slice(&n.to_le_bytes());
     }
 
     #[inline]
     fn write_u64(buf: &mut [u8], n: u64) {
-        unsafe_write_num_bytes!(u64, 8, n, buf, to_le);
+        buf[..8].copy_from_slice(&n.to_le_bytes());
     }
 
     #[inline]
     fn write_u128(buf: &mut [u8], n: u128) {
-        unsafe_write_num_bytes!(u128, 16, n, buf, to_le);
+        buf[..16].copy_from_slice(&n.to_le_bytes());
     }
 
     #[inline]
@@ -2268,58 +2202,42 @@ impl ByteOrder for LittleEndian {
 
     #[inline]
     fn read_u16_into(src: &[u8], dst: &mut [u16]) {
-        unsafe_read_slice!(src, dst, 2, to_le);
+        read_slice!(src, dst, u16, from_le_bytes);
     }
 
     #[inline]
     fn read_u32_into(src: &[u8], dst: &mut [u32]) {
-        unsafe_read_slice!(src, dst, 4, to_le);
+        read_slice!(src, dst, u32, from_le_bytes);
     }
 
     #[inline]
     fn read_u64_into(src: &[u8], dst: &mut [u64]) {
-        unsafe_read_slice!(src, dst, 8, to_le);
+        read_slice!(src, dst, u64, from_le_bytes);
     }
 
     #[inline]
     fn read_u128_into(src: &[u8], dst: &mut [u128]) {
-        unsafe_read_slice!(src, dst, 16, to_le);
+        read_slice!(src, dst, u128, from_le_bytes);
     }
 
     #[inline]
     fn write_u16_into(src: &[u16], dst: &mut [u8]) {
-        if cfg!(target_endian = "little") {
-            unsafe_write_slice_native!(src, dst, u16);
-        } else {
-            write_slice!(src, dst, u16, 2, Self::write_u16);
-        }
+        write_slice!(src, dst, u16, to_le_bytes);
     }
 
     #[inline]
     fn write_u32_into(src: &[u32], dst: &mut [u8]) {
-        if cfg!(target_endian = "little") {
-            unsafe_write_slice_native!(src, dst, u32);
-        } else {
-            write_slice!(src, dst, u32, 4, Self::write_u32);
-        }
+        write_slice!(src, dst, u32, to_le_bytes);
     }
 
     #[inline]
     fn write_u64_into(src: &[u64], dst: &mut [u8]) {
-        if cfg!(target_endian = "little") {
-            unsafe_write_slice_native!(src, dst, u64);
-        } else {
-            write_slice!(src, dst, u64, 8, Self::write_u64);
-        }
+        write_slice!(src, dst, u64, to_le_bytes);
     }
 
     #[inline]
     fn write_u128_into(src: &[u128], dst: &mut [u8]) {
-        if cfg!(target_endian = "little") {
-            unsafe_write_slice_native!(src, dst, u128);
-        } else {
-            write_slice!(src, dst, u128, 16, Self::write_u128);
-        }
+        write_slice!(src, dst, u128, to_le_bytes);
     }
 
     #[inline]
@@ -2449,6 +2367,7 @@ mod test {
     macro_rules! qc_byte_order {
         ($name:ident, $ty_int:ty, $max:expr,
          $bytes:expr, $read:ident, $write:ident) => {
+            #[cfg(not(miri))]
             mod $name {
                 #[allow(unused_imports)]
                 use super::{qc_sized, Wi128};
@@ -2489,6 +2408,7 @@ mod test {
         };
         ($name:ident, $ty_int:ty, $max:expr,
          $read:ident, $write:ident) => {
+            #[cfg(not(miri))]
             mod $name {
                 #[allow(unused_imports)]
                 use super::{qc_sized, Wi128};
@@ -3405,6 +3325,7 @@ mod stdtests {
     macro_rules! qc_bytes_ext {
         ($name:ident, $ty_int:ty, $max:expr,
          $bytes:expr, $read:ident, $write:ident) => {
+            #[cfg(not(miri))]
             mod $name {
                 #[allow(unused_imports)]
                 use crate::test::{qc_sized, Wi128};
@@ -3455,6 +3376,7 @@ mod stdtests {
             }
         };
         ($name:ident, $ty_int:ty, $max:expr, $read:ident, $write:ident) => {
+            #[cfg(not(miri))]
             mod $name {
                 #[allow(unused_imports)]
                 use crate::test::{qc_sized, Wi128};
@@ -3951,6 +3873,7 @@ mod stdtests {
     // Test slice serialization/deserialization.
     macro_rules! qc_slice {
         ($name:ident, $ty_int:ty, $read:ident, $write:ident, $zero:expr) => {
+            #[cfg(not(miri))]
             mod $name {
                 use super::qc_unsized;
                 #[allow(unused_imports)]


Test patch: