Skip to content

Commit c5ef163

Browse files
committed
[derive] TryFromBytes on unions w/o Immutable
TODO: - Add tests in zerocopy-derive/tests for unions with UnsafeCells - Add tests (location TBD) for calling &mut methods on TryFromBytes on unions with UnsafeCells gherrit-pr-id: If86f182f8e3fda5d12d680c400c7a0a7f7095ce4
1 parent 525336d commit c5ef163

File tree

9 files changed

+110
-40
lines changed

9 files changed

+110
-40
lines changed

src/impls.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,8 @@ unsafe impl<T: TryFromBytes + ?Sized> TryFromBytes for UnsafeCell<T> {
808808
{
809809
}
810810

811+
const IS_IMMUTABLE: bool = false;
812+
811813
#[inline]
812814
fn is_bit_valid<A: invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool {
813815
// The only way to implement this function is using an exclusive-aliased

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,9 @@ pub unsafe trait TryFromBytes {
14481448
where
14491449
Self: Sized;
14501450

1451+
#[doc(hidden)]
1452+
const IS_IMMUTABLE: bool;
1453+
14511454
/// Does a given memory range contain a valid instance of `Self`?
14521455
///
14531456
/// # Safety

src/macros.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,10 @@ macro_rules! cryptocorrosion_derive_traits {
780780
$($field_ty: $crate::FromBytes,)*
781781
)?
782782
{
783+
const IS_IMMUTABLE: bool = true $(
784+
&& <$field_ty as $crate::TryFromBytes>::IS_IMMUTABLE
785+
)*;
786+
783787
fn is_bit_valid<A>(_c: $crate::Maybe<'_, Self, A>) -> bool
784788
where
785789
A: $crate::pointer::invariant::Reference
@@ -923,6 +927,10 @@ macro_rules! cryptocorrosion_derive_traits {
923927
$field_ty: $crate::FromBytes,
924928
)*
925929
{
930+
const IS_IMMUTABLE: bool = true $(
931+
&& <$field_ty as $crate::TryFromBytes>::IS_IMMUTABLE
932+
)*;
933+
926934
fn is_bit_valid<A>(_c: $crate::Maybe<'_, Self, A>) -> bool
927935
where
928936
A: $crate::pointer::invariant::Reference

src/util/macros.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ macro_rules! unsafe_impl {
134134
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
135135
fn only_derive_is_allowed_to_implement_this_trait() {}
136136

137+
// TODO: THIS IS UNSOUND. We are just using it to unblock ourselves.
138+
const IS_IMMUTABLE: bool = true;
139+
137140
#[inline]
138141
fn is_bit_valid<AA: crate::pointer::invariant::Reference>($candidate: Maybe<'_, Self, AA>) -> bool {
139142
$is_bit_valid
@@ -143,6 +146,10 @@ macro_rules! unsafe_impl {
143146
#[allow(clippy::missing_inline_in_public_items)]
144147
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
145148
fn only_derive_is_allowed_to_implement_this_trait() {}
149+
150+
// TODO: THIS IS UNSOUND. We are just using it to unblock ourselves.
151+
const IS_IMMUTABLE: bool = true;
152+
146153
#[inline(always)] fn is_bit_valid<AA: crate::pointer::invariant::Reference>(_: Maybe<'_, Self, AA>) -> bool { true }
147154
};
148155
(@method $trait:ident) => {
@@ -217,6 +224,10 @@ macro_rules! impl_for_transmute_from {
217224
$(<$tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?>)?
218225
TryFromBytes for $ty:ty [UnsafeCell<$repr:ty>]
219226
) => {
227+
// TODO: THIS IS UNSOUND because we don't know that `$ty` and `$repr`
228+
// have the same interior mutability.
229+
const IS_IMMUTABLE: bool = <$repr as TryFromBytes>::IS_IMMUTABLE;
230+
220231
#[inline]
221232
fn is_bit_valid<A: crate::pointer::invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool {
222233
let c: Maybe<'_, Self, crate::pointer::invariant::Exclusive> = candidate.into_exclusive_or_pme();
@@ -232,6 +243,10 @@ macro_rules! impl_for_transmute_from {
232243
$(<$tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?>)?
233244
TryFromBytes for $ty:ty [<$repr:ty>]
234245
) => {
246+
// TODO: THIS IS UNSOUND because we don't know that `$ty` and `$repr`
247+
// have the same interior mutability.
248+
const IS_IMMUTABLE: bool = <$repr as TryFromBytes>::IS_IMMUTABLE;
249+
235250
#[inline]
236251
fn is_bit_valid<A: crate::pointer::invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool {
237252
// SAFETY: This macro ensures that `$repr` and `Self` have the same

zerocopy-derive/src/enum.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use proc_macro2::{Span, TokenStream};
1010
use quote::quote;
11-
use syn::{parse_quote, DataEnum, Error, Fields, Generics, Ident, Path};
11+
use syn::{parse_quote, DataEnum, DeriveInput, Error, Fields, Generics, Ident, Path};
1212

1313
use crate::{derive_try_from_bytes_inner, repr::EnumRepr, Trait};
1414

@@ -210,6 +210,7 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
210210
/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
211211
/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
212212
pub(crate) fn derive_is_bit_valid(
213+
ast: &DeriveInput,
213214
enum_ident: &Ident,
214215
repr: &EnumRepr,
215216
generics: &Generics,
@@ -280,7 +281,10 @@ pub(crate) fn derive_is_bit_valid(
280281
}
281282
});
282283

284+
let is_immutable = crate::gen_is_immutable(ast, zerocopy_crate);
283285
Ok(quote! {
286+
#is_immutable
287+
284288
// SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
285289
// enum's tag corresponds to one of the enum's discriminants. Then, we
286290
// check the bit validity of each field of the corresponding variant.

zerocopy-derive/src/lib.rs

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,11 @@ fn derive_try_from_bytes_struct(
745745
let fields = strct.fields();
746746
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
747747
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
748+
749+
let is_immutable = gen_is_immutable(ast, zerocopy_crate);
748750
quote!(
751+
#is_immutable
752+
749753
// SAFETY: We use `is_bit_valid` to validate that each field is
750754
// bit-valid, and only return `true` if all of them are. The bit
751755
// validity of a struct is just the composition of the bit
@@ -808,15 +812,15 @@ fn derive_try_from_bytes_union(
808812
top_level: Trait,
809813
zerocopy_crate: &Path,
810814
) -> TokenStream {
811-
// FIXME(#5): Remove the `Immutable` bound.
812-
let field_type_trait_bounds =
813-
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
814815
let extras =
815816
try_gen_trivial_is_bit_valid(ast, top_level, zerocopy_crate).unwrap_or_else(|| {
816817
let fields = unn.fields();
817818
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
818819
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
820+
let is_immutable = gen_is_immutable(ast, zerocopy_crate);
819821
quote!(
822+
#is_immutable
823+
820824
// SAFETY: We use `is_bit_valid` to validate that any field is
821825
// bit-valid; we only return `true` if at least one of them is. The
822826
// bit validity of a union is not yet well defined in Rust, but it
@@ -828,16 +832,35 @@ fn derive_try_from_bytes_union(
828832
where
829833
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
830834
{
831-
use #zerocopy_crate::util::macro_util::core_reexport;
835+
use #zerocopy_crate::{
836+
util::macro_util::core_reexport,
837+
pointer::invariant::Aliasing
838+
};
839+
840+
trait ConstAssert {
841+
const IS_READ: bool;
842+
}
843+
844+
// TODO: What do we name `TryFromBytes`?
845+
impl<T: #zerocopy_crate::TryFromBytes, A: #zerocopy_crate::pointer::invariant::Aliasing> ConstAssert for (T, A) {
846+
const IS_READ: bool = {
847+
assert!(T::IS_IMMUTABLE || A::IS_EXCLUSIVE);
848+
true
849+
};
850+
}
851+
852+
assert!(<(Self, ___ZerocopyAliasing) as ConstAssert>::IS_READ);
832853

833854
false #(|| {
834855
// SAFETY:
835856
// - `project` is a field projection, and so it addresses a
836857
// subset of the bytes addressed by `slf`
837858
// - ..., and so it preserves provenance
838-
// - Since `Self: Immutable` is enforced by
839-
// `self_type_trait_bounds`, neither `*slf` nor the
840-
// returned pointer's referent contain any `UnsafeCell`s
859+
// - By the preceding assert, it is either the case that
860+
// `Self: Immutable` or that `___ZerocopyAliasing` is
861+
// `Exclusive`. In the former case, `Self: Immutable`
862+
// ensures that `*slf` nor the returned pointer's
863+
// referent contain any `UnsafeCell`s.
841864
let field_candidate = unsafe {
842865
let project = |slf: core_reexport::ptr::NonNull<Self>| {
843866
let slf = slf.as_ptr();
@@ -860,7 +883,7 @@ fn derive_try_from_bytes_union(
860883
}
861884
)
862885
});
863-
ImplBlockBuilder::new(ast, unn, Trait::TryFromBytes, field_type_trait_bounds, zerocopy_crate)
886+
ImplBlockBuilder::new(ast, unn, Trait::TryFromBytes, FieldBounds::ALL_SELF, zerocopy_crate)
864887
.inner_extras(extras)
865888
.build()
866889
}
@@ -887,9 +910,9 @@ fn derive_try_from_bytes_enum(
887910
(Some(is_bit_valid), _) => is_bit_valid,
888911
// SAFETY: It would be sound for the enum to implement `FomBytes`, as
889912
// required by `gen_trivial_is_bit_valid_unchecked`.
890-
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(zerocopy_crate) },
913+
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ast, zerocopy_crate) },
891914
(None, false) => {
892-
r#enum::derive_is_bit_valid(&ast.ident, &repr, &ast.generics, enm, zerocopy_crate)?
915+
r#enum::derive_is_bit_valid(ast, &ast.ident, &repr, &ast.generics, enm, zerocopy_crate)?
893916
}
894917
};
895918

@@ -932,8 +955,12 @@ fn try_gen_trivial_is_bit_valid(
932955
// make this no longer true. To hedge against these, we include an explicit
933956
// `Self: FromBytes` check in the generated `is_bit_valid`, which is
934957
// bulletproof.
958+
959+
let is_immutable = gen_is_immutable(ast, zerocopy_crate);
935960
if top_level == Trait::FromBytes && ast.generics.params.is_empty() {
936961
Some(quote!(
962+
#is_immutable
963+
937964
// SAFETY: See inline.
938965
fn is_bit_valid<___ZerocopyAliasing>(
939966
_candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
@@ -963,6 +990,21 @@ fn try_gen_trivial_is_bit_valid(
963990
}
964991
}
965992

993+
fn gen_is_immutable(ast: &DeriveInput, zerocopy_crate: &Path) -> proc_macro2::TokenStream {
994+
let fields = match &ast.data {
995+
Data::Struct(strct) => strct.fields(),
996+
Data::Enum(enm) => enm.fields(),
997+
Data::Union(unn) => unn.fields(),
998+
};
999+
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
1000+
1001+
quote!(
1002+
const IS_IMMUTABLE: bool = true #(
1003+
&& <#field_tys as #zerocopy_crate::TryFromBytes>::IS_IMMUTABLE
1004+
)*;
1005+
)
1006+
}
1007+
9661008
/// Generates a `TryFromBytes::is_bit_valid` instance that unconditionally
9671009
/// returns true.
9681010
///
@@ -974,8 +1016,14 @@ fn try_gen_trivial_is_bit_valid(
9741016
///
9751017
/// The caller must ensure that all initialized bit patterns are valid for
9761018
/// `Self`.
977-
unsafe fn gen_trivial_is_bit_valid_unchecked(zerocopy_crate: &Path) -> proc_macro2::TokenStream {
1019+
unsafe fn gen_trivial_is_bit_valid_unchecked(
1020+
ast: &DeriveInput,
1021+
zerocopy_crate: &Path,
1022+
) -> proc_macro2::TokenStream {
1023+
let is_immutable = gen_is_immutable(ast, zerocopy_crate);
9781024
quote!(
1025+
#is_immutable
1026+
9791027
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
9801028
// promised that all initialized bit patterns are valid for `Self`.
9811029
fn is_bit_valid<___ZerocopyAliasing>(
@@ -1146,12 +1194,7 @@ fn derive_from_zeros_union(
11461194
unn: &DataUnion,
11471195
zerocopy_crate: &Path,
11481196
) -> TokenStream {
1149-
// FIXME(#5): Remove the `Immutable` bound. It's only necessary for
1150-
// compatibility with `derive(TryFromBytes)` on unions; not for soundness.
1151-
let field_type_trait_bounds =
1152-
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
1153-
ImplBlockBuilder::new(ast, unn, Trait::FromZeros, field_type_trait_bounds, zerocopy_crate)
1154-
.build()
1197+
ImplBlockBuilder::new(ast, unn, Trait::FromZeros, FieldBounds::ALL_SELF, zerocopy_crate).build()
11551198
}
11561199

11571200
/// A struct is `FromBytes` if:
@@ -1223,12 +1266,7 @@ fn derive_from_bytes_union(
12231266
unn: &DataUnion,
12241267
zerocopy_crate: &Path,
12251268
) -> TokenStream {
1226-
// FIXME(#5): Remove the `Immutable` bound. It's only necessary for
1227-
// compatibility with `derive(TryFromBytes)` on unions; not for soundness.
1228-
let field_type_trait_bounds =
1229-
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Immutable)]);
1230-
ImplBlockBuilder::new(ast, unn, Trait::FromBytes, field_type_trait_bounds, zerocopy_crate)
1231-
.build()
1269+
ImplBlockBuilder::new(ast, unn, Trait::FromBytes, FieldBounds::ALL_SELF, zerocopy_crate).build()
12321270
}
12331271

12341272
fn derive_into_bytes_struct(

zerocopy-derive/tests/union_from_bytes.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ include!("include.rs");
1515
// A union is `imp::FromBytes` if:
1616
// - all fields are `imp::FromBytes`
1717

18-
#[derive(Clone, Copy, imp::Immutable, imp::FromBytes)]
18+
#[derive(Clone, Copy, imp::FromBytes)]
1919
union Zst {
2020
a: (),
2121
}
2222

2323
util_assert_impl_all!(Zst: imp::FromBytes);
2424
test_trivial_is_bit_valid!(Zst => test_zst_trivial_is_bit_valid);
2525

26-
#[derive(imp::Immutable, imp::FromBytes)]
26+
#[derive(imp::FromBytes)]
2727
union One {
2828
a: u8,
2929
}
3030

3131
util_assert_impl_all!(One: imp::FromBytes);
3232
test_trivial_is_bit_valid!(One => test_one_trivial_is_bit_valid);
3333

34-
#[derive(imp::Immutable, imp::FromBytes)]
34+
#[derive(imp::FromBytes)]
3535
union Two {
3636
a: u8,
3737
b: Zst,
@@ -40,7 +40,7 @@ union Two {
4040
util_assert_impl_all!(Two: imp::FromBytes);
4141
test_trivial_is_bit_valid!(Two => test_two_trivial_is_bit_valid);
4242

43-
#[derive(imp::Immutable, imp::FromBytes)]
43+
#[derive(imp::FromBytes)]
4444
union TypeParams<'a, T: imp::Copy, I: imp::Iterator>
4545
where
4646
I::Item: imp::Copy,
@@ -58,7 +58,7 @@ test_trivial_is_bit_valid!(TypeParams<'static, (), imp::IntoIter<()>> => test_ty
5858

5959
// Deriving `imp::FromBytes` should work if the union has bounded parameters.
6060

61-
#[derive(imp::Immutable, imp::FromBytes)]
61+
#[derive(imp::FromBytes)]
6262
#[repr(C)]
6363
union WithParams<'a: 'b, 'b: 'a, T: 'a + 'b + imp::FromBytes, const N: usize>
6464
where

zerocopy-derive/tests/union_from_zeros.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,29 @@ include!("include.rs");
1515
// A union is `imp::FromZeros` if:
1616
// - all fields are `imp::FromZeros`
1717

18-
#[derive(Clone, Copy, imp::Immutable, imp::FromZeros)]
18+
#[derive(Clone, Copy, imp::FromZeros)]
1919
union Zst {
2020
a: (),
2121
}
2222

2323
util_assert_impl_all!(Zst: imp::FromZeros);
2424

25-
#[derive(imp::Immutable, imp::FromZeros)]
25+
#[derive(imp::FromZeros)]
2626
union One {
2727
a: bool,
2828
}
2929

3030
util_assert_impl_all!(One: imp::FromZeros);
3131

32-
#[derive(imp::Immutable, imp::FromZeros)]
32+
#[derive(imp::FromZeros)]
3333
union Two {
3434
a: bool,
3535
b: Zst,
3636
}
3737

3838
util_assert_impl_all!(Two: imp::FromZeros);
3939

40-
#[derive(imp::Immutable, imp::FromZeros)]
40+
#[derive(imp::FromZeros)]
4141
union TypeParams<'a, T: imp::Copy, I: imp::Iterator>
4242
where
4343
I::Item: imp::Copy,
@@ -54,7 +54,7 @@ util_assert_impl_all!(TypeParams<'static, (), imp::IntoIter<()>>: imp::FromZeros
5454

5555
// Deriving `imp::FromZeros` should work if the union has bounded parameters.
5656

57-
#[derive(imp::Immutable, imp::FromZeros)]
57+
#[derive(imp::FromZeros)]
5858
#[repr(C)]
5959
union WithParams<'a: 'b, 'b: 'a, T: 'a + 'b + imp::FromZeros, const N: usize>
6060
where

0 commit comments

Comments
 (0)