| 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 | |