Skip to content

Commit ad4766f

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

File tree

12 files changed

+171
-11
lines changed

12 files changed

+171
-11
lines changed

src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4758,6 +4758,31 @@ fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
47584758
///
47594759
/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
47604760
///
4761+
/// # Unions
4762+
///
4763+
/// Currently, union bit validity is [up in the air][union-validity], and so
4764+
/// zerocopy does not support `#[derive(IntoBytes)]` on unions by default.
4765+
/// However, implementing `IntoBytes` on a union type is likely sound on all
4766+
/// existing Rust toolchains - it's just that it may become unsound in the
4767+
/// future. You can opt-in to `#[derive(IntoBytes)]` support on unions by
4768+
/// passing the unstable `zerocopy_derive_union_into_bytes` cfg:
4769+
///
4770+
/// ```shell
4771+
/// $ RUSTFLAGS='--cfg zerocopy_derive_union_into_bytes' cargo build
4772+
/// ```
4773+
///
4774+
/// We make no stability guarantees regarding this cfg, and may remove it at any
4775+
/// point.
4776+
///
4777+
/// We are actively working with Rust to stabilize the necessary language
4778+
/// guarantees to support this in a forwards-compatible way, which will enable
4779+
/// us to remove the cfg gate. As part of this effort, we need to know how much
4780+
/// demand there is for this feature. If you would like to use `IntoBytes` on
4781+
/// unions, [please let us know][discussion].
4782+
///
4783+
/// [union-validity]: https://github.com/rust-lang/unsafe-code-guidelines/issues/438
4784+
/// [discussion]: https://github.com/google/zerocopy/discussions/1802
4785+
///
47614786
/// # Analysis
47624787
///
47634788
/// *This section describes, roughly, the analysis performed by this derive 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 & 3 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

@@ -32,9 +36,6 @@ syn = { version = "2.0.46", features = ["full"] }
3236

3337
[dev-dependencies]
3438
dissimilar = "1.0.9"
35-
# We don't use this directly, but trybuild does. On the MSRV toolchain, the
36-
# version resolver fails to select any version for once_cell unless we
37-
# depend on it directly.
3839
once_cell = "=1.9"
3940
# This is the latest version which is compatible with `syn` 2.0.46, which we pin
4041
# to in CI for MSRV compatibility reasons.

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: 39 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,40 @@ 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+
// NOTE: `OnceCell` is required because const initialization of `Mutex`es is not
28+
// supported on our MSRV.
29+
static ENV_MTX: OnceCell<Mutex<()>> = OnceCell::new();
30+
31+
#[test]
32+
#[cfg_attr(miri, ignore)]
33+
fn ui() {
34+
let guard = ENV_MTX.get_or_init(Default::default).lock().unwrap();
35+
test("");
36+
mem::drop(guard);
37+
}
38+
39+
#[test]
40+
#[cfg_attr(miri, ignore)]
41+
fn ui_union_into_bytes_cfg() {
42+
// This tests the behavior when `--cfg zerocopy_derive_union_into_bytes` is
43+
// not present, so remove it. If this logic is wrong, that's fine - it will
44+
// exhibit as a test failure that we can debug at that point.
45+
let rustflags = env::var("RUSTFLAGS").unwrap();
46+
let rustflags = rustflags.replace("--cfg zerocopy_derive_union_into_bytes", "");
47+
48+
let guard = ENV_MTX.get_or_init(Default::default).lock().unwrap();
49+
// SAFETY: Thanks to holding `ENV_MTX`, we guarantee that the `ui` test is
50+
// not running in this critical section. It's still possible that the test
51+
// framework has spawned other threads that are concurrently accessing env
52+
// vars, but we can't do anything about that.
53+
#[allow(unused_unsafe)] // `set_var` is safe on our MSRV.
54+
unsafe {
55+
env::set_var("RUSTFLAGS", rustflags)
56+
};
57+
mem::drop(guard);
58+
59+
test("union_into_bytes_cfg");
2560
}
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)