1 | // Copyright 2019 The Fuchsia Authors |
2 | // |
3 | // Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 |
4 | // <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT |
5 | // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. |
6 | // This file may not be copied, modified, or distributed except according to |
7 | // those terms. |
8 | |
9 | //! Derive macros for [zerocopy]'s traits. |
10 | //! |
11 | //! [zerocopy]: https://docs.rs/zerocopy |
12 | |
13 | // Sometimes we want to use lints which were added after our MSRV. |
14 | // `unknown_lints` is `warn` by default and we deny warnings in CI, so without |
15 | // this attribute, any unknown lint would cause a CI failure when testing with |
16 | // our MSRV. |
17 | #![allow (unknown_lints)] |
18 | #![deny (renamed_and_removed_lints)] |
19 | #![deny (clippy::all, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)] |
20 | #![deny ( |
21 | rustdoc::bare_urls, |
22 | rustdoc::broken_intra_doc_links, |
23 | rustdoc::invalid_codeblock_attributes, |
24 | rustdoc::invalid_html_tags, |
25 | rustdoc::invalid_rust_codeblocks, |
26 | rustdoc::missing_crate_level_docs, |
27 | rustdoc::private_intra_doc_links |
28 | )] |
29 | #![recursion_limit = "128" ] |
30 | |
31 | mod ext; |
32 | mod repr; |
33 | |
34 | use { |
35 | proc_macro2::Span, |
36 | quote::quote, |
37 | syn::{ |
38 | parse_quote, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr, ExprLit, |
39 | GenericParam, Ident, Lit, |
40 | }, |
41 | }; |
42 | |
43 | use {crate::ext::*, crate::repr::*}; |
44 | |
45 | // Unwraps a `Result<_, Vec<Error>>`, converting any `Err` value into a |
46 | // `TokenStream` and returning it. |
47 | macro_rules! try_or_print { |
48 | ($e:expr) => { |
49 | match $e { |
50 | Ok(x) => x, |
51 | Err(errors) => return print_all_errors(errors).into(), |
52 | } |
53 | }; |
54 | } |
55 | |
56 | // TODO(https://github.com/rust-lang/rust/issues/54140): Some errors could be |
57 | // made better if we could add multiple lines of error output like this: |
58 | // |
59 | // error: unsupported representation |
60 | // --> enum.rs:28:8 |
61 | // | |
62 | // 28 | #[repr(transparent)] |
63 | // | |
64 | // help: required by the derive of FromBytes |
65 | // |
66 | // Instead, we have more verbose error messages like "unsupported representation |
67 | // for deriving FromZeroes, FromBytes, AsBytes, or Unaligned on an enum" |
68 | // |
69 | // This will probably require Span::error |
70 | // (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error), |
71 | // which is currently unstable. Revisit this once it's stable. |
72 | |
73 | #[proc_macro_derive (KnownLayout)] |
74 | pub fn derive_known_layout(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { |
75 | let ast = syn::parse_macro_input!(ts as DeriveInput); |
76 | |
77 | let is_repr_c_struct = match &ast.data { |
78 | Data::Struct(..) => { |
79 | let reprs = try_or_print!(repr::reprs::<Repr>(&ast.attrs)); |
80 | if reprs.iter().any(|(_meta, repr)| repr == &Repr::C) { |
81 | Some(reprs) |
82 | } else { |
83 | None |
84 | } |
85 | } |
86 | Data::Enum(..) | Data::Union(..) => None, |
87 | }; |
88 | |
89 | let fields = ast.data.field_types(); |
90 | |
91 | let (require_self_sized, extras) = if let ( |
92 | Some(reprs), |
93 | Some((trailing_field, leading_fields)), |
94 | ) = (is_repr_c_struct, fields.split_last()) |
95 | { |
96 | let repr_align = reprs |
97 | .iter() |
98 | .find_map( |
99 | |(_meta, repr)| { |
100 | if let Repr::Align(repr_align) = repr { |
101 | Some(repr_align) |
102 | } else { |
103 | None |
104 | } |
105 | }, |
106 | ) |
107 | .map(|repr_align| quote!(NonZeroUsize::new(#repr_align as usize))) |
108 | .unwrap_or(quote!(None)); |
109 | |
110 | let repr_packed = reprs |
111 | .iter() |
112 | .find_map(|(_meta, repr)| match repr { |
113 | Repr::Packed => Some(1), |
114 | Repr::PackedN(repr_packed) => Some(*repr_packed), |
115 | _ => None, |
116 | }) |
117 | .map(|repr_packed| quote!(NonZeroUsize::new(#repr_packed as usize))) |
118 | .unwrap_or(quote!(None)); |
119 | |
120 | ( |
121 | false, |
122 | quote!( |
123 | // SAFETY: `LAYOUT` accurately describes the layout of `Self`. |
124 | // The layout of `Self` is reflected using a sequence of |
125 | // invocations of `DstLayout::{new_zst,extend,pad_to_align}`. |
126 | // The documentation of these items vows that invocations in |
127 | // this manner will acurately describe a type, so long as: |
128 | // |
129 | // - that type is `repr(C)`, |
130 | // - its fields are enumerated in the order they appear, |
131 | // - the presence of `repr_align` and `repr_packed` are correctly accounted for. |
132 | // |
133 | // We respect all three of these preconditions here. This |
134 | // expansion is only used if `is_repr_c_struct`, we enumerate |
135 | // the fields in order, and we extract the values of `align(N)` |
136 | // and `packed(N)`. |
137 | const LAYOUT: ::zerocopy::DstLayout = { |
138 | use ::zerocopy::macro_util::core_reexport::num::NonZeroUsize; |
139 | use ::zerocopy::{DstLayout, KnownLayout}; |
140 | |
141 | let repr_align = #repr_align; |
142 | let repr_packed = #repr_packed; |
143 | |
144 | DstLayout::new_zst(repr_align) |
145 | #(.extend(DstLayout::for_type::<#leading_fields>(), repr_packed))* |
146 | .extend(<#trailing_field as KnownLayout>::LAYOUT, repr_packed) |
147 | .pad_to_align() |
148 | }; |
149 | |
150 | // SAFETY: |
151 | // - The recursive call to `raw_from_ptr_len` preserves both address and provenance. |
152 | // - The `as` cast preserves both address and provenance. |
153 | // - `NonNull::new_unchecked` preserves both address and provenance. |
154 | #[inline(always)] |
155 | fn raw_from_ptr_len( |
156 | bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>, |
157 | elems: usize, |
158 | ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> { |
159 | use ::zerocopy::{KnownLayout}; |
160 | let trailing = <#trailing_field as KnownLayout>::raw_from_ptr_len(bytes, elems); |
161 | let slf = trailing.as_ptr() as *mut Self; |
162 | // SAFETY: Constructed from `trailing`, which is non-null. |
163 | unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) } |
164 | } |
165 | ), |
166 | ) |
167 | } else { |
168 | // For enums, unions, and non-`repr(C)` structs, we require that |
169 | // `Self` is sized, and as a result don't need to reason about the |
170 | // internals of the type. |
171 | ( |
172 | true, |
173 | quote!( |
174 | // SAFETY: `LAYOUT` is guaranteed to accurately describe the |
175 | // layout of `Self`, because that is the documented safety |
176 | // contract of `DstLayout::for_type`. |
177 | const LAYOUT: ::zerocopy::DstLayout = ::zerocopy::DstLayout::for_type::<Self>(); |
178 | |
179 | // SAFETY: `.cast` preserves address and provenance. |
180 | // |
181 | // TODO(#429): Add documentation to `.cast` that promises that |
182 | // it preserves provenance. |
183 | #[inline(always)] |
184 | fn raw_from_ptr_len( |
185 | bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull<u8>, |
186 | _elems: usize, |
187 | ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> { |
188 | bytes.cast::<Self>() |
189 | } |
190 | ), |
191 | ) |
192 | }; |
193 | |
194 | match &ast.data { |
195 | Data::Struct(strct) => { |
196 | let require_trait_bound_on_field_types = if require_self_sized { |
197 | RequireBoundedFields::No |
198 | } else { |
199 | RequireBoundedFields::Trailing |
200 | }; |
201 | |
202 | // A bound on the trailing field is required, since structs are |
203 | // unsized if their trailing field is unsized. Reflecting the layout |
204 | // of an usized trailing field requires that the field is |
205 | // `KnownLayout`. |
206 | impl_block( |
207 | &ast, |
208 | strct, |
209 | Trait::KnownLayout, |
210 | require_trait_bound_on_field_types, |
211 | require_self_sized, |
212 | None, |
213 | Some(extras), |
214 | ) |
215 | } |
216 | Data::Enum(enm) => { |
217 | // A bound on the trailing field is not required, since enums cannot |
218 | // currently be unsized. |
219 | impl_block( |
220 | &ast, |
221 | enm, |
222 | Trait::KnownLayout, |
223 | RequireBoundedFields::No, |
224 | true, |
225 | None, |
226 | Some(extras), |
227 | ) |
228 | } |
229 | Data::Union(unn) => { |
230 | // A bound on the trailing field is not required, since unions |
231 | // cannot currently be unsized. |
232 | impl_block( |
233 | &ast, |
234 | unn, |
235 | Trait::KnownLayout, |
236 | RequireBoundedFields::No, |
237 | true, |
238 | None, |
239 | Some(extras), |
240 | ) |
241 | } |
242 | } |
243 | .into() |
244 | } |
245 | |
246 | #[proc_macro_derive (FromZeroes)] |
247 | pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { |
248 | let ast: DeriveInput = syn::parse_macro_input!(ts as DeriveInput); |
249 | matchTokenStream &ast.data { |
250 | Data::Struct(strct: &DataStruct) => derive_from_zeroes_struct(&ast, strct), |
251 | Data::Enum(enm: &DataEnum) => derive_from_zeroes_enum(&ast, enm), |
252 | Data::Union(unn: &DataUnion) => derive_from_zeroes_union(&ast, unn), |
253 | } |
254 | .into() |
255 | } |
256 | |
257 | #[proc_macro_derive (FromBytes)] |
258 | pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { |
259 | let ast: DeriveInput = syn::parse_macro_input!(ts as DeriveInput); |
260 | matchTokenStream &ast.data { |
261 | Data::Struct(strct: &DataStruct) => derive_from_bytes_struct(&ast, strct), |
262 | Data::Enum(enm: &DataEnum) => derive_from_bytes_enum(&ast, enm), |
263 | Data::Union(unn: &DataUnion) => derive_from_bytes_union(&ast, unn), |
264 | } |
265 | .into() |
266 | } |
267 | |
268 | #[proc_macro_derive (AsBytes)] |
269 | pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { |
270 | let ast: DeriveInput = syn::parse_macro_input!(ts as DeriveInput); |
271 | matchTokenStream &ast.data { |
272 | Data::Struct(strct: &DataStruct) => derive_as_bytes_struct(&ast, strct), |
273 | Data::Enum(enm: &DataEnum) => derive_as_bytes_enum(&ast, enm), |
274 | Data::Union(unn: &DataUnion) => derive_as_bytes_union(&ast, unn), |
275 | } |
276 | .into() |
277 | } |
278 | |
279 | #[proc_macro_derive (Unaligned)] |
280 | pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { |
281 | let ast: DeriveInput = syn::parse_macro_input!(ts as DeriveInput); |
282 | matchTokenStream &ast.data { |
283 | Data::Struct(strct: &DataStruct) => derive_unaligned_struct(&ast, strct), |
284 | Data::Enum(enm: &DataEnum) => derive_unaligned_enum(&ast, enm), |
285 | Data::Union(unn: &DataUnion) => derive_unaligned_union(&ast, unn), |
286 | } |
287 | .into() |
288 | } |
289 | |
290 | const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ |
291 | &[StructRepr::C], |
292 | &[StructRepr::Transparent], |
293 | &[StructRepr::Packed], |
294 | &[StructRepr::C, StructRepr::Packed], |
295 | ]; |
296 | |
297 | // A struct is `FromZeroes` if: |
298 | // - all fields are `FromZeroes` |
299 | |
300 | fn derive_from_zeroes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { |
301 | impl_block(input:ast, data:strct, trt:Trait::FromZeroes, require_trait_bound_on_field_types:RequireBoundedFields::Yes, require_self_sized:false, padding_check:None, extras:None) |
302 | } |
303 | |
304 | // An enum is `FromZeroes` if: |
305 | // - all of its variants are fieldless |
306 | // - one of the variants has a discriminant of `0` |
307 | |
308 | fn derive_from_zeroes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { |
309 | if !enm.is_c_like() { |
310 | return Error::new_spanned(ast, "only C-like enums can implement FromZeroes" ) |
311 | .to_compile_error(); |
312 | } |
313 | |
314 | let has_explicit_zero_discriminant = |
315 | enm.variants.iter().filter_map(|v| v.discriminant.as_ref()).any(|(_, e)| { |
316 | if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = e { |
317 | i.base10_parse::<usize>().ok() == Some(0) |
318 | } else { |
319 | false |
320 | } |
321 | }); |
322 | // If the first variant of an enum does not specify its discriminant, it is set to zero: |
323 | // https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations |
324 | let has_implicit_zero_discriminant = |
325 | enm.variants.iter().next().map(|v| v.discriminant.is_none()) == Some(true); |
326 | |
327 | if !has_explicit_zero_discriminant && !has_implicit_zero_discriminant { |
328 | return Error::new_spanned( |
329 | ast, |
330 | "FromZeroes only supported on enums with a variant that has a discriminant of `0`" , |
331 | ) |
332 | .to_compile_error(); |
333 | } |
334 | |
335 | impl_block(ast, enm, Trait::FromZeroes, RequireBoundedFields::Yes, false, None, None) |
336 | } |
337 | |
338 | // Like structs, unions are `FromZeroes` if |
339 | // - all fields are `FromZeroes` |
340 | |
341 | fn derive_from_zeroes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { |
342 | impl_block(input:ast, data:unn, trt:Trait::FromZeroes, require_trait_bound_on_field_types:RequireBoundedFields::Yes, require_self_sized:false, padding_check:None, extras:None) |
343 | } |
344 | |
345 | // A struct is `FromBytes` if: |
346 | // - all fields are `FromBytes` |
347 | |
348 | fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { |
349 | impl_block(input:ast, data:strct, trt:Trait::FromBytes, require_trait_bound_on_field_types:RequireBoundedFields::Yes, require_self_sized:false, padding_check:None, extras:None) |
350 | } |
351 | |
352 | // An enum is `FromBytes` if: |
353 | // - Every possible bit pattern must be valid, which means that every bit |
354 | // pattern must correspond to a different enum variant. Thus, for an enum |
355 | // whose layout takes up N bytes, there must be 2^N variants. |
356 | // - Since we must know N, only representations which guarantee the layout's |
357 | // size are allowed. These are `repr(uN)` and `repr(iN)` (`repr(C)` implies an |
358 | // implementation-defined size). `usize` and `isize` technically guarantee the |
359 | // layout's size, but would require us to know how large those are on the |
360 | // target platform. This isn't terribly difficult - we could emit a const |
361 | // expression that could call `core::mem::size_of` in order to determine the |
362 | // size and check against the number of enum variants, but a) this would be |
363 | // platform-specific and, b) even on Rust's smallest bit width platform (32), |
364 | // this would require ~4 billion enum variants, which obviously isn't a thing. |
365 | |
366 | fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { |
367 | if !enm.is_c_like() { |
368 | return Error::new_spanned(ast, "only C-like enums can implement FromBytes" ) |
369 | .to_compile_error(); |
370 | } |
371 | |
372 | let reprs = try_or_print!(ENUM_FROM_BYTES_CFG.validate_reprs(ast)); |
373 | |
374 | let variants_required = match reprs.as_slice() { |
375 | [EnumRepr::U8] | [EnumRepr::I8] => 1usize << 8, |
376 | [EnumRepr::U16] | [EnumRepr::I16] => 1usize << 16, |
377 | // `validate_reprs` has already validated that it's one of the preceding |
378 | // patterns. |
379 | _ => unreachable!(), |
380 | }; |
381 | if enm.variants.len() != variants_required { |
382 | return Error::new_spanned( |
383 | ast, |
384 | format!( |
385 | "FromBytes only supported on {} enum with {} variants" , |
386 | reprs[0], variants_required |
387 | ), |
388 | ) |
389 | .to_compile_error(); |
390 | } |
391 | |
392 | impl_block(ast, enm, Trait::FromBytes, RequireBoundedFields::Yes, false, None, None) |
393 | } |
394 | |
395 | #[rustfmt::skip] |
396 | const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = { |
397 | use EnumRepr::*; |
398 | Config { |
399 | allowed_combinations_message: r#"FromBytes requires repr of "u8", "u16", "i8", or "i16""# , |
400 | derive_unaligned: false, |
401 | allowed_combinations: &[ |
402 | &[U8], |
403 | &[U16], |
404 | &[I8], |
405 | &[I16], |
406 | ], |
407 | disallowed_but_legal_combinations: &[ |
408 | &[C], |
409 | &[U32], |
410 | &[I32], |
411 | &[U64], |
412 | &[I64], |
413 | &[Usize], |
414 | &[Isize], |
415 | ], |
416 | } |
417 | }; |
418 | |
419 | // Like structs, unions are `FromBytes` if |
420 | // - all fields are `FromBytes` |
421 | |
422 | fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { |
423 | impl_block(input:ast, data:unn, trt:Trait::FromBytes, require_trait_bound_on_field_types:RequireBoundedFields::Yes, require_self_sized:false, padding_check:None, extras:None) |
424 | } |
425 | |
426 | // A struct is `AsBytes` if: |
427 | // - all fields are `AsBytes` |
428 | // - `repr(C)` or `repr(transparent)` and |
429 | // - no padding (size of struct equals sum of size of field types) |
430 | // - `repr(packed)` |
431 | |
432 | fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { |
433 | let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); |
434 | let is_transparent = reprs.contains(&StructRepr::Transparent); |
435 | let is_packed = reprs.contains(&StructRepr::Packed); |
436 | |
437 | // TODO(#10): Support type parameters for non-transparent, non-packed |
438 | // structs. |
439 | if !ast.generics.params.is_empty() && !is_transparent && !is_packed { |
440 | return Error::new( |
441 | Span::call_site(), |
442 | "unsupported on generic structs that are not repr(transparent) or repr(packed)" , |
443 | ) |
444 | .to_compile_error(); |
445 | } |
446 | |
447 | // We don't need a padding check if the struct is repr(transparent) or |
448 | // repr(packed). |
449 | // - repr(transparent): The layout and ABI of the whole struct is the same |
450 | // as its only non-ZST field (meaning there's no padding outside of that |
451 | // field) and we require that field to be `AsBytes` (meaning there's no |
452 | // padding in that field). |
453 | // - repr(packed): Any inter-field padding bytes are removed, meaning that |
454 | // any padding bytes would need to come from the fields, all of which |
455 | // we require to be `AsBytes` (meaning they don't have any padding). |
456 | let padding_check = if is_transparent || is_packed { None } else { Some(PaddingCheck::Struct) }; |
457 | impl_block(ast, strct, Trait::AsBytes, RequireBoundedFields::Yes, false, padding_check, None) |
458 | } |
459 | |
460 | const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config { |
461 | // Since `disallowed_but_legal_combinations` is empty, this message will |
462 | // never actually be emitted. |
463 | allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""# , |
464 | derive_unaligned: false, |
465 | allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, |
466 | disallowed_but_legal_combinations: &[], |
467 | }; |
468 | |
469 | // An enum is `AsBytes` if it is C-like and has a defined repr. |
470 | |
471 | fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { |
472 | if !enm.is_c_like() { |
473 | return ErrorError::new_spanned(tokens:ast, message:"only C-like enums can implement AsBytes" ) |
474 | .to_compile_error(); |
475 | } |
476 | |
477 | // We don't care what the repr is; we only care that it is one of the |
478 | // allowed ones. |
479 | let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast)); |
480 | impl_block(input:ast, data:enm, trt:Trait::AsBytes, require_trait_bound_on_field_types:RequireBoundedFields::No, require_self_sized:false, padding_check:None, extras:None) |
481 | } |
482 | |
483 | #[rustfmt::skip] |
484 | const ENUM_AS_BYTES_CFG: Config<EnumRepr> = { |
485 | use EnumRepr::*; |
486 | Config { |
487 | // Since `disallowed_but_legal_combinations` is empty, this message will |
488 | // never actually be emitted. |
489 | allowed_combinations_message: r#"AsBytes requires repr of "C", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", or "isize""# , |
490 | derive_unaligned: false, |
491 | allowed_combinations: &[ |
492 | &[C], |
493 | &[U8], |
494 | &[U16], |
495 | &[I8], |
496 | &[I16], |
497 | &[U32], |
498 | &[I32], |
499 | &[U64], |
500 | &[I64], |
501 | &[Usize], |
502 | &[Isize], |
503 | ], |
504 | disallowed_but_legal_combinations: &[], |
505 | } |
506 | }; |
507 | |
508 | // A union is `AsBytes` if: |
509 | // - all fields are `AsBytes` |
510 | // - `repr(C)`, `repr(transparent)`, or `repr(packed)` |
511 | // - no padding (size of union equals size of each field type) |
512 | |
513 | fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { |
514 | // TODO(#10): Support type parameters. |
515 | if !ast.generics.params.is_empty() { |
516 | return ErrorError::new(Span::call_site(), message:"unsupported on types with type parameters" ) |
517 | .to_compile_error(); |
518 | } |
519 | |
520 | try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast)); |
521 | |
522 | impl_block( |
523 | input:ast, |
524 | data:unn, |
525 | trt:Trait::AsBytes, |
526 | require_trait_bound_on_field_types:RequireBoundedFields::Yes, |
527 | require_self_sized:false, |
528 | padding_check:Some(PaddingCheck::Union), |
529 | extras:None, |
530 | ) |
531 | } |
532 | |
533 | // A struct is `Unaligned` if: |
534 | // - `repr(align)` is no more than 1 and either |
535 | // - `repr(C)` or `repr(transparent)` and |
536 | // - all fields `Unaligned` |
537 | // - `repr(packed)` |
538 | |
539 | fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { |
540 | let reprs: Vec = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); |
541 | let require_trait_bounds_on_field_types: RequireBoundedFields = (!reprs.contains(&StructRepr::Packed)).into(); |
542 | |
543 | impl_block(input:ast, data:strct, trt:Trait::Unaligned, require_trait_bound_on_field_types:require_trait_bounds_on_field_types, require_self_sized:false, padding_check:None, extras:None) |
544 | } |
545 | |
546 | const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config { |
547 | // Since `disallowed_but_legal_combinations` is empty, this message will |
548 | // never actually be emitted. |
549 | allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""# , |
550 | derive_unaligned: true, |
551 | allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS, |
552 | disallowed_but_legal_combinations: &[], |
553 | }; |
554 | |
555 | // An enum is `Unaligned` if: |
556 | // - No `repr(align(N > 1))` |
557 | // - `repr(u8)` or `repr(i8)` |
558 | |
559 | fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::TokenStream { |
560 | if !enm.is_c_like() { |
561 | return ErrorError::new_spanned(tokens:ast, message:"only C-like enums can implement Unaligned" ) |
562 | .to_compile_error(); |
563 | } |
564 | |
565 | // The only valid reprs are `u8` and `i8`, and optionally `align(1)`. We |
566 | // don't actually care what the reprs are so long as they satisfy that |
567 | // requirement. |
568 | let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_UNALIGNED_CFG.validate_reprs(ast)); |
569 | |
570 | // C-like enums cannot currently have type parameters, so this value of true |
571 | // for `require_trait_bound_on_field_types` doesn't really do anything. But |
572 | // it's marginally more future-proof in case that restriction is lifted in |
573 | // the future. |
574 | impl_block(input:ast, data:enm, trt:Trait::Unaligned, require_trait_bound_on_field_types:RequireBoundedFields::Yes, require_self_sized:false, padding_check:None, extras:None) |
575 | } |
576 | |
577 | #[rustfmt::skip] |
578 | const ENUM_UNALIGNED_CFG: Config<EnumRepr> = { |
579 | use EnumRepr::*; |
580 | Config { |
581 | allowed_combinations_message: |
582 | r#"Unaligned requires repr of "u8" or "i8", and no alignment (i.e., repr(align(N > 1)))"# , |
583 | derive_unaligned: true, |
584 | allowed_combinations: &[ |
585 | &[U8], |
586 | &[I8], |
587 | ], |
588 | disallowed_but_legal_combinations: &[ |
589 | &[C], |
590 | &[U16], |
591 | &[U32], |
592 | &[U64], |
593 | &[Usize], |
594 | &[I16], |
595 | &[I32], |
596 | &[I64], |
597 | &[Isize], |
598 | ], |
599 | } |
600 | }; |
601 | |
602 | // Like structs, a union is `Unaligned` if: |
603 | // - `repr(align)` is no more than 1 and either |
604 | // - `repr(C)` or `repr(transparent)` and |
605 | // - all fields `Unaligned` |
606 | // - `repr(packed)` |
607 | |
608 | fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream { |
609 | let reprs: Vec = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast)); |
610 | let require_trait_bound_on_field_types: RequireBoundedFields = (!reprs.contains(&StructRepr::Packed)).into(); |
611 | |
612 | impl_block(input:ast, data:unn, trt:Trait::Unaligned, require_trait_bound_on_field_types, require_self_sized:false, padding_check:None, extras:None) |
613 | } |
614 | |
615 | // This enum describes what kind of padding check needs to be generated for the |
616 | // associated impl. |
617 | enum PaddingCheck { |
618 | // Check that the sum of the fields' sizes exactly equals the struct's size. |
619 | Struct, |
620 | // Check that the size of each field exactly equals the union's size. |
621 | Union, |
622 | } |
623 | |
624 | impl PaddingCheck { |
625 | /// Returns the ident of the macro to call in order to validate that a type |
626 | /// passes the padding check encoded by `PaddingCheck`. |
627 | fn validator_macro_ident(&self) -> Ident { |
628 | let s: &'static str = match self { |
629 | PaddingCheck::Struct => "struct_has_padding" , |
630 | PaddingCheck::Union => "union_has_padding" , |
631 | }; |
632 | |
633 | Ident::new(string:s, Span::call_site()) |
634 | } |
635 | } |
636 | |
637 | #[derive (Debug, Eq, PartialEq)] |
638 | enum Trait { |
639 | KnownLayout, |
640 | FromZeroes, |
641 | FromBytes, |
642 | AsBytes, |
643 | Unaligned, |
644 | } |
645 | |
646 | impl Trait { |
647 | fn ident(&self) -> Ident { |
648 | Ident::new(string:format!(" {:?}" , self).as_str(), Span::call_site()) |
649 | } |
650 | } |
651 | |
652 | #[derive (Debug, Eq, PartialEq)] |
653 | enum RequireBoundedFields { |
654 | No, |
655 | Yes, |
656 | Trailing, |
657 | } |
658 | |
659 | impl From<bool> for RequireBoundedFields { |
660 | fn from(do_require: bool) -> Self { |
661 | match do_require { |
662 | true => Self::Yes, |
663 | false => Self::No, |
664 | } |
665 | } |
666 | } |
667 | |
668 | fn impl_block<D: DataExt>( |
669 | input: &DeriveInput, |
670 | data: &D, |
671 | trt: Trait, |
672 | require_trait_bound_on_field_types: RequireBoundedFields, |
673 | require_self_sized: bool, |
674 | padding_check: Option<PaddingCheck>, |
675 | extras: Option<proc_macro2::TokenStream>, |
676 | ) -> proc_macro2::TokenStream { |
677 | // In this documentation, we will refer to this hypothetical struct: |
678 | // |
679 | // #[derive(FromBytes)] |
680 | // struct Foo<T, I: Iterator> |
681 | // where |
682 | // T: Copy, |
683 | // I: Clone, |
684 | // I::Item: Clone, |
685 | // { |
686 | // a: u8, |
687 | // b: T, |
688 | // c: I::Item, |
689 | // } |
690 | // |
691 | // We extract the field types, which in this case are `u8`, `T`, and |
692 | // `I::Item`. We re-use the existing parameters and where clauses. If |
693 | // `require_trait_bound == true` (as it is for `FromBytes), we add where |
694 | // bounds for each field's type: |
695 | // |
696 | // impl<T, I: Iterator> FromBytes for Foo<T, I> |
697 | // where |
698 | // T: Copy, |
699 | // I: Clone, |
700 | // I::Item: Clone, |
701 | // T: FromBytes, |
702 | // I::Item: FromBytes, |
703 | // { |
704 | // } |
705 | // |
706 | // NOTE: It is standard practice to only emit bounds for the type parameters |
707 | // themselves, not for field types based on those parameters (e.g., `T` vs |
708 | // `T::Foo`). For a discussion of why this is standard practice, see |
709 | // https://github.com/rust-lang/rust/issues/26925. |
710 | // |
711 | // The reason we diverge from this standard is that doing it that way for us |
712 | // would be unsound. E.g., consider a type, `T` where `T: FromBytes` but |
713 | // `T::Foo: !FromBytes`. It would not be sound for us to accept a type with |
714 | // a `T::Foo` field as `FromBytes` simply because `T: FromBytes`. |
715 | // |
716 | // While there's no getting around this requirement for us, it does have the |
717 | // pretty serious downside that, when lifetimes are involved, the trait |
718 | // solver ties itself in knots: |
719 | // |
720 | // #[derive(Unaligned)] |
721 | // #[repr(C)] |
722 | // struct Dup<'a, 'b> { |
723 | // a: PhantomData<&'a u8>, |
724 | // b: PhantomData<&'b u8>, |
725 | // } |
726 | // |
727 | // error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned` |
728 | // --> src/main.rs:6:10 |
729 | // | |
730 | // 6 | #[derive(Unaligned)] |
731 | // | ^^^^^^^^^ |
732 | // | |
733 | // = note: required by `zerocopy::Unaligned` |
734 | |
735 | let type_ident = &input.ident; |
736 | let trait_ident = trt.ident(); |
737 | let field_types = data.field_types(); |
738 | |
739 | let bound_tt = |ty| parse_quote!(#ty: ::zerocopy::#trait_ident); |
740 | let field_type_bounds: Vec<_> = match (require_trait_bound_on_field_types, &field_types[..]) { |
741 | (RequireBoundedFields::Yes, _) => field_types.iter().map(bound_tt).collect(), |
742 | (RequireBoundedFields::No, _) | (RequireBoundedFields::Trailing, []) => vec![], |
743 | (RequireBoundedFields::Trailing, [.., last]) => vec![bound_tt(last)], |
744 | }; |
745 | |
746 | // Don't bother emitting a padding check if there are no fields. |
747 | #[allow ( |
748 | unstable_name_collisions, // See `BoolExt` below |
749 | clippy::incompatible_msrv, // https://github.com/rust-lang/rust-clippy/issues/12280 |
750 | )] |
751 | let padding_check_bound = padding_check.and_then(|check| (!field_types.is_empty()).then_some(check)).map(|check| { |
752 | let fields = field_types.iter(); |
753 | let validator_macro = check.validator_macro_ident(); |
754 | parse_quote!( |
755 | ::zerocopy::macro_util::HasPadding<#type_ident, {::zerocopy::#validator_macro!(#type_ident, #(#fields),*)}>: |
756 | ::zerocopy::macro_util::ShouldBe<false> |
757 | ) |
758 | }); |
759 | |
760 | let self_sized_bound = if require_self_sized { Some(parse_quote!(Self: Sized)) } else { None }; |
761 | |
762 | let bounds = input |
763 | .generics |
764 | .where_clause |
765 | .as_ref() |
766 | .map(|where_clause| where_clause.predicates.iter()) |
767 | .into_iter() |
768 | .flatten() |
769 | .chain(field_type_bounds.iter()) |
770 | .chain(padding_check_bound.iter()) |
771 | .chain(self_sized_bound.iter()); |
772 | |
773 | // The parameters with trait bounds, but without type defaults. |
774 | let params = input.generics.params.clone().into_iter().map(|mut param| { |
775 | match &mut param { |
776 | GenericParam::Type(ty) => ty.default = None, |
777 | GenericParam::Const(cnst) => cnst.default = None, |
778 | GenericParam::Lifetime(_) => {} |
779 | } |
780 | quote!(#param) |
781 | }); |
782 | |
783 | // The identifiers of the parameters without trait bounds or type defaults. |
784 | let param_idents = input.generics.params.iter().map(|param| match param { |
785 | GenericParam::Type(ty) => { |
786 | let ident = &ty.ident; |
787 | quote!(#ident) |
788 | } |
789 | GenericParam::Lifetime(l) => { |
790 | let ident = &l.lifetime; |
791 | quote!(#ident) |
792 | } |
793 | GenericParam::Const(cnst) => { |
794 | let ident = &cnst.ident; |
795 | quote!({#ident}) |
796 | } |
797 | }); |
798 | |
799 | quote! { |
800 | // TODO(#553): Add a test that generates a warning when |
801 | // `#[allow(deprecated)]` isn't present. |
802 | #[allow(deprecated)] |
803 | unsafe impl < #(#params),* > ::zerocopy::#trait_ident for #type_ident < #(#param_idents),* > |
804 | where |
805 | #(#bounds,)* |
806 | { |
807 | fn only_derive_is_allowed_to_implement_this_trait() {} |
808 | |
809 | #extras |
810 | } |
811 | } |
812 | } |
813 | |
814 | fn print_all_errors(errors: Vec<Error>) -> proc_macro2::TokenStream { |
815 | errors.iter().map(Error::to_compile_error).collect() |
816 | } |
817 | |
818 | // A polyfill for `Option::then_some`, which was added after our MSRV. |
819 | // |
820 | // TODO(#67): Remove this once our MSRV is >= 1.62. |
821 | #[allow (unused)] |
822 | trait BoolExt { |
823 | fn then_some<T>(self, t: T) -> Option<T>; |
824 | } |
825 | |
826 | #[allow (unused)] |
827 | impl BoolExt for bool { |
828 | fn then_some<T>(self, t: T) -> Option<T> { |
829 | if self { |
830 | Some(t) |
831 | } else { |
832 | None |
833 | } |
834 | } |
835 | } |
836 | |
837 | #[cfg (test)] |
838 | mod tests { |
839 | use super::*; |
840 | |
841 | #[test ] |
842 | fn test_config_repr_orderings() { |
843 | // Validate that the repr lists in the various configs are in the |
844 | // canonical order. If they aren't, then our algorithm to look up in |
845 | // those lists won't work. |
846 | |
847 | // TODO(https://github.com/rust-lang/rust/issues/53485): Remove once |
848 | // `Vec::is_sorted` is stabilized. |
849 | fn is_sorted_and_deduped<T: Clone + Ord>(ts: &[T]) -> bool { |
850 | let mut sorted = ts.to_vec(); |
851 | sorted.sort(); |
852 | sorted.dedup(); |
853 | ts == sorted.as_slice() |
854 | } |
855 | |
856 | fn elements_are_sorted_and_deduped<T: Clone + Ord>(lists: &[&[T]]) -> bool { |
857 | lists.iter().all(|list| is_sorted_and_deduped(list)) |
858 | } |
859 | |
860 | fn config_is_sorted<T: KindRepr + Clone>(config: &Config<T>) -> bool { |
861 | elements_are_sorted_and_deduped(config.allowed_combinations) |
862 | && elements_are_sorted_and_deduped(config.disallowed_but_legal_combinations) |
863 | } |
864 | |
865 | assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG)); |
866 | assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG)); |
867 | assert!(config_is_sorted(&ENUM_UNALIGNED_CFG)); |
868 | } |
869 | |
870 | #[test ] |
871 | fn test_config_repr_no_overlap() { |
872 | // Validate that no set of reprs appears in both the |
873 | // `allowed_combinations` and `disallowed_but_legal_combinations` lists. |
874 | |
875 | fn overlap<T: Eq>(a: &[T], b: &[T]) -> bool { |
876 | a.iter().any(|elem| b.contains(elem)) |
877 | } |
878 | |
879 | fn config_overlaps<T: KindRepr + Eq>(config: &Config<T>) -> bool { |
880 | overlap(config.allowed_combinations, config.disallowed_but_legal_combinations) |
881 | } |
882 | |
883 | assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG)); |
884 | assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG)); |
885 | assert!(!config_overlaps(&ENUM_UNALIGNED_CFG)); |
886 | } |
887 | } |
888 | |