diff --git a/crates/macros/src/invariant_free.rs b/crates/macros/src/invariant_free.rs new file mode 100644 index 0000000..bf272a9 --- /dev/null +++ b/crates/macros/src/invariant_free.rs @@ -0,0 +1,64 @@ +use quote::quote; +use syn::DeriveInput; + +pub fn expand(input: DeriveInput) -> syn::Result { + let type_name = &input.ident; + let attrs = &input.attrs; + if !input.generics.params.is_empty() { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "InvariantFree on types with generics is not currently supported", + )); + } + let ensure = match &input.data { + syn::Data::Struct(s) => ensure_struct_invariant_free(type_name, s)?, + syn::Data::Enum(e) => ensure_enum_invariant_free(type_name, e, attrs)?, + syn::Data::Union(u) => ensure_union_invariant_free(type_name, u, attrs)?, + }; + + let stream = quote! { + #ensure + unsafe impl ::mem_markers::InvariantFree for #type_name {} + }; + Ok(stream) +} + +fn ensure_struct_invariant_free( + type_name: &syn::Ident, + s: &syn::DataStruct, +) -> syn::Result { + let field_types = crate::utils::struct_field_types(s); + if field_types.is_empty() { + return Ok(quote! {}); + } + + let stream = crate::utils::ensure_field_types( + field_types, + type_name, + "e::format_ident!("InvariantFree"), + None, + ); + Ok(stream) +} + +fn ensure_enum_invariant_free( + _type_name: &syn::Ident, + _e: &syn::DataEnum, + _attrs: &Vec, +) -> syn::Result { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "InvariantFree on enums is not currently supported", + )); +} + +fn ensure_union_invariant_free( + _type_name: &syn::Ident, + _u: &syn::DataUnion, + _attrs: &Vec, +) -> syn::Result { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "InvariantFree on unions is not currently supported", + )); +} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index ac70ddf..176a704 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -3,29 +3,25 @@ extern crate proc_macro; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -mod as_bytes; -mod byte_complete; -mod fixed_layout; -mod from_bytes; -mod no_uninit; mod utils; -mod zeroable; macro_rules! define_derive { - ($item:meta => $fun:ident => $expand:path) => { + ($item:meta => $mod:ident) => { + mod $mod; #[proc_macro_derive($item)] - pub fn $fun(item: TokenStream) -> TokenStream { + pub fn $mod(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); - $expand(input) + $mod::expand(input) .unwrap_or_else(|err| err.to_compile_error()) .into() } }; } -define_derive!(FixedLayout => fixed_layout => fixed_layout::expand); -define_derive!(ByteComplete => byte_complete => byte_complete::expand); -define_derive!(FromBytes => from_bytes => from_bytes::expand); -define_derive!(NoUninit => no_uninit => no_uninit::expand); -define_derive!(AsBytes => as_bytes => as_bytes::expand); -define_derive!(Zeroable => zeroable => zeroable::expand); +define_derive!(FixedLayout => fixed_layout); +define_derive!(ByteComplete => byte_complete); +define_derive!(FromBytes => from_bytes); +define_derive!(NoUninit => no_uninit); +define_derive!(AsBytes => as_bytes); +define_derive!(Zeroable => zeroable); +define_derive!(InvariantFree => invariant_free); diff --git a/crates/macros/tests/fail/from-bytes/not_fixed_layout.rs b/crates/macros/tests/fail/from-bytes/not_fixed_layout.rs index 3810238..10c1308 100644 --- a/crates/macros/tests/fail/from-bytes/not_fixed_layout.rs +++ b/crates/macros/tests/fail/from-bytes/not_fixed_layout.rs @@ -1,6 +1,6 @@ use mem_markers::*; -#[derive(FromBytes, ByteComplete, Zeroable)] +#[derive(FromBytes, ByteComplete, InvariantFree, Zeroable)] #[repr(C)] struct Foo { a: u8, diff --git a/crates/macros/tests/fail/from-bytes/not_fixed_layout.stderr b/crates/macros/tests/fail/from-bytes/not_fixed_layout.stderr index 3d3018f..e27fc67 100644 --- a/crates/macros/tests/fail/from-bytes/not_fixed_layout.stderr +++ b/crates/macros/tests/fail/from-bytes/not_fixed_layout.stderr @@ -1,7 +1,7 @@ error[E0277]: the trait bound `Foo: mem_markers::fixed_layout::FixedLayout` is not satisfied --> $DIR/not_fixed_layout.rs:3:10 | -3 | #[derive(FromBytes, ByteComplete, Zeroable)] +3 | #[derive(FromBytes, ByteComplete, InvariantFree, Zeroable)] | ^^^^^^^^^ the trait `mem_markers::fixed_layout::FixedLayout` is not implemented for `Foo` | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/macros/tests/pass/as-bytes/struct.rs b/crates/macros/tests/pass/as-bytes/struct.rs index 750a713..ebbdff4 100644 --- a/crates/macros/tests/pass/as-bytes/struct.rs +++ b/crates/macros/tests/pass/as-bytes/struct.rs @@ -1,6 +1,6 @@ -use mem_markers::{AsBytes, FixedLayout, NoUninit}; +use mem_markers::{AsBytes, FixedLayout, InvariantFree, NoUninit}; -#[derive(AsBytes, FixedLayout, NoUninit)] +#[derive(AsBytes, FixedLayout, InvariantFree, NoUninit)] #[repr(C)] struct Foo { a: u8, diff --git a/crates/macros/tests/pass/from_bytes/struct.rs b/crates/macros/tests/pass/from_bytes/struct.rs index 4792b28..f0d0cd6 100644 --- a/crates/macros/tests/pass/from_bytes/struct.rs +++ b/crates/macros/tests/pass/from_bytes/struct.rs @@ -1,6 +1,6 @@ use mem_markers::*; -#[derive(FromBytes, FixedLayout, ByteComplete, Zeroable)] +#[derive(FromBytes, FixedLayout, InvariantFree, ByteComplete, Zeroable)] #[repr(C)] struct Foo { a: u8, diff --git a/src/as_bytes.rs b/src/as_bytes.rs index 96b8d42..1461b38 100644 --- a/src/as_bytes.rs +++ b/src/as_bytes.rs @@ -1,7 +1,15 @@ /// A type which can be legally converted to raw bytes. /// -/// It is necessary for this type have a fixed layout and not -/// have any uninitialized bytes hence it requires those two traits +/// Types that implement `AsBytes` require the following traits to also +/// be implemented: +/// * [`NoUninit`]: It is undefined behavior to view uninitialized memory so `AsByte` types +/// must not contain any uninitialized memory. +/// * [`FixedLayout`]: In order to be sure that a type does not have uninitialized memory, +/// it must have a fixed layout otherwise it is possible that the compiler rearranges the type +/// to have padding (and thus uninitialized memory). +/// +/// [`FixedLayout`]: trait.FixedLayout.html +/// [`NoUninit`]: trait.InvariantFree.html pub unsafe trait AsBytes: crate::NoUninit + crate::FixedLayout {} macro_rules! as_bytes_impl { diff --git a/src/byte_complete.rs b/src/byte_complete.rs index dbd1c36..6e38cfd 100644 --- a/src/byte_complete.rs +++ b/src/byte_complete.rs @@ -9,7 +9,7 @@ use crate::Zeroable; /// `ByteComplete` is a more general version of `Zeroable` which has the same constraints but only for /// zero bytes. /// -/// `ByteComplete` vs `FromBytes` +/// # `ByteComplete` vs `FromBytes` /// `ByteComplete` types are not guranteed to have fixed layouts so creating types from bytes /// may not be predictable though it will always yield a valid value. If you want predictability, /// use `FromBytes`. diff --git a/src/from_bytes.rs b/src/from_bytes.rs index b743c4f..567de72 100644 --- a/src/from_bytes.rs +++ b/src/from_bytes.rs @@ -1,11 +1,17 @@ -use crate::{ByteComplete, FixedLayout}; - -/// A type which can be legally instantiated from raw bytes. +/// A type which can be safely _and_ correctly instantiated from raw +/// bytes _and_ then safely _and_ correctly used. /// -/// It is necessary for this type have a fixed layout and be +/// Types that implement `FromBytes` require the following traits to also +/// be implemented: +/// * [`FixedLayout`]: in order to get reliable results, the type +/// must have a well known layout. +/// * [`ByteComplete`]: in order to be able to turn _any_ properly sized +/// aligned byte array into the type, the type must be byte complete. /// byte complete hence it requires the type implements -/// [`ByteComplete`] and [`FixedLayout`]. See those types -/// documentation for information on the invariants those +/// * [`InvariantFree`]: The type must not rely on any invariants to be confirmed +/// before the type can be used safely _and_ correctly. +/// +/// See those types documentation for information on the invariants those /// traits represent. /// /// [`FromBytes`] is the logical opposite as [`AsBytes`]. @@ -14,7 +20,11 @@ use crate::{ByteComplete, FixedLayout}; /// [`AsBytes`]: trait.AsBytes.html /// [`ByteComplete`]: trait.ByteComplete.html /// [`FixedLayout`]: trait.FixedLayout.html -pub unsafe trait FromBytes: ByteComplete + FixedLayout {} +/// [`InvariantFree`]: trait.InvariantFree.html +pub unsafe trait FromBytes: + crate::InvariantFree + crate::ByteComplete + crate::FixedLayout +{ +} macro_rules! from_bytes_impl { ($($type:ty),*) => { @@ -23,6 +33,7 @@ macro_rules! from_bytes_impl { } from_bytes_impl!( + (), u8, u16, u32, diff --git a/src/invariant_free.rs b/src/invariant_free.rs new file mode 100644 index 0000000..b8b131c --- /dev/null +++ b/src/invariant_free.rs @@ -0,0 +1,91 @@ +/// A type does not have any internal invariants that must be held in order for the +/// type to be used safely _and_ correctly. +/// +/// Such data is often known as "plain ol' data" (a.k.a. POD) types. +/// +/// # Examples +/// +/// For example, `u8`s are `InvariantFree` since they are valid no matter what value they contain. +/// Conversely, `NonZeroU8` is not `InvariantFree` since it requires that it cannot be made zero. +/// +/// It is important to note that this trait assumes that types keep no invariants even if violating +/// those invariants is memory safe. For example, the following type is _not_ `InvariantFree`: +/// +/// ```rust +/// // A `u8` that is never `5` +/// struct NotFive(u8); +/// impl NotFive { +/// fn inc(&mut self) { +/// if self.0 == 4 { +/// self.0 = 6; +/// } else { +/// self.0 = self.0.wrapping_add(1); +/// } +/// } +/// } +/// ``` +pub unsafe trait InvariantFree {} + +macro_rules! invariant_free_impl { + ($($type:ty),*) => { + $(unsafe impl InvariantFree for $type {})* + }; +} + +invariant_free_impl!( + (), + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128, + usize, + isize, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + f32, + f64 +); + +unsafe impl InvariantFree for *const T {} +unsafe impl InvariantFree for *mut T {} +unsafe impl InvariantFree for Option> {} + +unsafe impl InvariantFree for [T; 0] {} +unsafe impl InvariantFree for [T; 1] {} +unsafe impl InvariantFree for [T; 2] {} +unsafe impl InvariantFree for [T; 3] {} +unsafe impl InvariantFree for [T; 4] {} +unsafe impl InvariantFree for [T; 5] {} +unsafe impl InvariantFree for [T; 6] {} +unsafe impl InvariantFree for [T; 7] {} +unsafe impl InvariantFree for [T; 8] {} +unsafe impl InvariantFree for [T; 9] {} +unsafe impl InvariantFree for [T; 10] {} +unsafe impl InvariantFree for [T; 11] {} +unsafe impl InvariantFree for [T; 12] {} +unsafe impl InvariantFree for [T; 13] {} +unsafe impl InvariantFree for [T; 14] {} +unsafe impl InvariantFree for [T; 15] {} +unsafe impl InvariantFree for [T; 16] {} +unsafe impl InvariantFree for [T; 17] {} +unsafe impl InvariantFree for [T; 18] {} +unsafe impl InvariantFree for [T; 19] {} +unsafe impl InvariantFree for [T; 20] {} +unsafe impl InvariantFree for [T; 21] {} +unsafe impl InvariantFree for [T; 22] {} +unsafe impl InvariantFree for [T; 23] {} +unsafe impl InvariantFree for [T; 24] {} diff --git a/src/lib.rs b/src/lib.rs index 602f68a..f62c4d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod as_bytes; mod byte_complete; mod fixed_layout; mod from_bytes; +mod invariant_free; mod no_uninit; mod zeroable; @@ -19,6 +20,8 @@ pub use fixed_layout::FixedLayout; #[doc(inline)] pub use from_bytes::FromBytes; #[doc(inline)] +pub use invariant_free::InvariantFree; +#[doc(inline)] pub use no_uninit::NoUninit; #[doc(inline)] pub use zeroable::Zeroable;