Skip to content

Commit

Permalink
Improve documentation, add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
djkoloski committed Sep 10, 2024
1 parent 3f2b1db commit 4eec0f6
Showing 1 changed file with 191 additions and 3 deletions.
194 changes: 191 additions & 3 deletions bytecheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,48 @@ use simdutf8::basic::from_utf8;
/// `Self`. Because `value` must always be properly aligned for `Self` and point
/// to enough bytes to represent the type, this implies that `value` may be
/// dereferenced safely.
///
/// # Example
///
/// ```
/// use core::{error::Error, fmt};
///
/// use bytecheck::CheckBytes;
/// use rancor::{fail, Fallible, Source};
///
/// #[repr(C, align(4))]
/// pub struct NonMaxU32(u32);
///
/// unsafe impl<C: Fallible + ?Sized> CheckBytes<C> for NonMaxU32
/// where
/// C::Error: Source,
/// {
/// unsafe fn check_bytes(
/// value: *const Self,
/// context: &mut C,
/// ) -> Result<(), C::Error> {
/// #[derive(Debug)]
/// struct NonMaxCheckError;
///
/// impl fmt::Display for NonMaxCheckError {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "non-max u32 was set to u32::MAX")
/// }
/// }
///
/// impl Error for NonMaxCheckError {}
///
/// let value = unsafe { value.read() };
/// if value.0 == u32::MAX {
/// fail!(NonMaxCheckError);
/// }
///
/// Ok(())
/// }
/// }
/// ```
///
/// See [`Verify`] for an example which uses less unsafe.
pub unsafe trait CheckBytes<C: Fallible + ?Sized> {
/// Checks whether the given pointer points to a valid value within the
/// given context.
Expand All @@ -128,12 +170,54 @@ pub unsafe trait CheckBytes<C: Fallible + ?Sized> {

/// A type that can check whether its invariants are upheld.
///
/// When using [the derive](macro@CheckBytes), adding `#[bytecheck(verify)]`
/// allows implementing `Verify` for the derived type. [`Verify::verify`] will
/// be called after the type is checked and all fields are known to be valid.
///
/// # Safety
///
/// - `verify` must only return `Ok` if all of the invariants of this type are
/// upheld by `self`.
/// - `verify` may not assume that its type invariants are upheld by the given
/// `self.`
/// `self` (the invariants of each field are guaranteed to be upheld).
///
/// # Example
///
/// ```
/// use core::{error::Error, fmt};
///
/// use bytecheck::{CheckBytes, Verify};
/// use rancor::{fail, Fallible, Source};
///
/// #[derive(CheckBytes)]
/// #[bytecheck(verify)]
/// #[repr(C, align(4))]
/// pub struct NonMaxU32(u32);
///
/// unsafe impl<C: Fallible + ?Sized> Verify<C> for NonMaxU32
/// where
/// C::Error: Source,
/// {
/// fn verify(&self, context: &mut C) -> Result<(), C::Error> {
/// #[derive(Debug)]
/// struct NonMaxCheckError;
///
/// impl fmt::Display for NonMaxCheckError {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "non-max u32 was set to u32::MAX")
/// }
/// }
///
/// impl Error for NonMaxCheckError {}
///
/// if self.0 == u32::MAX {
/// fail!(NonMaxCheckError);
/// }
///
/// Ok(())
/// }
/// }
/// ```
pub unsafe trait Verify<C: Fallible + ?Sized> {
/// Checks whether the invariants of this type are upheld by `self`.
fn verify(&self, context: &mut C) -> Result<(), C::Error>;
Expand All @@ -145,6 +229,22 @@ pub unsafe trait Verify<C: Fallible + ?Sized> {
///
/// The passed pointer must be aligned and point to enough initialized bytes to
/// represent the type.
///
/// # Example
///
/// ```
/// use bytecheck::check_bytes;
/// use rancor::Failure;
///
/// unsafe {
/// // 0 and 1 are valid values for bools
/// check_bytes::<bool, Failure>((&0u8 as *const u8).cast()).unwrap();
/// check_bytes::<bool, Failure>((&1u8 as *const u8).cast()).unwrap();
///
/// // 2 is not a valid value
/// check_bytes::<bool, Failure>((&2u8 as *const u8).cast()).unwrap_err();
/// }
/// ```
#[inline]
pub unsafe fn check_bytes<T, E>(value: *const T) -> Result<(), E>
where
Expand All @@ -162,6 +262,79 @@ where
///
/// The passed pointer must be aligned and point to enough initialized bytes to
/// represent the type.
///
/// # Example
///
/// ```
/// use core::{error::Error, fmt};
///
/// use bytecheck::{check_bytes_with_context, CheckBytes, Verify};
/// use rancor::{fail, Failure, Fallible, Source, Strategy};
///
/// trait Context {
/// fn is_allowed(&self, value: u8) -> bool;
/// }
///
/// impl<T: Context + ?Sized, E> Context for Strategy<T, E> {
/// fn is_allowed(&self, value: u8) -> bool {
/// T::is_allowed(self, value)
/// }
/// }
///
/// struct Allowed(u8);
///
/// impl Context for Allowed {
/// fn is_allowed(&self, value: u8) -> bool {
/// value == self.0
/// }
/// }
///
/// #[derive(CheckBytes)]
/// #[bytecheck(verify)]
/// #[repr(C)]
/// pub struct ContextualByte(u8);
///
/// unsafe impl<C: Context + Fallible + ?Sized> Verify<C> for ContextualByte
/// where
/// C::Error: Source,
/// {
/// fn verify(&self, context: &mut C) -> Result<(), C::Error> {
/// #[derive(Debug)]
/// struct InvalidByte(u8);
///
/// impl fmt::Display for InvalidByte {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "invalid contextual byte: {}", self.0)
/// }
/// }
///
/// impl Error for InvalidByte {}
///
/// if !context.is_allowed(self.0) {
/// fail!(InvalidByte(self.0));
/// }
///
/// Ok(())
/// }
/// }
///
/// let value = 45u8;
/// unsafe {
/// // Checking passes when the context allows byte 45
/// check_bytes_with_context::<ContextualByte, _, Failure>(
/// (&value as *const u8).cast(),
/// &mut Allowed(45),
/// )
/// .unwrap();
///
/// // Checking fails when the conteext does not allow byte 45
/// check_bytes_with_context::<ContextualByte, _, Failure>(
/// (&value as *const u8).cast(),
/// &mut Allowed(0),
/// )
/// .unwrap_err();
/// }
/// ```
pub unsafe fn check_bytes_with_context<T, C, E>(
value: *const T,
context: &mut C,
Expand Down Expand Up @@ -606,6 +779,9 @@ where
// Generic contexts used by the derive.

/// Context for errors resulting from invalid structs.
///
/// This context is used by the derive macro to trace which field of a struct
/// failed validation.
#[derive(Debug)]
pub struct StructCheckContext {
/// The name of the struct with an invalid field.
Expand All @@ -625,6 +801,9 @@ impl fmt::Display for StructCheckContext {
}

/// Context for errors resulting from invalid tuple structs.
///
/// This context is used by the derive macro to trace which field of a tuple
/// struct failed validation.
#[derive(Debug)]
pub struct TupleStructCheckContext {
/// The name of the tuple struct with an invalid field.
Expand All @@ -644,6 +823,9 @@ impl fmt::Display for TupleStructCheckContext {
}

/// An error resulting from an invalid enum tag.
///
/// This context is used by the derive macro to trace what the invalid
/// discriminant for an enum is.
#[derive(Debug)]
pub struct InvalidEnumDiscriminantError<T> {
/// The name of the enum with an invalid discriminant.
Expand All @@ -668,6 +850,9 @@ impl<T> Error for InvalidEnumDiscriminantError<T> where
}

/// Context for errors resulting from checking enum variants with named fields.
///
/// This context is used by the derive macro to trace which field of an enum
/// variant failed validation.
#[derive(Debug)]
pub struct NamedEnumVariantCheckContext {
/// The name of the enum with an invalid variant.
Expand All @@ -690,6 +875,9 @@ impl fmt::Display for NamedEnumVariantCheckContext {

/// Context for errors resulting from checking enum variants with unnamed
/// fields.
///
/// This context is used by the derive macro to trace which field of an enum
/// variant failed validation.
#[derive(Debug)]
pub struct UnnamedEnumVariantCheckContext {
/// The name of the enum with an invalid variant.
Expand Down Expand Up @@ -908,7 +1096,7 @@ mod tests {

#[derive(Debug)]
#[repr(transparent)]
pub struct CharLE(u32);
struct CharLE(u32);

impl From<char> for CharLE {
fn from(c: char) -> Self {
Expand Down Expand Up @@ -946,7 +1134,7 @@ mod tests {
}

#[repr(C, align(16))]
pub struct Aligned<const N: usize>(pub [u8; N]);
struct Aligned<const N: usize>(pub [u8; N]);

macro_rules! bytes {
($($byte:literal),* $(,)?) => {
Expand Down

0 comments on commit 4eec0f6

Please sign in to comment.