1 // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT 2 // 3 use proc_macro2::{Ident, Span, TokenStream}; 4 use quote::{quote, ToTokens as _}; 5 use syn::{spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, Error, Field}; 6 7 use crate::{ 8 derive::project::{ 9 derive_enum, derive_projection_struct_union, generate_tag_consts, struct_union_variant_id, 10 tag_ident, 11 }, 12 repr::EnumRepr, 13 util::{ 14 enum_size_from_repr, generate_tag_enum, Client, Ctx, DataExt, FieldBounds, 15 ImplBlockBuilder, Trait, TraitBound, 16 }, 17 }; 18 19 fn candidate_ident(ctx: &Ctx) -> Ident { 20 // Choose a source-pointer name distinct from the field names. Mixed-site 21 // hygiene keeps it out of the user's invariant expressions. 22 let fields = ctx.ast.data.fields(); 23 let mut name = "candidate".to_owned(); 24 while fields.iter().any(|(_, field, _)| field.to_string().trim_start_matches("r#") == name) { 25 name.push('_'); 26 } 27 Ident::new(&name, Span::mixed_site()) 28 } 29 30 /// Generates validation of every field of a struct or enum variant, leaving 31 /// the validated pointers in scope for subsequent field invariants. 32 /// A union is checked by invoking this once per field. 33 fn derive_variant_is_safe<'a>( 34 ctx: &Ctx, 35 variant_id: &TokenStream, 36 fields: impl IntoIterator<Item = &'a Field>, 37 ) -> Result<TokenStream, Error> { 38 let fields = fields.into_iter().collect::<Vec<_>>(); 39 if fields.is_empty() { 40 return Ok(quote!(true)); 41 } 42 43 let zerocopy_crate = &ctx.zerocopy_crate; 44 let core = ctx.core_path(); 45 let candidate = candidate_ident(ctx); 46 let field_names = fields.iter().enumerate().map(|(idx, field)| { 47 field 48 .ident 49 .as_ref() 50 .map(|name| name.to_token_stream()) 51 .unwrap_or_else(|| syn::Index::from(idx).to_token_stream()) 52 }); 53 let field_checks = field_names 54 .map(|name| { 55 quote! { 56 #zerocopy_crate::into_inner!( 57 #candidate.project::< 58 #zerocopy_crate::project_clients::TryFromBytesDerive, 59 _, 60 { #variant_id }, 61 { #zerocopy_crate::ident_id!(#name) }, 62 >() 63 ).try_into_safe::<_, #zerocopy_crate::BecauseImmutable>() 64 } 65 }) 66 .collect::<Vec<_>>(); 67 // Without invariants, no expression needs the validated pointers. Avoid 68 // introducing bindings that could collide with caller-defined items. 69 if ctx.invariant_span.is_none() { 70 return Ok(quote! { true #(&& #field_checks.is_ok())* }); 71 } 72 73 let field_bindings = fields 74 .iter() 75 .enumerate() 76 .map(|(idx, _)| ctx.fresh_ident(&format!("field_{}", idx))) 77 .collect::<Vec<_>>(); 78 let bound_fields = 79 fields.iter().copied().zip(field_bindings.iter().cloned()).collect::<Vec<_>>(); 80 let field_validations = fields 81 .iter() 82 .enumerate() 83 .map(|(idx, field)| { 84 let invariants = crate::invariant::parse(&field.attrs)?; 85 let invariants = invariants 86 .iter() 87 .map(|expression| crate::invariant::bind_fields(&bound_fields[..=idx], expression)); 88 Ok(quote! { 89 #( 90 // Keep `return` in an invariant from bypassing later fields. 91 if !{ #[inline(always)] || -> #core::primitive::bool { #invariants } }() { 92 return false; 93 } 94 )* 95 }) 96 }) 97 .collect::<Result<Vec<_>, Error>>()?; 98 Ok(quote! { 99 { 100 #( 101 // Retain each shared pointer for subsequent field invariants. 102 #[allow(unused_variables)] 103 let #field_bindings = match #field_checks { 104 #core::result::Result::Ok(#field_bindings) => #field_bindings, 105 #core::result::Result::Err(_) => return false, 106 }; 107 #field_validations 108 )* 109 true 110 } 111 }) 112 } 113 114 /// Generates an implementation of `is_safe` for an arbitrary enum. 115 /// 116 /// For an enum with fields, [`derive_enum`] generates the representation model 117 /// and projection impls. This function reads the tag, matches it against the 118 /// enum's discriminants, and validates each field of the selected variant 119 /// through those projections. A fieldless enum needs only the generated tag 120 /// enum and discriminant constants. 121 pub(crate) fn derive_is_safe( 122 ctx: &Ctx, 123 data: &DataEnum, 124 repr: &EnumRepr, 125 ) -> Result<TokenStream, Error> { 126 if !(repr.is_c() || repr.is_primitive()) { 127 return Err(Error::new( 128 ctx.ast.span(), 129 "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout", 130 )); 131 } 132 133 let zerocopy_crate = &ctx.zerocopy_crate; 134 let core = ctx.core_path(); 135 let candidate = candidate_ident(ctx); 136 let alignment = ctx.fresh_ident("___ZcAlignment"); 137 let tag = Ident::new("tag", Span::mixed_site()); 138 let projections = if data.fields().is_empty() { 139 let tag_enum = generate_tag_enum(ctx, repr, data); 140 let tag_consts = generate_tag_consts(data); 141 quote! { 142 #tag_enum 143 144 type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag< 145 { #core::mem::size_of::<___ZerocopyTag>() }, 146 >; 147 148 #tag_consts 149 } 150 } else { 151 derive_enum(ctx, data, Client::TryFromBytesDerive)? 152 }; 153 154 let match_arms = data 155 .variants 156 .iter() 157 .enumerate() 158 .map(|(idx, variant)| { 159 let name = &variant.ident; 160 let variant_id = quote! { #zerocopy_crate::ident_id!(#name) }; 161 let fields_is_safe = derive_variant_is_safe(ctx, &variant_id, &variant.fields)?; 162 let pattern = if ctx.invariant_span.is_some() { 163 quote! { #core::option::Option::Some(#idx) } 164 } else { 165 tag_ident(name).to_token_stream() 166 }; 167 Ok(quote! { 168 #pattern => { #fields_is_safe } 169 }) 170 }) 171 .collect::<Result<Vec<_>, Error>>()?; 172 173 let read_tag = quote! { 174 let #tag = #candidate 175 .reborrow() 176 .cast::< 177 ___ZerocopyTagPrimitive, 178 #zerocopy_crate::pointer::cast::CastSized, 179 (#zerocopy_crate::pointer::BecauseRead, _), 180 >() 181 .recall_validity::<_, (_, (_, _))>() 182 .read::<#zerocopy_crate::BecauseImmutable>(); 183 }; 184 let tag_init = if ctx.invariant_span.is_some() { 185 let allow = crate::util::allow_generated_code(); 186 let tag_arms = data.variants.iter().enumerate().map(|(idx, variant)| { 187 let tag = tag_ident(&variant.ident); 188 quote! { #tag => #core::option::Option::Some(#idx) } 189 }); 190 quote! { 191 // Keep implementation-only items and their lint allowances out 192 // of the scopes containing caller-authored invariant expressions. 193 #allow 194 let #tag = { 195 #projections 196 #read_tag 197 match #tag { 198 #(#tag_arms,)* 199 _ => #core::option::Option::None, 200 } 201 }; 202 } 203 } else { 204 quote! { #projections #read_tag } 205 }; 206 207 Ok(quote! { 208 // SAFETY: We use `is_safe` to validate that the bit pattern of the 209 // enum's tag corresponds to one of the enum's discriminants. Then, we 210 // check the bit validity of each field of the corresponding variant. 211 // Thus, this is a sound implementation of `is_safe`. 212 #[inline] 213 fn is_safe<#alignment>( 214 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>, 215 ) -> #core::primitive::bool 216 where 217 #alignment: #zerocopy_crate::invariant::Alignment, 218 { 219 #tag_init 220 221 match #tag { 222 #(#match_arms,)* 223 _ => false, 224 } 225 } 226 }) 227 } 228 pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> { 229 match &ctx.ast.data { 230 Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level), 231 Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level), 232 Data::Union(unn) => derive_try_from_bytes_union(ctx, unn, top_level), 233 } 234 } 235 fn derive_try_from_bytes_struct( 236 ctx: &Ctx, 237 strct: &DataStruct, 238 top_level: Trait, 239 ) -> Result<TokenStream, Error> { 240 let extras = if let Some(extras) = try_gen_trivial_is_safe(ctx, top_level) { 241 extras 242 } else { 243 let zerocopy_crate = &ctx.zerocopy_crate; 244 let variant_id = quote! { #zerocopy_crate::STRUCT_VARIANT_ID }; 245 let fields_is_safe = derive_variant_is_safe(ctx, &variant_id, &strct.fields)?; 246 let core = ctx.core_path(); 247 let candidate = candidate_ident(ctx); 248 let alignment = ctx.fresh_ident("___ZcAlignment"); 249 quote!( 250 // SAFETY: We use `is_safe` to validate that each field is bit-valid, 251 // and only return `true` if all of them are. The bit validity of a 252 // struct is just the composition of the bit validities of its 253 // fields, so this is a sound implementation of `is_safe`. 254 #[inline] 255 fn is_safe<#alignment>( 256 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>, 257 ) -> #core::primitive::bool 258 where 259 #alignment: #zerocopy_crate::invariant::Alignment, 260 { 261 #fields_is_safe 262 } 263 ) 264 }; 265 Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF) 266 .inner_extras(extras) 267 .outer_extras(derive_projection_struct_union(ctx, strct, Client::TryFromBytesDerive)) 268 .build()) 269 } 270 fn derive_try_from_bytes_union( 271 ctx: &Ctx, 272 unn: &DataUnion, 273 top_level: Trait, 274 ) -> Result<TokenStream, Error> { 275 let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]); 276 277 let zerocopy_crate = &ctx.zerocopy_crate; 278 let union_variant_id = struct_union_variant_id(ctx).to_token_stream(); 279 let extras = if let Some(extras) = try_gen_trivial_is_safe(ctx, top_level) { 280 extras 281 } else { 282 let fields_is_safe = unn 283 .fields 284 .named 285 .iter() 286 .map(|field| derive_variant_is_safe(ctx, &union_variant_id, [field])) 287 .collect::<Result<Vec<_>, _>>()?; 288 let core = ctx.core_path(); 289 let candidate = candidate_ident(ctx); 290 let alignment = ctx.fresh_ident("___ZcAlignment"); 291 quote!( 292 // SAFETY: We use `is_safe` to validate that any field is bit-valid; 293 // we only return `true` if at least one of them is and its 294 // invariants also hold. The bit validity of a union is not yet 295 // well defined in Rust, but it is guaranteed to be no more strict 296 // than this definition. See #696 for a more in-depth discussion. 297 #[inline] 298 fn is_safe<#alignment>( 299 mut #candidate: #zerocopy_crate::Maybe<'_, Self, #alignment>, 300 ) -> #core::primitive::bool 301 where 302 #alignment: #zerocopy_crate::invariant::Alignment, 303 { 304 // Keep each field's bindings and early returns local to that 305 // field, so a failure lets validation try the next field. 306 false #(|| { #[inline(always)] || { #fields_is_safe } }())* 307 } 308 ) 309 }; 310 Ok(ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds) 311 .inner_extras(extras) 312 .outer_extras(derive_projection_struct_union(ctx, unn, Client::TryFromBytesDerive)) 313 .build()) 314 } 315 fn derive_try_from_bytes_enum( 316 ctx: &Ctx, 317 enm: &DataEnum, 318 top_level: Trait, 319 ) -> Result<TokenStream, Error> { 320 let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?; 321 322 // If an enum has no fields, it has a well-defined integer representation, 323 // and every possible bit pattern corresponds to a valid discriminant tag, 324 // then it *could* be `FromBytes` (even if the user hasn't derived 325 // `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N 326 // variants. 327 let could_be_from_bytes = enum_size_from_repr(&repr) 328 .map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size) 329 .unwrap_or(false); 330 331 let trivial_is_safe = try_gen_trivial_is_safe(ctx, top_level); 332 let extra = match (trivial_is_safe, could_be_from_bytes) { 333 (Some(is_safe), _) => is_safe, 334 // SAFETY: It would be sound for the enum to implement `FromBytes`, as 335 // required by `gen_trivial_is_safe_unchecked`. 336 (None, true) => unsafe { gen_trivial_is_safe_unchecked(ctx) }, 337 (None, false) => match derive_is_safe(ctx, enm, &repr) { 338 Ok(extra) => extra, 339 Err(e) => return ctx.error_or_skip(e), 340 }, 341 }; 342 343 Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF) 344 .inner_extras(extra) 345 .build()) 346 } 347 fn try_gen_trivial_is_safe(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> { 348 // If the top-level trait is `FromBytes` and `Self` has no type parameters, 349 // then the `FromBytes` derive will fail compilation if `Self` is not 350 // actually soundly `FromBytes`, and so we can rely on that for our `is_safe` 351 // impl. It's plausible that we could make changes - or Rust could make 352 // changes (such as the "trivial bounds" language feature) - that make this 353 // no longer true. To hedge against these, we include an explicit `Self: 354 // FromBytes` check in the generated `is_safe`, which is bulletproof. 355 // 356 // If `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive 357 // to fail compilation if `Self` is not actually soundly `FromBytes`. 358 if matches!(top_level, Trait::FromBytes) 359 && ctx.ast.generics.params.is_empty() 360 && !ctx.skip_on_error 361 { 362 let zerocopy_crate = &ctx.zerocopy_crate; 363 let core = ctx.core_path(); 364 Some(quote!( 365 // SAFETY: See inline. 366 #[inline(always)] 367 fn is_safe<___ZcAlignment>( 368 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>, 369 ) -> #core::primitive::bool 370 where 371 ___ZcAlignment: #zerocopy_crate::invariant::Alignment, 372 { 373 if false { 374 fn assert_is_from_bytes<T>() 375 where 376 T: #zerocopy_crate::FromBytes, 377 T: ?#core::marker::Sized, 378 { 379 } 380 381 assert_is_from_bytes::<Self>(); 382 } 383 384 // SAFETY: The preceding code only compiles if `Self: 385 // FromBytes`. Thus, this code only compiles if all initialized 386 // byte sequences represent valid instances of `Self`. 387 true 388 } 389 )) 390 } else { 391 None 392 } 393 } 394 395 /// # Safety 396 /// 397 /// All initialized bit patterns must be valid for `Self`. 398 unsafe fn gen_trivial_is_safe_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream { 399 let zerocopy_crate = &ctx.zerocopy_crate; 400 let core = ctx.core_path(); 401 quote!( 402 // SAFETY: The caller of `gen_trivial_is_safe_unchecked` has promised 403 // that all initialized bit patterns are valid for `Self`. 404 #[inline(always)] 405 fn is_safe<___ZcAlignment>( 406 _candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>, 407 ) -> #core::primitive::bool 408 where 409 ___ZcAlignment: #zerocopy_crate::invariant::Alignment, 410 { 411 true 412 } 413 ) 414 } 415 416 #[cfg(test)] 417 mod tests { 418 use syn::{parse_quote, DeriveInput}; 419 420 use super::*; 421 422 fn derive_error(input: DeriveInput) -> String { 423 let ctx = Ctx::try_from_derive_input(input).unwrap(); 424 match derive_try_from_bytes(&ctx, Trait::TryFromBytes) { 425 Ok(_) => panic!("expected derive to reject conflicting representations"), 426 Err(error) => error.to_string(), 427 } 428 } 429 430 #[test] 431 fn raw_repr_conflict_matches_ordinary_repr() { 432 let ordinary = derive_error(parse_quote! { 433 #[repr(C, align(8))] 434 #[repr(u8)] 435 enum Packet { 436 Value(core::num::NonZeroU8), 437 End, 438 } 439 }); 440 let raw = derive_error(parse_quote! { 441 #[repr(C, align(8))] 442 #[r#repr(r#u8)] 443 enum Packet { 444 Value(core::num::NonZeroU8), 445 End, 446 } 447 }); 448 449 // Rejecting both spellings prevents generating a validator whose 450 // synthetic enum layout disagrees with the compiler's actual layout. 451 assert_eq!(ordinary, "this conflicts with another representation hint"); 452 assert_eq!(ordinary, raw); 453 } 454 } 455