Skip to content

Commit 66d39a9

Browse files
Practice good Self hygiene (#2127) (#2313)
This commit revises the `KnownLayout` derive on `repr(C)` target structs to preserve the correct resolution of `Self` when constructing `__ZerocopyKnownLayoutMaybeUninit`. This type contains a `MaybeUninit` version of each of the target struct's field types. Previously, it achieved this by simply copying the tokens corresponding to field types from the definition of the target struct into the definition of `__ZerocopyKnownLayoutMaybeUninit` However, on types like this: #[repr(C)] struct Struct([u8; Self::N]); …this approach is insufficient. Pasted into `__ZerocopyKnownLayoutMaybeUninit`, `Self` ceases to refer to the target struct and instead refers to `__ZerocopyKnownLayoutMaybeUninit`. To preserve `Self` hygiene, this commit defines a struct for projecting the field types of the target struct based on their index: pub unsafe trait Field<Index> { type Type: ?Sized; } …then implements it for each of the field types of the target struct; e.g.: struct Index<const N: usize>; impl Field<Index<0>> for Struct { type Type = [u8; Self::N]; } With this, the fields of `__ZerocopyKnownLayoutMaybeUninit` can be defined hygienically; e.g., as `<Struct as Field<0>>::Type`. Fixes #2116 Co-authored-by: Jack Wrenn <[email protected]>
1 parent 64df858 commit 66d39a9

File tree

5 files changed

+188
-50
lines changed

5 files changed

+188
-50
lines changed

src/util/macro_util.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ use crate::{
2929
Immutable, IntoBytes, Ptr, TryFromBytes, Unalign, ValidityError,
3030
};
3131

32+
/// Projects the type of the field at `Index` in `Self`.
33+
///
34+
/// The `Index` parameter is any sort of handle that identifies the field; its
35+
/// definition is the obligation of the implementer.
36+
///
37+
/// # Safety
38+
///
39+
/// Unsafe code may assume that this accurately reflects the definition of
40+
/// `Self`.
41+
pub unsafe trait Field<Index> {
42+
/// The type of the field at `Index`.
43+
type Type: ?Sized;
44+
}
45+
3246
#[cfg_attr(
3347
zerocopy_diagnostic_on_unimplemented,
3448
diagnostic::on_unimplemented(

zerocopy-derive/src/ext.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use proc_macro2::{Span, TokenStream};
1010
use quote::ToTokens;
11-
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type};
11+
use syn::{Data, DataEnum, DataStruct, DataUnion, Field, Ident, Index, Type, Visibility};
1212

1313
pub(crate) trait DataExt {
1414
/// Extracts the names and types of all fields. For enums, extracts the names
@@ -19,23 +19,23 @@ pub(crate) trait DataExt {
1919
/// makes sense because we don't care about where they live - we just care
2020
/// about transitive ownership. But for field names, we'd only use them when
2121
/// generating is_bit_valid, which cares about where they live.
22-
fn fields(&self) -> Vec<(TokenStream, &Type)>;
22+
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
2323

24-
fn variants(&self) -> Vec<Vec<(TokenStream, &Type)>>;
24+
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>>;
2525

2626
fn tag(&self) -> Option<Ident>;
2727
}
2828

2929
impl DataExt for Data {
30-
fn fields(&self) -> Vec<(TokenStream, &Type)> {
30+
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
3131
match self {
3232
Data::Struct(strc) => strc.fields(),
3333
Data::Enum(enm) => enm.fields(),
3434
Data::Union(un) => un.fields(),
3535
}
3636
}
3737

38-
fn variants(&self) -> Vec<Vec<(TokenStream, &Type)>> {
38+
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
3939
match self {
4040
Data::Struct(strc) => strc.variants(),
4141
Data::Enum(enm) => enm.variants(),
@@ -53,11 +53,11 @@ impl DataExt for Data {
5353
}
5454

5555
impl DataExt for DataStruct {
56-
fn fields(&self) -> Vec<(TokenStream, &Type)> {
56+
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
5757
map_fields(&self.fields)
5858
}
5959

60-
fn variants(&self) -> Vec<Vec<(TokenStream, &Type)>> {
60+
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
6161
vec![self.fields()]
6262
}
6363

@@ -67,11 +67,11 @@ impl DataExt for DataStruct {
6767
}
6868

6969
impl DataExt for DataEnum {
70-
fn fields(&self) -> Vec<(TokenStream, &Type)> {
70+
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
7171
map_fields(self.variants.iter().flat_map(|var| &var.fields))
7272
}
7373

74-
fn variants(&self) -> Vec<Vec<(TokenStream, &Type)>> {
74+
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
7575
self.variants.iter().map(|var| map_fields(&var.fields)).collect()
7676
}
7777

@@ -81,11 +81,11 @@ impl DataExt for DataEnum {
8181
}
8282

8383
impl DataExt for DataUnion {
84-
fn fields(&self) -> Vec<(TokenStream, &Type)> {
84+
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
8585
map_fields(&self.fields.named)
8686
}
8787

88-
fn variants(&self) -> Vec<Vec<(TokenStream, &Type)>> {
88+
fn variants(&self) -> Vec<Vec<(&Visibility, TokenStream, &Type)>> {
8989
vec![self.fields()]
9090
}
9191

@@ -96,12 +96,13 @@ impl DataExt for DataUnion {
9696

9797
fn map_fields<'a>(
9898
fields: impl 'a + IntoIterator<Item = &'a Field>,
99-
) -> Vec<(TokenStream, &'a Type)> {
99+
) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
100100
fields
101101
.into_iter()
102102
.enumerate()
103103
.map(|(idx, f)| {
104104
(
105+
&f.vis,
105106
f.ident
106107
.as_ref()
107108
.map(ToTokens::to_token_stream)

zerocopy-derive/src/lib.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ fn derive_known_layout_inner(ast: &DeriveInput, _top_level: Trait) -> Result<Tok
144144
Some((trailing_field, leading_fields)),
145145
) = (is_repr_c_struct, fields.split_last())
146146
{
147-
let (_name, trailing_field_ty) = trailing_field;
148-
let leading_fields_tys = leading_fields.iter().map(|(_name, ty)| ty);
147+
let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
148+
let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
149149

150150
let core_path = quote!(::zerocopy::util::macro_util::core_reexport);
151151
let repr_align = repr
@@ -266,20 +266,66 @@ fn derive_known_layout_inner(ast: &DeriveInput, _top_level: Trait) -> Result<Tok
266266
Default::default()
267267
};
268268

269+
// Generate a valid ident for a type-level handle to a field of a
270+
// given `name`.
271+
let field_index =
272+
|name| Ident::new(&format!("__Zerocopy_Field_{}", name), ident.span());
273+
274+
let field_indices: Vec<_> =
275+
fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
276+
277+
// Define the collection of type-level field handles.
278+
let field_defs = field_indices.iter().zip(&fields).map(|(idx, (vis, _, _))| {
279+
quote! {
280+
#[allow(non_camel_case_types)]
281+
#vis struct #idx;
282+
}
283+
});
284+
285+
let field_impls = field_indices.iter().zip(&fields).map(|(idx, (_, _, ty))| quote! {
286+
// SAFETY: `#ty` is the type of `#ident`'s field at `#idx`.
287+
unsafe impl #impl_generics ::zerocopy::util::macro_util::Field<#idx> for #ident #ty_generics
288+
where
289+
#predicates
290+
{
291+
type Type = #ty;
292+
}
293+
});
294+
295+
let trailing_field_index = field_index(trailing_field_name);
296+
let leading_field_indices =
297+
leading_fields.iter().map(|(_vis, name, _ty)| field_index(name));
298+
299+
let trailing_field_ty = quote! {
300+
<#ident #ty_generics as
301+
::zerocopy::util::macro_util::Field<#trailing_field_index>
302+
>::Type
303+
};
304+
269305
let methods = make_methods(&parse_quote! {
270306
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit
271307
});
272308

273309
quote! {
310+
#(#field_defs)*
311+
312+
#(#field_impls)*
313+
274314
// SAFETY: This has the same layout as the derive target type,
275-
// except that it admits uninit bytes. This is ensured by using the
276-
// same repr as the target type, and by using field types which have
277-
// the same layout as the target type's fields, except that they
278-
// admit uninit bytes.
315+
// except that it admits uninit bytes. This is ensured by using
316+
// the same repr as the target type, and by using field types
317+
// which have the same layout as the target type's fields,
318+
// except that they admit uninit bytes. We indirect through
319+
// `Field` to ensure that occurrences of `Self` resolve to
320+
// `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
279321
#repr
280322
#[doc(hidden)]
281323
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
282-
#(::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<#leading_fields_tys>,)*
324+
#(::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<
325+
<#ident #ty_generics as
326+
::zerocopy::util::macro_util::Field<#leading_field_indices>
327+
>::Type
328+
>,)*
283329
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit
284330
)
285331
where
@@ -295,9 +341,6 @@ fn derive_known_layout_inner(ast: &DeriveInput, _top_level: Trait) -> Result<Tok
295341
unsafe impl #impl_generics ::zerocopy::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
296342
where
297343
#trailing_field_ty: ::zerocopy::KnownLayout,
298-
// This bound may appear to be superfluous, but is required
299-
// on our MSRV (1.55) to avoid an ICE.
300-
<#trailing_field_ty as ::zerocopy::KnownLayout>::MaybeUninit: ::zerocopy::KnownLayout,
301344
#predicates
302345
{
303346
#[allow(clippy::missing_inline_in_public_items)]
@@ -494,8 +537,8 @@ fn derive_try_from_bytes_struct(
494537
) -> Result<TokenStream, Error> {
495538
let extras = try_gen_trivial_is_bit_valid(ast, top_level).unwrap_or_else(|| {
496539
let fields = strct.fields();
497-
let field_names = fields.iter().map(|(name, _ty)| name);
498-
let field_tys = fields.iter().map(|(_name, ty)| ty);
540+
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
541+
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
499542
quote!(
500543
// SAFETY: We use `is_bit_valid` to validate that each field is
501544
// bit-valid, and only return `true` if all of them are. The bit
@@ -552,8 +595,8 @@ fn derive_try_from_bytes_union(
552595
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
553596
let extras = try_gen_trivial_is_bit_valid(ast, top_level).unwrap_or_else(|| {
554597
let fields = unn.fields();
555-
let field_names = fields.iter().map(|(name, _ty)| name);
556-
let field_tys = fields.iter().map(|(_name, ty)| ty);
598+
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
599+
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
557600
quote!(
558601
// SAFETY: We use `is_bit_valid` to validate that any field is
559602
// bit-valid; we only return `true` if at least one of them is. The
@@ -1419,12 +1462,13 @@ fn impl_block<D: DataExt>(
14191462
parse_quote!(#ty: #(#traits)+*)
14201463
}
14211464
let field_type_bounds: Vec<_> = match (field_type_trait_bounds, &fields[..]) {
1422-
(FieldBounds::All(traits), _) => {
1423-
fields.iter().map(|(_name, ty)| bound_tt(ty, normalize_bounds(trt, traits))).collect()
1424-
}
1465+
(FieldBounds::All(traits), _) => fields
1466+
.iter()
1467+
.map(|(_vis, _name, ty)| bound_tt(ty, normalize_bounds(trt, traits)))
1468+
.collect(),
14251469
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
14261470
(FieldBounds::Trailing(traits), [.., last]) => {
1427-
vec![bound_tt(last.1, normalize_bounds(trt, traits))]
1471+
vec![bound_tt(last.2, normalize_bounds(trt, traits))]
14281472
}
14291473
(FieldBounds::Explicit(bounds), _) => bounds,
14301474
};
@@ -1436,7 +1480,7 @@ fn impl_block<D: DataExt>(
14361480
let padding_check_bound =
14371481
padding_check.and_then(|check| (!fields.is_empty()).then_some(check)).map(|check| {
14381482
let variant_types = variants.iter().map(|var| {
1439-
let types = var.iter().map(|(_name, ty)| ty);
1483+
let types = var.iter().map(|(_vis, _name, ty)| ty);
14401484
quote!([#(#types),*])
14411485
});
14421486
let validator_context = check.validator_macro_context();

zerocopy-derive/src/output_tests.rs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -176,20 +176,47 @@ fn test_known_layout() {
176176
<U>::pointer_to_metadata(ptr as *mut _)
177177
}
178178
}
179+
#[allow(non_camel_case_types)]
180+
struct __Zerocopy_Field_0;
181+
#[allow(non_camel_case_types)]
182+
struct __Zerocopy_Field_1;
183+
unsafe impl<T, U> ::zerocopy::util::macro_util::Field<__Zerocopy_Field_0>
184+
for Foo<T, U> {
185+
type Type = T;
186+
}
187+
unsafe impl<T, U> ::zerocopy::util::macro_util::Field<__Zerocopy_Field_1>
188+
for Foo<T, U> {
189+
type Type = U;
190+
}
179191
#[repr(C)]
180192
#[repr(align(2))]
181193
#[doc(hidden)]
182194
struct __ZerocopyKnownLayoutMaybeUninit<T, U>(
183-
::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<T>,
184-
<U as ::zerocopy::KnownLayout>::MaybeUninit,
195+
::zerocopy::util::macro_util::core_reexport::mem::MaybeUninit<
196+
<Foo<T, U> as ::zerocopy::util::macro_util::Field<__Zerocopy_Field_0>>::Type,
197+
>,
198+
<<Foo<
199+
T,
200+
U,
201+
> as ::zerocopy::util::macro_util::Field<
202+
__Zerocopy_Field_1,
203+
>>::Type as ::zerocopy::KnownLayout>::MaybeUninit,
185204
)
186205
where
187-
U: ::zerocopy::KnownLayout;
188-
unsafe impl<T, U> ::zerocopy::KnownLayout
189-
for __ZerocopyKnownLayoutMaybeUninit<T, U>
206+
<Foo<
207+
T,
208+
U,
209+
> as ::zerocopy::util::macro_util::Field<
210+
__Zerocopy_Field_1,
211+
>>::Type: ::zerocopy::KnownLayout;
212+
unsafe impl<T, U> ::zerocopy::KnownLayout for __ZerocopyKnownLayoutMaybeUninit<T, U>
190213
where
191-
U: ::zerocopy::KnownLayout,
192-
<U as ::zerocopy::KnownLayout>::MaybeUninit: ::zerocopy::KnownLayout,
214+
<Foo<
215+
T,
216+
U,
217+
> as ::zerocopy::util::macro_util::Field<
218+
__Zerocopy_Field_1,
219+
>>::Type: ::zerocopy::KnownLayout,
193220
{
194221
#[allow(clippy::missing_inline_in_public_items)]
195222
fn only_derive_is_allowed_to_implement_this_trait() {}
@@ -205,7 +232,12 @@ fn test_known_layout() {
205232
meta: Self::PointerMetadata,
206233
) -> ::zerocopy::util::macro_util::core_reexport::ptr::NonNull<Self> {
207234
use ::zerocopy::KnownLayout;
208-
let trailing = <<U as ::zerocopy::KnownLayout>::MaybeUninit as KnownLayout>::raw_from_ptr_len(
235+
let trailing = <<<Foo<
236+
T,
237+
U,
238+
> as ::zerocopy::util::macro_util::Field<
239+
__Zerocopy_Field_1,
240+
>>::Type as ::zerocopy::KnownLayout>::MaybeUninit as KnownLayout>::raw_from_ptr_len(
209241
bytes,
210242
meta,
211243
);
@@ -218,7 +250,12 @@ fn test_known_layout() {
218250
}
219251
#[inline(always)]
220252
fn pointer_to_metadata(ptr: *mut Self) -> Self::PointerMetadata {
221-
<<U as ::zerocopy::KnownLayout>::MaybeUninit>::pointer_to_metadata(
253+
<<<Foo<
254+
T,
255+
U,
256+
> as ::zerocopy::util::macro_util::Field<
257+
__Zerocopy_Field_1,
258+
>>::Type as ::zerocopy::KnownLayout>::MaybeUninit>::pointer_to_metadata(
222259
ptr as *mut _,
223260
)
224261
}

0 commit comments

Comments
 (0)