#![doc = include_str!("../example.md")]
#![deny(
future_incompatible,
missing_docs,
nonstandard_style,
unsafe_op_in_unsafe_fn,
unused,
warnings,
clippy::all,
clippy::missing_safety_doc,
clippy::undocumented_unsafe_blocks,
rustdoc::broken_intra_doc_links,
rustdoc::missing_crate_level_docs
)]
#![no_std]
#![cfg_attr(all(docsrs, not(doctest)), feature(doc_cfg))]
#[cfg(feature = "uuid-1")]
mod uuid;
#[cfg(not(feature = "simdutf8"))]
use core::str::from_utf8;
#[cfg(target_has_atomic = "8")]
use core::sync::atomic::{AtomicBool, AtomicI8, AtomicU8};
#[cfg(target_has_atomic = "16")]
use core::sync::atomic::{AtomicI16, AtomicU16};
#[cfg(target_has_atomic = "32")]
use core::sync::atomic::{AtomicI32, AtomicU32};
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::{AtomicI64, AtomicU64};
use core::{
cell::{Cell, UnsafeCell},
error::Error,
ffi::CStr,
fmt,
marker::{PhantomData, PhantomPinned},
mem::ManuallyDrop,
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8,
NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8,
},
ops, ptr,
};
pub use bytecheck_derive::CheckBytes;
pub use rancor;
use rancor::{fail, Fallible, ResultExt as _, Source, Strategy, Trace};
#[cfg(feature = "simdutf8")]
use simdutf8::basic::from_utf8;
pub unsafe trait CheckBytes<C: Fallible + ?Sized> {
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error>;
}
pub unsafe trait Verify<C: Fallible + ?Sized> {
fn verify(&self, context: &mut C) -> Result<(), C::Error>;
}
#[inline]
pub unsafe fn check_bytes<T, E>(value: *const T) -> Result<(), E>
where
T: CheckBytes<Strategy<(), E>> + ?Sized,
{
unsafe { check_bytes_with_context(value, &mut ()) }
}
pub unsafe fn check_bytes_with_context<T, C, E>(
value: *const T,
context: &mut C,
) -> Result<(), E>
where
T: CheckBytes<Strategy<C, E>> + ?Sized,
{
unsafe { CheckBytes::check_bytes(value, Strategy::wrap(context)) }
}
macro_rules! impl_primitive {
($type:ty) => {
unsafe impl<C: Fallible + ?Sized> CheckBytes<C> for $type {
#[inline]
unsafe fn check_bytes(
_: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
Ok(())
}
}
};
}
macro_rules! impl_primitives {
($($type:ty),* $(,)?) => {
$(
impl_primitive!($type);
)*
}
}
impl_primitives! {
(),
i8, i16, i32, i64, i128,
u8, u16, u32, u64, u128,
f32, f64,
}
#[cfg(target_has_atomic = "8")]
impl_primitives!(AtomicI8, AtomicU8);
#[cfg(target_has_atomic = "16")]
impl_primitives!(AtomicI16, AtomicU16);
#[cfg(target_has_atomic = "32")]
impl_primitives!(AtomicI32, AtomicU32);
#[cfg(target_has_atomic = "64")]
impl_primitives!(AtomicI64, AtomicU64);
unsafe impl<T: ?Sized, C: Fallible + ?Sized> CheckBytes<C> for PhantomData<T> {
#[inline]
unsafe fn check_bytes(_: *const Self, _: &mut C) -> Result<(), C::Error> {
Ok(())
}
}
unsafe impl<C: Fallible + ?Sized> CheckBytes<C> for PhantomPinned {
#[inline]
unsafe fn check_bytes(_: *const Self, _: &mut C) -> Result<(), C::Error> {
Ok(())
}
}
unsafe impl<T, C> CheckBytes<C> for ManuallyDrop<T>
where
T: CheckBytes<C> + ?Sized,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
c: &mut C,
) -> Result<(), C::Error> {
let inner_ptr =
unsafe { core::mem::transmute::<*const Self, *const T>(value) };
unsafe {
T::check_bytes(inner_ptr, c)
.trace("while checking inner value of `ManuallyDrop`")
}
}
}
unsafe impl<T, C> CheckBytes<C> for UnsafeCell<T>
where
T: CheckBytes<C> + ?Sized,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
c: &mut C,
) -> Result<(), C::Error> {
let inner_ptr =
unsafe { core::mem::transmute::<*const Self, *const T>(value) };
unsafe {
T::check_bytes(inner_ptr, c)
.trace("while checking inner value of `UnsafeCell`")
}
}
}
unsafe impl<T, C> CheckBytes<C> for Cell<T>
where
T: CheckBytes<C> + ?Sized,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
c: &mut C,
) -> Result<(), C::Error> {
let inner_ptr =
unsafe { core::mem::transmute::<*const Self, *const T>(value) };
unsafe {
T::check_bytes(inner_ptr, c)
.trace("while checking inner value of `Cell`")
}
}
}
#[derive(Debug)]
struct BoolCheckError {
byte: u8,
}
impl fmt::Display for BoolCheckError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"bool set to invalid byte {}, expected either 0 or 1",
self.byte,
)
}
}
impl Error for BoolCheckError {}
unsafe impl<C> CheckBytes<C> for bool
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
let byte = unsafe { *value.cast::<u8>() };
match byte {
0 | 1 => Ok(()),
_ => fail!(BoolCheckError { byte }),
}
}
}
#[cfg(target_has_atomic = "8")]
unsafe impl<C> CheckBytes<C> for AtomicBool
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe { bool::check_bytes(value.cast(), context) }
}
}
unsafe impl<C> CheckBytes<C> for char
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(ptr: *const Self, _: &mut C) -> Result<(), C::Error> {
let value = unsafe { ptr.cast::<u32>().read_unaligned() };
char::try_from(value).into_error()?;
Ok(())
}
}
#[derive(Debug)]
struct TupleIndexContext {
index: usize,
}
impl fmt::Display for TupleIndexContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "while checking index {} of tuple", self.index)
}
}
macro_rules! impl_tuple {
($($type:ident $index:tt),*) => {
unsafe impl<$($type,)* C> CheckBytes<C> for ($($type,)*)
where
$($type: CheckBytes<C>,)*
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
#[allow(clippy::unneeded_wildcard_pattern)]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
$(
unsafe {
<$type>::check_bytes(
ptr::addr_of!((*value).$index),
context,
).with_trace(|| TupleIndexContext { index: $index })?;
}
)*
Ok(())
}
}
}
}
impl_tuple!(T0 0);
impl_tuple!(T0 0, T1 1);
impl_tuple!(T0 0, T1 1, T2 2);
impl_tuple!(T0 0, T1 1, T2 2, T3 3);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7, T8 8);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7, T8 8, T9 9);
impl_tuple!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7, T8 8, T9 9, T10 10);
impl_tuple!(
T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7, T8 8, T9 9, T10 10, T11 11
);
impl_tuple!(
T0 0, T1 1, T2 2, T3 3, T4 4, T5 5, T6 6, T7 7, T8 8, T9 9, T10 10, T11 11,
T12 12
);
#[derive(Debug)]
struct ArrayCheckContext {
index: usize,
}
impl fmt::Display for ArrayCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "while checking index '{}' of array", self.index)
}
}
unsafe impl<T, const N: usize, C> CheckBytes<C> for [T; N]
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
let base = value.cast::<T>();
for index in 0..N {
unsafe {
T::check_bytes(base.add(index), context)
.with_trace(|| ArrayCheckContext { index })?;
}
}
Ok(())
}
}
#[derive(Debug)]
struct SliceCheckContext {
index: usize,
}
impl fmt::Display for SliceCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "while checking index '{}' of slice", self.index)
}
}
unsafe impl<T, C> CheckBytes<C> for [T]
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
let (data_address, len) = ptr_meta::to_raw_parts(value);
let base = data_address.cast::<T>();
for index in 0..len {
unsafe {
T::check_bytes(base.add(index), context)
.with_trace(|| SliceCheckContext { index })?;
}
}
Ok(())
}
}
unsafe impl<C> CheckBytes<C> for str
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
#[derive(Debug)]
struct Utf8Error;
impl fmt::Display for Utf8Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid UTF-8")
}
}
impl Error for Utf8Error {}
let slice_ptr = value as *const [u8];
let slice = unsafe { &*slice_ptr };
if !slice.is_ascii() {
from_utf8(slice).map_err(|_| Utf8Error).into_error()?;
}
Ok(())
}
}
unsafe impl<C> CheckBytes<C> for CStr
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
let slice_ptr = value as *const [u8];
let slice = unsafe { &*slice_ptr };
CStr::from_bytes_with_nul(slice).into_error()?;
Ok(())
}
}
#[derive(Debug)]
pub struct StructCheckContext {
pub struct_name: &'static str,
pub field_name: &'static str,
}
impl fmt::Display for StructCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"while checking field '{}' of struct '{}'",
self.field_name, self.struct_name
)
}
}
#[derive(Debug)]
pub struct TupleStructCheckContext {
pub tuple_struct_name: &'static str,
pub field_index: usize,
}
impl fmt::Display for TupleStructCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"while checking field index {} of tuple struct '{}'",
self.field_index, self.tuple_struct_name,
)
}
}
#[derive(Debug)]
pub struct InvalidEnumDiscriminantError<T> {
pub enum_name: &'static str,
pub invalid_discriminant: T,
}
impl<T: fmt::Display> fmt::Display for InvalidEnumDiscriminantError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid discriminant '{}' for enum '{}'",
self.invalid_discriminant, self.enum_name
)
}
}
impl<T> Error for InvalidEnumDiscriminantError<T> where
T: fmt::Debug + fmt::Display
{
}
#[derive(Debug)]
pub struct NamedEnumVariantCheckContext {
pub enum_name: &'static str,
pub variant_name: &'static str,
pub field_name: &'static str,
}
impl fmt::Display for NamedEnumVariantCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"while checking field '{}' of variant '{}' of enum '{}'",
self.field_name, self.variant_name, self.enum_name,
)
}
}
#[derive(Debug)]
pub struct UnnamedEnumVariantCheckContext {
pub enum_name: &'static str,
pub variant_name: &'static str,
pub field_index: usize,
}
impl fmt::Display for UnnamedEnumVariantCheckContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"while checking field index {} of variant '{}' of enum '{}'",
self.field_index, self.variant_name, self.enum_name,
)
}
}
unsafe impl<T, C> CheckBytes<C> for ops::Range<T>
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe {
T::check_bytes(ptr::addr_of!((*value).start), context).with_trace(
|| StructCheckContext {
struct_name: "Range",
field_name: "start",
},
)?;
}
unsafe {
T::check_bytes(ptr::addr_of!((*value).end), context).with_trace(
|| StructCheckContext {
struct_name: "Range",
field_name: "end",
},
)?;
}
Ok(())
}
}
unsafe impl<T, C> CheckBytes<C> for ops::RangeFrom<T>
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe {
T::check_bytes(ptr::addr_of!((*value).start), context).with_trace(
|| StructCheckContext {
struct_name: "RangeFrom",
field_name: "start",
},
)?;
}
Ok(())
}
}
unsafe impl<C: Fallible + ?Sized> CheckBytes<C> for ops::RangeFull {
#[inline]
unsafe fn check_bytes(_: *const Self, _: &mut C) -> Result<(), C::Error> {
Ok(())
}
}
unsafe impl<T, C> CheckBytes<C> for ops::RangeTo<T>
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe {
T::check_bytes(ptr::addr_of!((*value).end), context).with_trace(
|| StructCheckContext {
struct_name: "RangeTo",
field_name: "end",
},
)?;
}
Ok(())
}
}
unsafe impl<T, C> CheckBytes<C> for ops::RangeToInclusive<T>
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
C::Error: Trace,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe {
T::check_bytes(ptr::addr_of!((*value).end), context).with_trace(
|| StructCheckContext {
struct_name: "RangeToInclusive",
field_name: "end",
},
)?;
}
Ok(())
}
}
#[derive(Debug)]
struct NonZeroCheckError;
impl fmt::Display for NonZeroCheckError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "nonzero integer is zero")
}
}
impl Error for NonZeroCheckError {}
macro_rules! impl_nonzero {
($nonzero:ident, $underlying:ident) => {
unsafe impl<C> CheckBytes<C> for $nonzero
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
unsafe fn check_bytes(
value: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
if unsafe { *value.cast::<$underlying>() } == 0 {
fail!(NonZeroCheckError);
} else {
Ok(())
}
}
}
};
}
impl_nonzero!(NonZeroI8, i8);
impl_nonzero!(NonZeroI16, i16);
impl_nonzero!(NonZeroI32, i32);
impl_nonzero!(NonZeroI64, i64);
impl_nonzero!(NonZeroI128, i128);
impl_nonzero!(NonZeroU8, u8);
impl_nonzero!(NonZeroU16, u16);
impl_nonzero!(NonZeroU32, u32);
impl_nonzero!(NonZeroU64, u64);
impl_nonzero!(NonZeroU128, u128);
#[cfg(test)]
#[macro_use]
mod tests {
use core::ffi::CStr;
use rancor::{Failure, Fallible, Infallible, Source, Strategy};
use crate::{check_bytes, check_bytes_with_context, CheckBytes, Verify};
#[derive(Debug)]
#[repr(transparent)]
struct CharLE(u32);
impl From<char> for CharLE {
fn from(c: char) -> Self {
#[cfg(target_endian = "little")]
{
Self(c as u32)
}
#[cfg(target_endian = "big")]
{
Self((c as u32).swap_bytes())
}
}
}
unsafe impl<C> CheckBytes<C> for CharLE
where
C: Fallible + ?Sized,
C::Error: Source,
{
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
#[cfg(target_endian = "little")]
unsafe {
char::check_bytes(value.cast(), context)
}
#[cfg(target_endian = "big")]
unsafe {
let mut bytes = *value.cast::<[u8; 4]>();
bytes.reverse();
char::check_bytes(bytes.as_ref().as_ptr().cast(), context)
}
}
}
#[repr(C, align(16))]
struct Aligned<const N: usize>(pub [u8; N]);
macro_rules! bytes {
($($byte:literal),* $(,)?) => {
(&$crate::tests::Aligned([$($byte,)*]).0 as &[u8]).as_ptr()
}
}
#[test]
fn test_tuples() {
unsafe {
check_bytes::<_, Failure>(&(42u32, true, 'x')).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&(true,)).unwrap();
}
unsafe {
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 1u8, 255u8, 255u8, 255u8, 0x78u8, 0u8,
0u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
42u8, 16u8, 20u8, 3u8, 1u8, 255u8, 255u8, 255u8, 0x78u8,
0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 1u8, 255u8, 255u8, 255u8, 0x00u8,
0xd8u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 1u8, 255u8, 255u8, 255u8, 0x00u8,
0x00u8, 0x11u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 0x78u8, 0u8,
0u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<(u32, bool, CharLE), Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 2u8, 255u8, 255u8, 255u8, 0x78u8, 0u8,
0u8, 0u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
}
}
#[test]
fn test_arrays() {
unsafe {
check_bytes::<_, Failure>(&[true, false, true, false]).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&[false, true]).unwrap();
}
unsafe {
check_bytes::<[bool; 4], Failure>(
bytes![1u8, 0u8, 1u8, 0u8].cast(),
)
.unwrap();
check_bytes::<[bool; 4], Failure>(
bytes![1u8, 2u8, 1u8, 0u8].cast(),
)
.unwrap_err();
check_bytes::<[bool; 4], Failure>(
bytes![2u8, 0u8, 1u8, 0u8].cast(),
)
.unwrap_err();
check_bytes::<[bool; 4], Failure>(
bytes![1u8, 0u8, 1u8, 2u8].cast(),
)
.unwrap_err();
check_bytes::<[bool; 4], Failure>(
bytes![1u8, 0u8, 1u8, 0u8, 2u8].cast(),
)
.unwrap();
}
}
#[test]
fn test_unsized() {
unsafe {
check_bytes::<[i32], Infallible>(
&[1, 2, 3, 4] as &[i32] as *const [i32]
)
.unwrap();
check_bytes::<str, Failure>("hello world" as *const str).unwrap();
}
}
#[test]
fn test_c_str() {
macro_rules! test_cases {
($($bytes:expr, $pat:pat,)*) => {
$(
let bytes = $bytes;
let c_str = ::ptr_meta::from_raw_parts(
bytes.as_ptr().cast(),
bytes.len(),
);
assert!(matches!(
check_bytes::<CStr, Failure>(c_str),
$pat,
));
)*
}
}
unsafe {
test_cases! {
b"hello world\0", Ok(_),
b"hello world", Err(_),
b"", Err(_),
[0xc3u8, 0x28u8, 0x00u8], Ok(_),
[0xc3u8, 0x28u8, 0x00u8, 0xc3u8, 0x28u8, 0x00u8], Err(_),
}
}
}
#[test]
fn test_unit_struct() {
#[derive(CheckBytes)]
#[bytecheck(crate)]
struct Test;
unsafe {
check_bytes::<_, Infallible>(&Test).unwrap();
}
}
#[test]
fn test_tuple_struct() {
#[derive(CheckBytes, Debug)]
#[bytecheck(crate)]
struct Test(u32, bool, CharLE);
let value = Test(42, true, 'x'.into());
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
unsafe {
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
42u8, 16u8, 20u8, 3u8, 0x78u8, 0u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x00u8, 0xd8u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x00u8, 0x00u8, 0x11u8, 0u8, 1u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 0u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 2u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
}
}
#[test]
fn test_struct() {
#[derive(CheckBytes, Debug)]
#[bytecheck(crate)]
struct Test {
a: u32,
b: bool,
c: CharLE,
}
let value = Test {
a: 42,
b: true,
c: 'x'.into(),
};
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
unsafe {
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
42u8, 16u8, 20u8, 3u8, 0x78u8, 0u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x00u8, 0xd8u8, 0u8, 0u8, 1u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x00u8, 0x00u8, 0x11u8, 0u8, 1u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 0u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 0x78u8, 0u8, 0u8, 0u8, 2u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
}
}
#[test]
fn test_generic_struct() {
#[derive(CheckBytes, Debug)]
#[bytecheck(crate)]
struct Test<T> {
a: u32,
b: T,
}
let value = Test { a: 42, b: true };
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
unsafe {
check_bytes::<Test<bool>, Failure>(
bytes![0u8, 0u8, 0u8, 0u8, 1u8, 255u8, 255u8, 255u8].cast(),
)
.unwrap();
check_bytes::<Test<bool>, Failure>(
bytes![12u8, 34u8, 56u8, 78u8, 1u8, 255u8, 255u8, 255u8].cast(),
)
.unwrap();
check_bytes::<Test<bool>, Failure>(
bytes![0u8, 0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8].cast(),
)
.unwrap();
check_bytes::<Test<bool>, Failure>(
bytes![0u8, 0u8, 0u8, 0u8, 2u8, 255u8, 255u8, 255u8].cast(),
)
.unwrap_err();
}
}
#[test]
fn test_enum() {
#[allow(dead_code)]
#[derive(CheckBytes, Debug)]
#[bytecheck(crate)]
#[repr(u8)]
enum Test {
A(u32, bool, CharLE),
B { a: u32, b: bool, c: CharLE },
C,
}
let value = Test::A(42, true, 'x'.into());
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
let value = Test::B {
a: 42,
b: true,
c: 'x'.into(),
};
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
let value = Test::C;
unsafe {
check_bytes::<_, Failure>(&value).unwrap();
}
unsafe {
check_bytes::<Test, Failure>(
bytes![
0u8, 0u8, 0u8, 0u8, 12u8, 34u8, 56u8, 78u8, 1u8, 255u8,
255u8, 255u8, 120u8, 0u8, 0u8, 0u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
1u8, 0u8, 0u8, 0u8, 12u8, 34u8, 56u8, 78u8, 1u8, 255u8,
255u8, 255u8, 120u8, 0u8, 0u8, 0u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
2u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
255u8, 255u8, 255u8, 255u8, 25u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap();
check_bytes::<Test, Failure>(
bytes![
3u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
255u8, 255u8, 255u8, 255u8, 25u8, 255u8, 255u8, 255u8,
]
.cast(),
)
.unwrap_err();
}
}
#[test]
fn test_explicit_enum_values() {
#[derive(CheckBytes, Debug)]
#[bytecheck(crate)]
#[repr(u8)]
enum Test {
A,
B = 100,
C,
D = 200,
E,
}
unsafe {
check_bytes::<_, Failure>(&Test::A).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&Test::B).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&Test::C).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&Test::D).unwrap();
}
unsafe {
check_bytes::<_, Failure>(&Test::E).unwrap();
}
unsafe {
check_bytes::<Test, Failure>(bytes![1u8].cast()).unwrap_err();
check_bytes::<Test, Failure>(bytes![99u8].cast()).unwrap_err();
check_bytes::<Test, Failure>(bytes![102u8].cast()).unwrap_err();
check_bytes::<Test, Failure>(bytes![199u8].cast()).unwrap_err();
check_bytes::<Test, Failure>(bytes![202u8].cast()).unwrap_err();
check_bytes::<Test, Failure>(bytes![255u8].cast()).unwrap_err();
}
}
#[test]
fn test_recursive() {
struct MyBox<T: ?Sized> {
inner: *const T,
}
unsafe impl<T, C> CheckBytes<C> for MyBox<T>
where
T: CheckBytes<C>,
C: Fallible + ?Sized,
{
unsafe fn check_bytes(
value: *const Self,
context: &mut C,
) -> Result<(), C::Error> {
unsafe { T::check_bytes((*value).inner, context) }
}
}
#[allow(dead_code)]
#[derive(CheckBytes)]
#[bytecheck(crate)]
#[repr(u8)]
enum Node {
Nil,
Cons(#[bytecheck(omit_bounds)] MyBox<Node>),
}
unsafe {
let nil = Node::Nil;
let cons = Node::Cons(MyBox {
inner: &nil as *const Node,
});
check_bytes::<Node, Failure>(&cons).unwrap();
}
}
#[test]
fn test_explicit_crate_root() {
mod bytecheck {}
mod m {
pub use crate as bc;
}
#[derive(CheckBytes)]
#[bytecheck(crate = m::bc)]
struct Test;
unsafe {
check_bytes::<_, Infallible>(&Test).unwrap();
}
#[derive(CheckBytes)]
#[bytecheck(crate = crate)]
struct Test2;
unsafe {
check_bytes::<_, Infallible>(&Test2).unwrap();
}
}
trait MyContext {
fn set_value(&mut self, value: i32);
}
impl<T: MyContext, E> MyContext for Strategy<T, E> {
fn set_value(&mut self, value: i32) {
T::set_value(self, value)
}
}
struct FooContext {
value: i32,
}
impl MyContext for FooContext {
fn set_value(&mut self, value: i32) {
self.value = value;
}
}
#[test]
fn test_derive_verify_unit_struct() {
unsafe impl<C: Fallible + MyContext + ?Sized> Verify<C> for UnitStruct {
fn verify(&self, context: &mut C) -> Result<(), C::Error> {
context.set_value(1);
Ok(())
}
}
#[derive(CheckBytes)]
#[bytecheck(crate, verify)]
struct UnitStruct;
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Infallible>(
&UnitStruct,
&mut context,
)
.unwrap();
}
assert_eq!(context.value, 1);
}
#[test]
fn test_derive_verify_struct() {
unsafe impl<C: Fallible + MyContext + ?Sized> Verify<C> for Struct {
fn verify(&self, context: &mut C) -> Result<(), C::Error> {
context.set_value(self.value);
Ok(())
}
}
#[derive(CheckBytes)]
#[bytecheck(crate, verify)]
struct Struct {
value: i32,
}
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Infallible>(
&Struct { value: 4 },
&mut context,
)
.unwrap();
}
assert_eq!(context.value, 4);
}
#[test]
fn test_derive_verify_tuple_struct() {
unsafe impl<C> Verify<C> for TupleStruct
where
C: Fallible + MyContext + ?Sized,
{
fn verify(&self, context: &mut C) -> Result<(), C::Error> {
context.set_value(self.0);
Ok(())
}
}
#[derive(CheckBytes)]
#[bytecheck(crate, verify)]
struct TupleStruct(i32);
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Infallible>(
&TupleStruct(10),
&mut context,
)
.unwrap();
}
assert_eq!(context.value, 10);
}
#[test]
fn test_derive_verify_enum() {
unsafe impl<C: Fallible + MyContext + ?Sized> Verify<C> for Enum {
fn verify(&self, context: &mut C) -> Result<(), C::Error> {
match self {
Enum::A => context.set_value(2),
Enum::B(value) => context.set_value(*value),
Enum::C { value } => context.set_value(*value),
}
Ok(())
}
}
#[derive(CheckBytes)]
#[bytecheck(crate, verify)]
#[repr(u8)]
enum Enum {
A,
B(i32),
C { value: i32 },
}
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Failure>(&Enum::A, &mut context)
.unwrap();
}
assert_eq!(context.value, 2);
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Failure>(
&Enum::B(5),
&mut context,
)
.unwrap();
}
assert_eq!(context.value, 5);
let mut context = FooContext { value: 0 };
unsafe {
check_bytes_with_context::<_, _, Failure>(
&Enum::C { value: 7 },
&mut context,
)
.unwrap();
}
assert_eq!(context.value, 7);
}
}