Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .github/workflows/test-bytecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: test bytecheck
run-name: ${{ github.actor }}'s patch
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: true
toolchain: nightly
- run: |
cargo test --no-default-features --features=bytecheck
cargo test --no-default-features --features=bytecheck,std
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

- Added optional `bytecheck` support. Enable using the `bytecheck` feature.

### Fixed

- Implemented `Step::forward_overflowing()` and `Step::backward_overflowing()`, which are new required
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ borsh = ["dep:borsh"]

schemars = ["dep:schemars", "std"]

# Derive/implement traits for bytecheck
bytecheck = ["dep:bytecheck"]

# Implement traits for bytemuck
bytemuck = ["dep:bytemuck"]

Expand All @@ -52,6 +55,7 @@ borsh = { version = "1.5.1", optional = true, features = ["unstable__schema"], d
schemars = { version = "0.8.21", optional = true, features = ["derive"], default-features = false }
bytemuck = { version = "1", optional = true, default-features = false }
bin-proto = { version = "0.12.2", optional = true, default-features = false }
bytecheck = { version = "0.8.2", optional = true, default-features = false }

arbitrary = { version = "1", optional = true, default-features = false }
quickcheck = { version = "1", optional = true, default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,5 @@ impl fmt::Display for TryNewError {
write!(f, "Value too large to fit within this integer type")
}
}

impl core::error::Error for TryNewError {}
20 changes: 20 additions & 0 deletions src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ impl_signed_integer_native!((i8, u8), (i16, u16), (i32, u32), (i64, u64), (i128,
/// [`bytemuck::NoUninit`]: https://docs.rs/bytemuck/1/bytemuck/trait.NoUninit.html
/// [`bytemuck::Contiguous`]: https://docs.rs/bytemuck/1/bytemuck/trait.Contiguous.html
#[derive(Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "bytecheck", derive(bytecheck::CheckBytes))]
#[cfg_attr(feature = "bytecheck", bytecheck(verify))]
#[repr(transparent)]
pub struct Int<T: SignedInteger + BuiltinInteger, const BITS: usize> {
value: T,
Expand Down Expand Up @@ -1548,6 +1550,24 @@ macro_rules! int_impl {
};
}

#[cfg(feature = "bytecheck")]
unsafe impl<
T: SignedInteger + BuiltinInteger + Copy,
const BITS: usize,
C: bytecheck::rancor::Fallible + ?Sized,
> bytecheck::Verify<C> for Int<T, BITS>
where
C::Error: bytecheck::rancor::Source,
Self: Integer,
{
fn verify(&self, _context: &mut C) -> Result<(), C::Error> {
if self.value > Self::MAX.value || self.value < Self::MIN.value {
bytecheck::rancor::fail!(TryNewError);
}
Ok(())
}
}

// Because the methods within this macro are effectively copy-pasted for each underlying integer type,
// each documentation test gets executed five times (once for each underlying type), even though the
// tests themselves aren't specific to said underlying type. This severely slows down `cargo test`,
Expand Down
20 changes: 20 additions & 0 deletions src/unsigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ impl_integer_native!((u8, i8), (u16, i16), (u32, i32), (u64, i64), (u128, i128))
///
/// When `cfg(feature = "bytemuck")` is set, the appropriate bytemuck traits will be implemented.
#[derive(Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "bytecheck", derive(bytecheck::CheckBytes))]
#[cfg_attr(feature = "bytecheck", bytecheck(verify))]
#[repr(transparent)]
pub struct UInt<T: UnsignedInteger + BuiltinInteger, const BITS: usize> {
value: T,
Expand Down Expand Up @@ -161,6 +163,24 @@ where
pub const MASK: T = Self::MAX.value;
}

#[cfg(feature = "bytecheck")]
unsafe impl<
T: UnsignedInteger + BuiltinInteger + Copy,
const BITS: usize,
C: bytecheck::rancor::Fallible + ?Sized,
> bytecheck::Verify<C> for UInt<T, BITS>
where
C::Error: bytecheck::rancor::Source,
Self: Integer,
{
fn verify(&self, _context: &mut C) -> Result<(), C::Error> {
if self.value > Self::MAX.value {
bytecheck::rancor::fail!(TryNewError);
}
Ok(())
}
}

// Next are specific implementations for u8, u16, u32, u64 and u128. A couple notes:
// - The existence of MAX also serves as a neat bounds-check for BITS: If BITS is too large,
// the subtraction overflows which will fail to compile. This simplifies things a lot.
Expand Down
34 changes: 34 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4494,6 +4494,40 @@ mod bin_proto_tests {
}
}

#[cfg(feature = "bytecheck")]
mod bytecheck_tests {
use super::*;
use bytecheck::{check_bytes, rancor};

#[test]
fn test_validity_unsigned() {
let good_u6_val = 0x3fu8;
unsafe { check_bytes::<u6, rancor::Failure>((&raw const good_u6_val).cast()).unwrap() };

let bad_u6_val = 0x40u8;
unsafe { check_bytes::<u6, rancor::Failure>((&raw const bad_u6_val).cast()).unwrap_err() };
}

#[test]
fn test_validity_signed() {
let good_i31_val = i31::MAX.value() as i32;
unsafe { check_bytes::<i31, rancor::Failure>((&raw const good_i31_val).cast()).unwrap() };

let good_i32_val2 = i31::MIN.value() as i32;
unsafe { check_bytes::<i31, rancor::Failure>((&raw const good_i32_val2).cast()).unwrap() };

let bad_i31_val = (i31::MAX.value() as i32) + 1;
unsafe {
check_bytes::<i31, rancor::Failure>((&raw const bad_i31_val).cast()).unwrap_err()
};

let bad_i31_val2 = (i31::MIN.value() as i32) - 1;
unsafe {
check_bytes::<i31, rancor::Failure>((&raw const bad_i31_val2).cast()).unwrap_err()
};
}
}

#[test]
fn new_and_as_specific_types() {
let a = u6::new(42);
Expand Down
Loading