Skip to content

Commit c20262a

Browse files
authored
[derive] IntoBytes on unions requires --cfg (#1804)
Makes progress on #1792
1 parent 84719d9 commit c20262a

File tree

12 files changed

+170
-28
lines changed

12 files changed

+170
-28
lines changed

src/lib.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4668,8 +4668,8 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
46684668
///
46694669
/// This derive analyzes, at compile time, whether the annotated type satisfies
46704670
/// the [safety conditions] of `IntoBytes` and implements `IntoBytes` if it is
4671-
/// sound to do so. This derive can be applied to structs, enums, and unions;
4672-
/// e.g.:
4671+
/// sound to do so. This derive can be applied to structs and enums (see below
4672+
/// for union support); e.g.:
46734673
///
46744674
/// ```
46754675
/// # use zerocopy_derive::{IntoBytes};
@@ -4689,15 +4689,6 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
46894689
/// ...
46904690
/// # */
46914691
/// }
4692-
///
4693-
/// #[derive(IntoBytes)]
4694-
/// #[repr(C)]
4695-
/// union MyUnion {
4696-
/// # variant: u8,
4697-
/// # /*
4698-
/// ...
4699-
/// # */
4700-
/// }
47014692
/// ```
47024693
///
47034694
/// [safety conditions]: trait@IntoBytes#safety
@@ -4727,6 +4718,31 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
47274718
///
47284719
/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
47294720
///
4721+
/// # Unions
4722+
///
4723+
/// Currently, union bit validity is [up in the air][union-validity], and so
4724+
/// zerocopy does not support `#[derive(IntoBytes)]` on unions by default.
4725+
/// However, implementing `IntoBytes` on a union type is likely sound on all
4726+
/// existing Rust toolchains - it's just that it may become unsound in the
4727+
/// future. You can opt-in to `#[derive(IntoBytes)]` support on unions by
4728+
/// passing the unstable `zerocopy_derive_union_into_bytes` cfg:
4729+
///
4730+
/// ```shell
4731+
/// $ RUSTFLAGS='--cfg zerocopy_derive_union_into_bytes' cargo build
4732+
/// ```
4733+
///
4734+
/// We make no stability guarantees regarding this cfg, and may remove it at any
4735+
/// point.
4736+
///
4737+
/// We are actively working with Rust to stabilize the necessary language
4738+
/// guarantees to support this in a forwards-compatible way, which will enable
4739+
/// us to remove the cfg gate. As part of this effort, we need to know how much
4740+
/// demand there is for this feature. If you would like to use `IntoBytes` on
4741+
/// unions, [please let us know][discussion].
4742+
///
4743+
/// [union-validity]: https://github.com/rust-lang/unsafe-code-guidelines/issues/438
4744+
/// [discussion]: https://github.com/google/zerocopy/discussions/1802
4745+
///
47304746
/// # Analysis
47314747
///
47324748
/// *This section describes, roughly, the analysis performed by this derive to
@@ -4790,15 +4806,6 @@ pub use zerocopy_derive::IntoBytes;
47904806
/// ...
47914807
/// # */
47924808
/// }
4793-
///
4794-
/// #[derive(IntoBytes)]
4795-
/// #[repr(C)]
4796-
/// union MyUnion {
4797-
/// # variant: u8,
4798-
/// # /*
4799-
/// ...
4800-
/// # */
4801-
/// }
48024809
/// ```
48034810
///
48044811
/// This derive performs a sophisticated, compile-time safety analysis to

tools/cargo-zerocopy/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,11 @@ fn install_toolchain_or_exit(versions: &Versions, name: &str) -> Result<(), Erro
164164
}
165165

166166
fn get_rustflags(name: &str) -> &'static str {
167+
// See #1792 for context on zerocopy_derive_union_into_bytes.
167168
if name == "nightly" {
168-
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS "
169+
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS --cfg zerocopy_derive_union_into_bytes "
169170
} else {
170-
""
171+
"--cfg zerocopy_derive_union_into_bytes "
171172
}
172173
}
173174

zerocopy-derive/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ repository = "https://github.com/google/zerocopy"
2222
# [1] https://github.com/rust-lang/crater
2323
exclude = [".*", "tests/enum_from_bytes.rs", "tests/ui-nightly/enum_from_bytes_u16_too_few.rs.disabled"]
2424

25+
[lints.rust]
26+
# See #1792 for more context.
27+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zerocopy_derive_union_into_bytes)'] }
28+
2529
[lib]
2630
proc-macro = true
2731

zerocopy-derive/src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,17 @@ fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStre
949949
/// - `repr(C)`, `repr(transparent)`, or `repr(packed)`
950950
/// - no padding (size of union equals size of each field type)
951951
fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenStream, Error> {
952+
// See #1792 for more context.
953+
let cfg_compile_error = quote!(
954+
const _: () = {
955+
#[cfg(not(zerocopy_derive_union_into_bytes))]
956+
::zerocopy::util::macro_util::core_reexport::compile_error!(
957+
"requires --cfg zerocopy_derive_union_into_bytes;
958+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"
959+
);
960+
};
961+
);
962+
952963
// TODO(#10): Support type parameters.
953964
if !ast.generics.params.is_empty() {
954965
return Err(Error::new(Span::call_site(), "unsupported on types with type parameters"));
@@ -966,15 +977,16 @@ fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenSt
966977
));
967978
}
968979

969-
Ok(impl_block(
980+
let impl_block = impl_block(
970981
ast,
971982
unn,
972983
Trait::IntoBytes,
973984
FieldBounds::ALL_SELF,
974985
SelfBounds::None,
975986
Some(PaddingCheck::Union),
976987
None,
977-
))
988+
);
989+
Ok(quote!(#cfg_compile_error #impl_block))
978990
}
979991

980992
/// A struct is `Unaligned` if:

zerocopy-derive/src/output_tests.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,40 @@ fn test_into_bytes() {
332332
}
333333
}
334334

335+
#[test]
336+
fn test_union_into_bytes() {
337+
// Rustfmt spuriously adds spaces after the newline in the middle of the
338+
// string literal.
339+
#[rustfmt::skip]
340+
test! {
341+
IntoBytes {
342+
#[repr(C)]
343+
union Foo {
344+
a: u8,
345+
}
346+
} expands to {
347+
const _: () = {
348+
#[cfg(not(zerocopy_derive_union_into_bytes))]
349+
::zerocopy::util::macro_util::core_reexport::compile_error!(
350+
"requires --cfg zerocopy_derive_union_into_bytes;
351+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"
352+
);
353+
};
354+
#[allow(deprecated)]
355+
unsafe impl ::zerocopy::IntoBytes for Foo
356+
where
357+
u8: ::zerocopy::IntoBytes,
358+
(): ::zerocopy::util::macro_util::PaddingFree<
359+
Foo,
360+
{ ::zerocopy::union_has_padding!(Foo, [u8]) },
361+
>,
362+
{
363+
fn only_derive_is_allowed_to_implement_this_trait() {}
364+
}
365+
} no_build
366+
}
367+
}
368+
335369
#[test]
336370
fn test_unaligned() {
337371
test! {

zerocopy-derive/tests/trybuild.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
// This file may not be copied, modified, or distributed except according to
77
// those terms.
88

9+
use std::env;
910
use testutil::set_rustflags_w_warnings;
1011

11-
#[test]
12-
#[cfg_attr(miri, ignore)]
13-
fn ui() {
12+
fn test(subdir: &str) {
1413
let version = testutil::ToolchainVersion::extract_from_pwd().unwrap();
1514
// See the doc comment on this method for an explanation of what this does
1615
// and why we store source files in different directories.
@@ -21,5 +20,38 @@ fn ui() {
2120
set_rustflags_w_warnings();
2221

2322
let t = trybuild::TestCases::new();
24-
t.compile_fail(format!("tests/{}/*.rs", source_files_dirname));
23+
t.compile_fail(format!("tests/{}/{}/*.rs", source_files_dirname, subdir));
24+
}
25+
26+
#[test]
27+
#[cfg_attr(miri, ignore)]
28+
fn ui() {
29+
test("");
30+
31+
// This tests the behavior when `--cfg zerocopy_derive_union_into_bytes` is
32+
// not present, so remove it. If this logic is wrong, that's fine - it will
33+
// exhibit as a test failure that we can debug at that point.
34+
let rustflags = env::var("RUSTFLAGS").unwrap();
35+
let new_rustflags = rustflags.replace("--cfg zerocopy_derive_union_into_bytes", "");
36+
37+
// SAFETY: None of our code is concurrently accessinv env vars. It's
38+
// possible that the test framework has spawned other threads that are
39+
// concurrently accessing env vars, but we can't do anything about that.
40+
#[allow(unused_unsafe)] // `set_var` is safe on our MSRV.
41+
unsafe {
42+
env::set_var("RUSTFLAGS", new_rustflags)
43+
};
44+
45+
test("union_into_bytes_cfg");
46+
47+
// Reset RUSTFLAGS in case we later add other tests which rely on its value.
48+
// This isn't strictly necessary, but it's easier to add this now when we're
49+
// thinking about the semantics of these env vars than to debug later when
50+
// we've forgotten about it.
51+
//
52+
// SAFETY: See previous safety comment.
53+
#[allow(unused_unsafe)] // `set_var` is safe on our MSRV.
54+
unsafe {
55+
env::set_var("RUSTFLAGS", rustflags)
56+
};
2557
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../ui-nightly/union_into_bytes_cfg/union_into_bytes_cfg.rs
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: requires --cfg zerocopy_derive_union_into_bytes;
2+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802
3+
--> tests/ui-msrv/union_into_bytes_cfg/union_into_bytes_cfg.rs:20:10
4+
|
5+
20 | #[derive(IntoBytes)]
6+
| ^^^^^^^^^
7+
|
8+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2024 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+
//! See: https://github.com/google/zerocopy/issues/553
10+
//! zerocopy must still allow derives of deprecated types.
11+
//! This test has a hand-written impl of a deprecated type, and should result in a compilation
12+
//! error. If zerocopy does not tack an allow(deprecated) annotation onto its impls, then this
13+
//! test will fail because more than one compile error will be generated.
14+
#![deny(deprecated)]
15+
16+
extern crate zerocopy;
17+
18+
use zerocopy::IntoBytes;
19+
20+
#[derive(IntoBytes)]
21+
#[repr(C)]
22+
union Foo {
23+
a: u8,
24+
}
25+
26+
fn main() {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: requires --cfg zerocopy_derive_union_into_bytes;
2+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802
3+
--> tests/ui-nightly/union_into_bytes_cfg/union_into_bytes_cfg.rs:20:10
4+
|
5+
20 | #[derive(IntoBytes)]
6+
| ^^^^^^^^^
7+
|
8+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../ui-nightly/union_into_bytes_cfg/union_into_bytes_cfg.rs
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: requires --cfg zerocopy_derive_union_into_bytes;
2+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802
3+
--> tests/ui-stable/union_into_bytes_cfg/union_into_bytes_cfg.rs:20:10
4+
|
5+
20 | #[derive(IntoBytes)]
6+
| ^^^^^^^^^
7+
|
8+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)