Skip to content

Commit ef1a886

Browse files
committed
[wip] Introduce InitializeIntoBytes
Supports viewing types with padding as `&impl IntoBytes` by first zeroizing padding. gherrit-pr-id: G156cc6c0b2e4340c970ae4e097c7f6afdb38158e
1 parent bc52522 commit ef1a886

12 files changed

Lines changed: 410 additions & 47 deletions

File tree

src/byteorder.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,9 @@ example of how it can be used for parsing UDP packets.
505505
impl_or_verify!(O => Unaligned for $name<O>);
506506
};
507507

508+
#[cfg(not(any(feature = "derive", test)))]
509+
impl_initialize_into_bytes!(O => $name<O>);
510+
508511
impl<O> Default for $name<O> {
509512
#[inline(always)]
510513
fn default() -> $name<O> {

src/impls.rs

Lines changed: 104 additions & 42 deletions
Large diffs are not rendered by default.

src/layout.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,13 @@ impl DstLayout {
750750
}
751751
}
752752

753+
/*pub enum Fields {
754+
Primitive,
755+
Struct { fields: &'static [(usize, DstLayout)] },
756+
Union { fields: &'static [(usize, DstLayout)] },
757+
Enum,
758+
}*/
759+
753760
pub(crate) use cast_from::CastFrom;
754761
mod cast_from {
755762
use crate::*;

src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5496,6 +5496,27 @@ fn mut_from_prefix_suffix<T: FromBytes + IntoBytes + KnownLayout + ?Sized>(
54965496
Ok((slf.recall_validity::<_, (_, (_, _))>().as_mut(), prefix_suffix.as_mut()))
54975497
}
54985498

5499+
/// # Safety
5500+
///
5501+
/// TODO
5502+
pub unsafe trait InitializeIntoBytes {
5503+
#[doc(hidden)]
5504+
fn only_derive_is_allowed_to_implement_this_trait()
5505+
where
5506+
Self: Sized;
5507+
5508+
#[doc(hidden)]
5509+
fn initialize_padding(
5510+
ptr: Ptr<'_, Self, (invariant::Exclusive, invariant::Unaligned, invariant::Valid)>,
5511+
);
5512+
5513+
/// Produce an [`IntoBytes`] reference to `Self` by initializing its
5514+
/// padding.
5515+
fn initialize_into_bytes(&mut self) -> &(impl IntoBytes + Immutable + ?Sized) {
5516+
&42
5517+
}
5518+
}
5519+
54995520
/// Analyzes whether a type is [`IntoBytes`].
55005521
///
55015522
/// This derive analyzes, at compile time, whether the annotated type satisfies
@@ -5676,7 +5697,7 @@ pub use zerocopy_derive::IntoBytes;
56765697
not(no_zerocopy_diagnostic_on_unimplemented_1_78_0),
56775698
diagnostic::on_unimplemented(note = "Consider adding `#[derive(IntoBytes)]` to `{Self}`")
56785699
)]
5679-
pub unsafe trait IntoBytes {
5700+
pub unsafe trait IntoBytes: InitializeIntoBytes {
56805701
// The `Self: Sized` bound makes it so that this function doesn't prevent
56815702
// `IntoBytes` from being object safe. Note that other `IntoBytes` methods
56825703
// prevent object safety, but those provide a benefit in exchange for object

src/util/macros.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,18 @@ macro_rules! unsafe_impl {
128128
unsafe_impl!(@method $trait $(; |$candidate| $is_bit_valid)?);
129129
}
130130
}};
131+
(@method InitializeIntoBytes ; |$slf:ident| $initialize_padding:expr) => {
132+
#[allow(clippy::missing_inline_in_public_items, dead_code)]
133+
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
134+
fn only_derive_is_allowed_to_implement_this_trait() {}
131135

136+
#[allow(unused_mut)]
137+
#[inline(always)]
138+
fn initialize_padding($slf: Ptr<'_, Self, (invariant::Exclusive, invariant::Unaligned, invariant::Valid)>)
139+
{
140+
$initialize_padding
141+
}
142+
};
132143
(@method TryFromBytes ; |$candidate:ident| $is_bit_valid:expr) => {
133144
#[allow(clippy::missing_inline_in_public_items, dead_code)]
134145
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
@@ -174,6 +185,7 @@ macro_rules! impl_for_transmute_from {
174185
$(#[$attr:meta])*
175186
$($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?)?
176187
=> $trait:ident for $ty:ty [$repr:ty]
188+
$(|$slf:ident| $b:block)*
177189
) => {
178190
const _: () = {
179191
$(#[$attr])*
@@ -214,12 +226,20 @@ macro_rules! impl_for_transmute_from {
214226
$(<$tyvar $(: $(? $optbound +)* $($bound +)*)?>)?
215227
$trait for $ty [$repr]
216228
);
229+
230+
impl_for_transmute_from!(
231+
@initialize_into_bytes
232+
$(<$tyvar $(: $(? $optbound +)* $($bound +)*)?>)?
233+
$trait for $ty [$repr]
234+
$(|$slf| $b)*
235+
);
217236
}
218237
};
219238
};
220239
(@assert_is_supported_trait TryFromBytes) => {};
221240
(@assert_is_supported_trait FromZeros) => {};
222241
(@assert_is_supported_trait FromBytes) => {};
242+
(@assert_is_supported_trait InitializeIntoBytes) => {};
223243
(@assert_is_supported_trait IntoBytes) => {};
224244
(
225245
@is_bit_valid
@@ -242,7 +262,29 @@ macro_rules! impl_for_transmute_from {
242262
$(<$tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?>)?
243263
$trait:ident for $ty:ty [$repr:ty]
244264
) => {
245-
// Trait other than `TryFromBytes`; no `is_bit_valid` impl.
265+
// Other trait; no additional items.
266+
};
267+
(
268+
@initialize_into_bytes
269+
$(<$tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?>)?
270+
InitializeIntoBytes for $ty:ty [$repr:ty]
271+
) => {
272+
#[inline(always)]
273+
fn initialize_padding(ptr: Ptr<'_, Self, (invariant::Exclusive, invariant::Unaligned, invariant::Valid)>)
274+
{
275+
let ptr = ptr.transmute::<$repr, _, (_, (_, _))>();
276+
// SAFETY: This macro ensures that `$repr` and `Self` have the same
277+
// size and bit validity. Thus, a bit-valid instance of `$repr` is
278+
// also a bit-valid instance of `Self`.
279+
<$repr as InitializeIntoBytes>::initialize_padding(ptr)
280+
}
281+
};
282+
(
283+
@initialize_into_bytes
284+
$(<$tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?>)?
285+
$trait:ident for $ty:ty [$repr:ty] $(|$slf:ident| $b:block)*
286+
) => {
287+
// Other trait; no additional items.
246288
};
247289
}
248290

@@ -525,6 +567,36 @@ macro_rules! unsafe_impl_known_layout {
525567
}};
526568
}
527569

570+
macro_rules! impl_initialize_into_bytes {
571+
($(const $constvar:ident : $constty:ty, $tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
572+
$(impl_initialize_into_bytes!(@inner const $constvar: $constty, $tyvar $(: ?$optbound)? => $ty);)*
573+
};
574+
($($tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
575+
$(impl_initialize_into_bytes!(@inner , $tyvar $(: ?$optbound)? => $ty);)*
576+
};
577+
($($(#[$attrs:meta])* $ty:ty),*) => { $(impl_initialize_into_bytes!(@inner , => $(#[$attrs])* $ty);)* };
578+
(@inner $(const $constvar:ident : $constty:ty)? , $($tyvar:ident $(: ?$optbound:ident)?)? => $(#[$attrs:meta])* $ty:ty) => {
579+
const _: () = {
580+
#[allow(non_local_definitions)]
581+
$(#[$attrs])*
582+
// SAFETY: TODO
583+
unsafe impl<$($tyvar $(: ?$optbound)?)? $(, const $constvar : $constty)?> InitializeIntoBytes for $ty
584+
where
585+
Self: IntoBytes
586+
{
587+
#[allow(clippy::missing_inline_in_public_items)]
588+
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
589+
fn only_derive_is_allowed_to_implement_this_trait() where Self: Sized {}
590+
591+
#[inline(always)]
592+
fn initialize_padding(_: Ptr<'_, Self, (invariant::Exclusive, invariant::Unaligned, invariant::Valid)>) {
593+
// By invariant on `Self: IntoBytes`, values of type `Self` never contain padding.
594+
}
595+
}
596+
};
597+
};
598+
}
599+
528600
/// Uses `align_of` to confirm that a type or set of types have alignment 1.
529601
///
530602
/// Note that `align_of<T>` requires `T: Sized`, so this macro doesn't work for

src/util/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,26 @@ mod len_of {
656656

657657
pub(crate) use len_of::MetadataOf;
658658

659+
#[doc(hidden)]
660+
#[inline(always)]
661+
pub const fn sort_fields<const N: usize>(mut arr: [(usize, usize); N]) -> [(usize, usize); N] {
662+
let mut i = 0;
663+
while i < N {
664+
let mut j = 0;
665+
while j + 1 < N - i {
666+
if arr[j].0 > arr[j + 1].0 {
667+
let temp = arr[j];
668+
arr[j] = arr[j + 1];
669+
arr[j + 1] = temp;
670+
}
671+
j += 1;
672+
}
673+
i += 1;
674+
}
675+
676+
arr
677+
}
678+
659679
/// Since we support multiple versions of Rust, there are often features which
660680
/// have been stabilized in the most recent stable release which do not yet
661681
/// exist (stably) on our MSRV. This module provides polyfills for those

src/wrappers.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ const _: () = unsafe {
153153
);
154154
impl_or_verify!(T: FromZeros => FromZeros for Unalign<T>);
155155
impl_or_verify!(T: FromBytes => FromBytes for Unalign<T>);
156+
impl_or_verify!(
157+
T: InitializeIntoBytes => InitializeIntoBytes for Unalign<T>;
158+
|c| {
159+
let _ = c;
160+
// TODO
161+
//T::initialize_padding(c.transmute::<T, pointer::invariant::Valid, (pointer::BecauseMutationCompatible, _)>()
162+
}
163+
);
156164
impl_or_verify!(T: IntoBytes => IntoBytes for Unalign<T>);
157165
};
158166

@@ -636,6 +644,14 @@ mod read_only_def {
636644
}
637645

638646
impl<T: ?Sized> ReadOnly<T> {
647+
/// TODO
648+
#[inline(always)]
649+
pub fn from_mut(t: &mut T) -> &mut ReadOnly<T> {
650+
let ptr = crate::Ptr::from_mut(t).transmute::<Self, _, _>();
651+
let ptr = unsafe { ptr.assume_alignment() };
652+
ptr.as_mut()
653+
}
654+
639655
#[inline(always)]
640656
pub(crate) fn as_mut(r: &mut ReadOnly<T>) -> &mut T {
641657
// SAFETY: `r: &mut ReadOnly`, so this doesn't violate the invariant
@@ -675,6 +691,10 @@ const _: () = unsafe {
675691
);
676692
unsafe_impl!(T: ?Sized + FromZeros => FromZeros for ReadOnly<T>);
677693
unsafe_impl!(T: ?Sized + FromBytes => FromBytes for ReadOnly<T>);
694+
unsafe_impl!(T: ?Sized + InitializeIntoBytes => InitializeIntoBytes for ReadOnly<T>; |slf| {
695+
// TODO: Fix alignment.
696+
T::initialize_padding(slf.transmute());
697+
});
678698
unsafe_impl!(T: ?Sized + IntoBytes => IntoBytes for ReadOnly<T>);
679699
};
680700

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::quote;
3+
use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};
4+
5+
use crate::{
6+
repr::{EnumRepr, StructUnionRepr},
7+
util::{
8+
generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait,
9+
TraitBound,
10+
},
11+
SelfBounds,
12+
};
13+
14+
pub(crate) fn derive_initialize_into_bytes(
15+
ctx: &Ctx,
16+
top_level: Trait,
17+
) -> Result<TokenStream, Error> {
18+
try_gen_trivial_initialize_into_bytes(ctx, top_level).map(Ok).unwrap_or_else(|| {
19+
match &ctx.ast.data {
20+
Data::Struct(strct) => derive_initialize_into_bytes_struct(ctx, strct),
21+
Data::Enum(enm) => derive_initialize_into_bytes_enum(ctx, enm),
22+
Data::Union(unn) => derive_initialize_into_bytes_union(ctx, unn),
23+
}
24+
})
25+
}
26+
27+
fn try_gen_trivial_initialize_into_bytes(
28+
ctx: &Ctx,
29+
top_level: Trait,
30+
) -> Option<proc_macro2::TokenStream> {
31+
// If the top-level trait is `IntoBytes`, `IntoBytes` derive will fail
32+
// compilation if `Self` is not actually soundly `IntoBytes`, and so we can
33+
// rely on that for our `zeroize` impl. It's plausible that we could
34+
// make changes - or Rust could make changes (such as the "trivial bounds"
35+
// language feature) - that make this no longer true. To hedge against
36+
// these, we include an explicit `Self: IntoBytes` check in the generated
37+
// `is_bit_valid`, which is bulletproof.
38+
//
39+
// If `ctx.skip_on_error` is true, we can't rely on the `IntoBytes` derive
40+
// to fail compilation if `Self` is not actually soundly `IntoBytes`.
41+
if matches!(top_level, Trait::IntoBytes)
42+
&& ctx.ast.generics.params.is_empty()
43+
&& !ctx.skip_on_error
44+
{
45+
let zerocopy_crate = &ctx.zerocopy_crate;
46+
let core = ctx.core_path();
47+
48+
Some(
49+
ImplBlockBuilder::new(
50+
ctx,
51+
&ctx.ast.data,
52+
Trait::InitializeIntoBytes,
53+
FieldBounds::ALL_SELF,
54+
)
55+
.self_type_trait_bounds(SelfBounds::All(&[Trait::IntoBytes]))
56+
.inner_extras(quote!(
57+
// SAFETY: See inline.
58+
#[inline(always)]
59+
fn initialize_padding(ptr: #zerocopy_crate::Ptr<'_, Self, (
60+
#zerocopy_crate::invariant::Exclusive,
61+
#zerocopy_crate::invariant::Unaligned,
62+
#zerocopy_crate::invariant::Valid)>
63+
) {
64+
if false {
65+
fn assert_is_into_bytes<T>()
66+
where
67+
T: #zerocopy_crate::IntoBytes,
68+
T: ?#core::marker::Sized,
69+
{
70+
}
71+
72+
assert_is_into_bytes::<Self>();
73+
}
74+
}
75+
))
76+
.build(),
77+
)
78+
} else {
79+
None
80+
}
81+
}
82+
83+
fn derive_initialize_into_bytes_struct(
84+
ctx: &Ctx,
85+
strct: &DataStruct,
86+
) -> Result<TokenStream, Error> {
87+
let zerocopy_crate = &ctx.zerocopy_crate;
88+
let core: TokenStream = ctx.core_path();
89+
90+
// TODO: This is just the default-repr sized case. We also need a
91+
92+
let field_offsets_and_layouts = ctx.ast.data.fields().into_iter().map(
93+
|(_, name, ty)| quote!((#core::mem::offset_of!(Self, #name), #core::mem::size_of::<#ty>())),
94+
);
95+
96+
let subfield_zeroizations = ctx.ast.data.fields().into_iter().map(|(_, name, ty)| {
97+
quote! {{
98+
// TODO: Need to also emit `AsInitialized`/`Valid` projection impls
99+
// either here or in `TryFromBytes`.
100+
let field = #zerocopy_crate::into_inner!(ptr.reborrow().project::<
101+
_,
102+
{ #zerocopy_crate::STRUCT_VARIANT_ID },
103+
{ #zerocopy_crate::ident_id!(#name) }
104+
>());
105+
<#ty as #zerocopy_crate::InitializeIntoBytes>::initialize_padding(field);
106+
}}
107+
});
108+
109+
Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::InitializeIntoBytes, FieldBounds::ALL_SELF)
110+
.self_type_trait_bounds(SelfBounds::All(&[Trait::Sized, Trait::TryFromBytes]))
111+
.inner_extras(quote!(
112+
// SAFETY: See inline. TODO
113+
#[inline(always)]
114+
fn initialize_padding(ptr: #zerocopy_crate::Ptr<'_, Self, (
115+
#zerocopy_crate::invariant::Exclusive,
116+
#zerocopy_crate::invariant::Unaligned,
117+
#zerocopy_crate::invariant::Valid)>
118+
) {
119+
let fields = &#zerocopy_crate::util::sort_fields([
120+
#(#field_offsets_and_layouts,)*
121+
])[..];
122+
123+
let mut start = 0;
124+
while let [(offset, size), rest @ ..] = fields {
125+
fields = rest;
126+
127+
// Zero-out any padding between `start` and the field.
128+
{
129+
let ptr = self as *mut _ as *mut u8;
130+
let ptr = unsafe { ptr.add(start) };
131+
unsafe { #core::ptr::write_bytes(ptr, 0, *offset) };
132+
}
133+
134+
// Advance `start`.
135+
start += size;
136+
}
137+
138+
#(#subfield_zeroizations)*
139+
}
140+
))
141+
.build())
142+
}
143+
144+
fn derive_initialize_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
145+
todo!()
146+
}
147+
148+
fn derive_initialize_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
149+
todo!()
150+
}

0 commit comments

Comments
 (0)