Revision control
Copy as Markdown
Other Tools
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::utils::{self, FieldInfo};
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Error};
pub fn derive_impl(input: &DeriveInput) -> TokenStream2 {
if !utils::ReprInfo::compute(&input.attrs).cpacked_or_transparent() {
return Error::new(
input.span(),
"derive(ULE) must be applied to a #[repr(C, packed)] or #[repr(transparent)] type",
)
.to_compile_error();
}
if input.generics.type_params().next().is_some()
|| input.generics.lifetimes().next().is_some()
|| input.generics.const_params().next().is_some()
{
return Error::new(
input.generics.span(),
"derive(ULE) must be applied to a struct without any generics",
)
.to_compile_error();
}
let struc = if let Data::Struct(ref s) = input.data {
if s.fields.iter().next().is_none() {
return Error::new(
input.span(),
"derive(ULE) must be applied to a non-empty struct",
)
.to_compile_error();
}
s
} else {
return Error::new(input.span(), "derive(ULE) must be applied to a struct")
.to_compile_error();
};
let fields = FieldInfo::make_list(struc.fields.iter());
let (validators, remaining_offset) = generate_ule_validators(&fields);
let name = &input.ident;
// Safety (based on the safety checklist on the ULE trait):
// 1. #name does not include any uninitialized or padding bytes.
// (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types)
// 2. #name is aligned to 1 byte.
// (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types)
// 3. The impl of validate_byte_slice() returns an error if any byte is not valid.
// 4. The impl of validate_byte_slice() returns an error if there are extra bytes.
// 5. The other ULE methods use the default impl.
// 6. [This impl does not enforce the non-safety equality constraint, it is up to the user to do so, ideally via a custom derive]
quote! {
unsafe impl zerovec::ule::ULE for #name {
#[inline]
fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> {
const SIZE: usize = ::core::mem::size_of::<#name>();
#[allow(clippy::modulo_one)]
if bytes.len() % SIZE != 0 {
return Err(zerovec::ZeroVecError::length::<Self>(bytes.len()));
}
// Validate the bytes
#[allow(clippy::indexing_slicing)] // We're slicing a chunk of known size
for chunk in bytes.chunks_exact(SIZE) {
#validators
debug_assert_eq!(#remaining_offset, SIZE);
}
Ok(())
}
}
}
}
/// Given an slice over ULE struct fields, returns code validating that a slice variable `bytes` contains valid instances of those ULE types
/// in order, plus the byte offset of any remaining unvalidated bytes. ULE types should not have any remaining bytes, but VarULE types will since
/// the last field is the unsized one.
pub(crate) fn generate_ule_validators(
fields: &[FieldInfo],
// (validators, remaining_offset)
) -> (TokenStream2, syn::Ident) {
utils::generate_per_field_offsets(fields, false, |field, prev_offset_ident, size_ident| {
let ty = &field.field.ty;
quote! {
#[allow(clippy::indexing_slicing)] // generate_per_field_offsets produces valid indices
<#ty as zerovec::ule::ULE>::validate_byte_slice(&bytes[#prev_offset_ident .. #prev_offset_ident + #size_ident])?;
}
})
}
/// Make corresponding ULE fields for each field
pub(crate) fn make_ule_fields(fields: &[FieldInfo]) -> Vec<TokenStream2> {
fields
.iter()
.map(|f| {
let ty = &f.field.ty;
let ty = quote!(<#ty as zerovec::ule::AsULE>::ULE);
let setter = f.setter();
let vis = &f.field.vis;
quote!(#vis #setter #ty)
})
.collect::<Vec<_>>()
}