1 | // This file is part of ICU4X. For terms of use, please see the file |
2 | // called LICENSE at the top level of the ICU4X source tree |
3 | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
4 | |
5 | use crate::utils::{self, FieldInfo}; |
6 | use proc_macro2::Span; |
7 | use proc_macro2::TokenStream as TokenStream2; |
8 | use quote::quote; |
9 | use syn::spanned::Spanned; |
10 | use syn::{Data, DeriveInput, Error, Ident}; |
11 | |
12 | /// Implementation for derive(VarULE). `custom_varule_validator` validates the last field bytes `last_field_bytes` |
13 | /// if specified, if not, the VarULE implementation will be used. |
14 | pub fn derive_impl( |
15 | input: &DeriveInput, |
16 | custom_varule_validator: Option<TokenStream2>, |
17 | ) -> TokenStream2 { |
18 | if !utils::has_valid_repr(&input.attrs, |r| r == "packed" || r == "transparent" ) { |
19 | return Error::new( |
20 | input.span(), |
21 | "derive(VarULE) must be applied to a #[repr(packed)] or #[repr(transparent)] type" , |
22 | ) |
23 | .to_compile_error(); |
24 | } |
25 | if input.generics.type_params().next().is_some() |
26 | || input.generics.lifetimes().next().is_some() |
27 | || input.generics.const_params().next().is_some() |
28 | { |
29 | return Error::new( |
30 | input.generics.span(), |
31 | "derive(VarULE) must be applied to a struct without any generics" , |
32 | ) |
33 | .to_compile_error(); |
34 | } |
35 | let struc = if let Data::Struct(ref s) = input.data { |
36 | if s.fields.iter().next().is_none() { |
37 | return Error::new( |
38 | input.span(), |
39 | "derive(VarULE) must be applied to a non-empty struct" , |
40 | ) |
41 | .to_compile_error(); |
42 | } |
43 | s |
44 | } else { |
45 | return Error::new(input.span(), "derive(VarULE) must be applied to a struct" ) |
46 | .to_compile_error(); |
47 | }; |
48 | |
49 | let n_fields = struc.fields.len(); |
50 | |
51 | let ule_fields = FieldInfo::make_list(struc.fields.iter().take(n_fields - 1)); |
52 | |
53 | let sizes = ule_fields.iter().map(|f| { |
54 | let ty = &f.field.ty; |
55 | quote!(::core::mem::size_of::<#ty>()) |
56 | }); |
57 | let (validators, remaining_offset) = if n_fields > 1 { |
58 | // generate ULE validators |
59 | crate::ule::generate_ule_validators(&ule_fields) |
60 | } else { |
61 | // no ULE subfields |
62 | ( |
63 | quote!( |
64 | const ZERO: usize = 0; |
65 | ), |
66 | Ident::new("ZERO" , Span::call_site()), |
67 | ) |
68 | }; |
69 | |
70 | let unsized_field = &struc |
71 | .fields |
72 | .iter() |
73 | .next_back() |
74 | .expect("Already verified that struct is not empty" ) |
75 | .ty; |
76 | |
77 | let name = &input.ident; |
78 | let ule_size = Ident::new( |
79 | &format!("__IMPL_VarULE_FOR_ {name}_ULE_SIZE" ), |
80 | Span::call_site(), |
81 | ); |
82 | |
83 | let last_field_validator = if let Some(custom_varule_validator) = custom_varule_validator { |
84 | custom_varule_validator |
85 | } else { |
86 | quote!(<#unsized_field as zerovec::ule::VarULE>::validate_byte_slice(last_field_bytes)?;) |
87 | }; |
88 | |
89 | // Safety (based on the safety checklist on the ULE trait): |
90 | // 1. #name does not include any uninitialized or padding bytes |
91 | // (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types) |
92 | // 2. #name is aligned to 1 byte. |
93 | // (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types) |
94 | // 3. The impl of `validate_byte_slice()` returns an error if any byte is not valid. |
95 | // 4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety |
96 | // 5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data. |
97 | // 6. The other VarULE methods use the default impl |
98 | // 7. [This impl does not enforce the non-safety equality constraint, it is up to the user to do so, ideally via a custom derive] |
99 | quote! { |
100 | // The size of the ULE section of this type |
101 | const #ule_size: usize = 0 #(+ #sizes)*; |
102 | unsafe impl zerovec::ule::VarULE for #name { |
103 | #[inline] |
104 | fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> { |
105 | |
106 | if bytes.len() < #ule_size { |
107 | return Err(zerovec::ZeroVecError::parse::<Self>()); |
108 | } |
109 | #validators |
110 | debug_assert_eq!(#remaining_offset, #ule_size); |
111 | #[allow(clippy::indexing_slicing)] // TODO explain |
112 | let last_field_bytes = &bytes[#remaining_offset..]; |
113 | #last_field_validator |
114 | Ok(()) |
115 | } |
116 | #[inline] |
117 | unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self { |
118 | // just the unsized part |
119 | #[allow(clippy::indexing_slicing)] // TODO explain |
120 | let unsized_bytes = &bytes[#ule_size..]; |
121 | let unsized_ref = <#unsized_field as zerovec::ule::VarULE>::from_byte_slice_unchecked(unsized_bytes); |
122 | // We should use the pointer metadata APIs here when they are stable: https://github.com/rust-lang/rust/issues/81513 |
123 | // For now we rely on all DST metadata being a usize to extract it via a fake slice pointer |
124 | let (_ptr, metadata): (usize, usize) = ::core::mem::transmute(unsized_ref); |
125 | let entire_struct_as_slice: *const [u8] = ::core::slice::from_raw_parts(bytes.as_ptr(), metadata); |
126 | &*(entire_struct_as_slice as *const Self) |
127 | } |
128 | } |
129 | } |
130 | } |
131 | |