Skip to content

Commit 36484da

Browse files
committed
[derive] IntoBytes on unions requires --cfg
Makes progress on #1792
1 parent a9f09d7 commit 36484da

File tree

12 files changed

+168
-28
lines changed

12 files changed

+168
-28
lines changed

src/lib.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4699,8 +4699,8 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
46994699
///
47004700
/// This derive analyzes, at compile time, whether the annotated type satisfies
47014701
/// the [safety conditions] of `IntoBytes` and implements `IntoBytes` if it is
4702-
/// sound to do so. This derive can be applied to structs, enums, and unions;
4703-
/// e.g.:
4702+
/// sound to do so. This derive can be applied to structs and enums (see below
4703+
/// for union support); e.g.:
47044704
///
47054705
/// ```
47064706
/// # use zerocopy_derive::{IntoBytes};
@@ -4720,15 +4720,6 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
47204720
/// ...
47214721
/// # */
47224722
/// }
4723-
///
4724-
/// #[derive(IntoBytes)]
4725-
/// #[repr(C)]
4726-
/// union MyUnion {
4727-
/// # variant: u8,
4728-
/// # /*
4729-
/// ...
4730-
/// # */
4731-
/// }
47324723
/// ```
47334724
///
47344725
/// [safety conditions]: trait@IntoBytes#safety
@@ -4758,6 +4749,31 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
47584749
///
47594750
/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
47604751
///
4752+
/// # Unions
4753+
///
4754+
/// Currently, union bit validity is [up in the air][union-validity], and so
4755+
/// zerocopy does not support `#[derive(IntoBytes)]` on unions by default.
4756+
/// However, implementing `IntoBytes` on a union type is likely sound on all
4757+
/// existing Rust toolchains - it's just that it may become unsound in the
4758+
/// future. You can opt-in to `#[derive(IntoBytes)]` support on unions by
4759+
/// passing the unstable `zerocopy_derive_union_into_bytes` cfg:
4760+
///
4761+
/// ```shell
4762+
/// $ RUSTFLAGS='--cfg zerocopy_derive_union_into_bytes' cargo build
4763+
/// ```
4764+
///
4765+
/// We make no stability guarantees regarding this cfg, and may remove it at any
4766+
/// point.
4767+
///
4768+
/// We are actively working with Rust to stabilize the necessary language
4769+
/// guarantees to support this in a forwards-compatible way, which will enable
4770+
/// us to remove the cfg gate. As part of this effort, we need to know how much
4771+
/// demand there is for this feature. If you would like to use `IntoBytes` on
4772+
/// unions, [please let us know][discussion].
4773+
///
4774+
/// [union-validity]: https://github.com/rust-lang/unsafe-code-guidelines/issues/438
4775+
/// [discussion]: https://github.com/google/zerocopy/discussions/1802
4776+
///
47614777
/// # Analysis
47624778
///
47634779
/// *This section describes, roughly, the analysis performed by this derive to
@@ -4821,15 +4837,6 @@ pub use zerocopy_derive::IntoBytes;
48214837
/// ...
48224838
/// # */
48234839
/// }
4824-
///
4825-
/// #[derive(IntoBytes)]
4826-
/// #[repr(C)]
4827-
/// union MyUnion {
4828-
/// # variant: u8,
4829-
/// # /*
4830-
/// ...
4831-
/// # */
4832-
/// }
48334840
/// ```
48344841
///
48354842
/// 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
@@ -934,6 +934,17 @@ fn derive_into_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> Result<TokenStre
934934
/// - `repr(C)`, `repr(transparent)`, or `repr(packed)`
935935
/// - no padding (size of union equals size of each field type)
936936
fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenStream, Error> {
937+
// See #1792 for more context.
938+
let cfg_compile_error = quote!(
939+
const _: () = {
940+
#[cfg(not(zerocopy_derive_union_into_bytes))]
941+
::zerocopy::util::macro_util::core_reexport::compile_error!(
942+
"requires --cfg zerocopy_derive_union_into_bytes;
943+
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802"
944+
);
945+
};
946+
);
947+
937948
// TODO(#10): Support type parameters.
938949
if !ast.generics.params.is_empty() {
939950
return Err(Error::new(Span::call_site(), "unsupported on types with type parameters"));
@@ -951,15 +962,16 @@ fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenSt
951962
));
952963
}
953964

954-
Ok(impl_block(
965+
let impl_block = impl_block(
955966
ast,
956967
unn,
957968
Trait::IntoBytes,
958969
FieldBounds::ALL_SELF,
959970
SelfBounds::None,
960971
Some(PaddingCheck::Union),
961972
None,
962-
))
973+
);
974+
Ok(quote!(#cfg_compile_error #impl_block))
963975
}
964976

965977
/// 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: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
// This file may not be copied, modified, or distributed except according to
77
// those terms.
88

9+
use once_cell::sync::OnceCell;
10+
use std::{env, mem, sync::Mutex};
911
use testutil::set_rustflags_w_warnings;
1012

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

2323
let t = trybuild::TestCases::new();
24-
t.compile_fail(format!("tests/{}/*.rs", source_files_dirname));
24+
t.compile_fail(format!("tests/{}/{}/*.rs", source_files_dirname, subdir));
25+
}
26+
27+
#[test]
28+
#[cfg_attr(miri, ignore)]
29+
fn ui() {
30+
test("");
31+
32+
// This tests the behavior when `--cfg zerocopy_derive_union_into_bytes` is
33+
// not present, so remove it. If this logic is wrong, that's fine - it will
34+
// exhibit as a test failure that we can debug at that point.
35+
let rustflags = env::var("RUSTFLAGS").unwrap();
36+
let new_rustflags = rustflags.replace("--cfg zerocopy_derive_union_into_bytes", "");
37+
38+
// SAFETY: None of our code is concurrently accessinv env vars. It's
39+
// possible that the test framework has spawned other threads that are
40+
// concurrently accessing env vars, but we can't do anything about that.
41+
#[allow(unused_unsafe)] // `set_var` is safe on our MSRV.
42+
unsafe {
43+
env::set_var("RUSTFLAGS", new_rustflags)
44+
};
45+
46+
test("union_into_bytes_cfg");
47+
48+
// Reset RUSTFLAGS in case we later add other tests which rely on its value.
49+
// This isn't strictly necessary, but it's easier to add this now when we're
50+
// thinking about the semantics of these env vars than to debug later when
51+
// we've forgotten about it.
52+
//
53+
// SAFETY: See previous safety comment.
54+
unsafe { env::set_var("RUSTFLAGS", rustflags) };
2555
}
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)