Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tools/cargo-zerocopy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ fn install_targets_or_exit(version: &str, targets: &[String]) -> Result<(), Erro
fn get_rustflags(name: &str) -> String {
// See #1792 for context on zerocopy_derive_union_into_bytes.
let mut flags =
"--cfg zerocopy_unstable_derive_on_error --cfg zerocopy_derive_union_into_bytes --cfg __ZEROCOPY_INTERNAL_USE_ONLY_DEV_MODE"
"--cfg zerocopy_unstable_linux --cfg zerocopy_derive_union_into_bytes --cfg __ZEROCOPY_INTERNAL_USE_ONLY_DEV_MODE"
.to_string();
flags += &format!(" --cfg __ZEROCOPY_INTERNAL_USE_ONLY_TOOLCHAIN=\"{name}\"");

Expand Down
1 change: 1 addition & 0 deletions zerocopy/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ fn main() {
println!("cargo:rustc-check-cfg=cfg(coverage_nightly)");
println!("cargo:rustc-check-cfg=cfg(zerocopy_inline_always)");
println!("cargo:rustc-check-cfg=cfg(zerocopy_unstable_ptr)");
println!("cargo:rustc-check-cfg=cfg(zerocopy_unstable_linux)");
println!("cargo:rustc-check-cfg=cfg(no_fp_fmt_parse)");
}

Expand Down
3 changes: 3 additions & 0 deletions zerocopy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,9 @@ const _: () = {
WARNING
};

#[doc(hidden)]
#[cfg(all(any(feature = "derive", test), zerocopy_unstable_linux))]
pub use zerocopy_derive::most_traits;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Trailing newline after this?

/// Implements [`KnownLayout`].
///
/// This derive analyzes various aspects of a type's layout that are needed for
Expand Down
4 changes: 2 additions & 2 deletions zerocopy/testutil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ impl UiTestRunner {
let mut command = Command::new("cargo");
command.current_dir(workspace_root.clone());
// We strip `--cfg zerocopy_derive_union_into_bytes` and `--cfg
// zerocopy_unstable_derive_on_error` from `RUSTFLAGS` so that the
// zerocopy_unstable_linux` from `RUSTFLAGS` so that the
// `zerocopy-derive` proc macro is built without them. This ensures it
// generates the feature-gate checks into the UI tests, which we can
// then explicitly enable or disable via `rustc_args`.
let mut rustflags = env::var("RUSTFLAGS").unwrap_or_default();
let cfgs_to_strip =
["--cfg zerocopy_derive_union_into_bytes", "--cfg zerocopy_unstable_derive_on_error"];
["--cfg zerocopy_derive_union_into_bytes", "--cfg zerocopy_unstable_linux"];
for &cfg in &cfgs_to_strip {
rustflags = rustflags.replace(cfg, "");
}
Expand Down
2 changes: 1 addition & 1 deletion zerocopy/zerocopy-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ exclude = [".*", "tests/enum_from_bytes.rs", "tests/ui-nightly/enum_from_bytes_u
# See #1792 for more context.
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(zerocopy_derive_union_into_bytes)',
'cfg(zerocopy_unstable_derive_on_error)',
'cfg(zerocopy_unstable_linux)',
] }

[lib]
Expand Down
18 changes: 11 additions & 7 deletions zerocopy/zerocopy-derive/src/derive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use crate::{
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
};

pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
match &ctx.ast.data {
pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
Ok(match &ctx.ast.data {
Data::Struct(strct) => {
ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
Expand All @@ -24,7 +24,7 @@ pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
Data::Union(unn) => {
ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
}
})
}

pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
Expand Down Expand Up @@ -95,16 +95,20 @@ pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStrea
match &ctx.ast.data {
Data::Struct(_) => {}
Data::Enum(_) | Data::Union(_) => {
return Err(Error::new(Span::call_site(), "can only be applied to structs"));
return ctx
.error_or_skip(Error::new(Span::call_site(), "can only be applied to structs"));
}
};

if repr.get_packed().is_some() {
return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute"));
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must not have #[repr(packed)] attribute",
));
}

if !(repr.is_c() || repr.is_transparent()) {
return Err(Error::new(
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
));
Expand All @@ -114,7 +118,7 @@ pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStrea
let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
trailing_field
} else {
return Err(Error::new(Span::call_site(), "must at least one field"));
return ctx.error_or_skip(Error::new(Span::call_site(), "must at least one field"));
};

let zerocopy_crate = &ctx.zerocopy_crate;
Expand Down
34 changes: 34 additions & 0 deletions zerocopy/zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,40 @@ derive!(ByteHash => derive_hash => crate::derive::derive_hash);
derive!(ByteEq => derive_eq => crate::derive::derive_eq);
derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);

#[cfg_attr(zerocopy_unstable_linux, doc(hidden))]
#[proc_macro_derive(most_traits, attributes(zerocopy))]
pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(ts as DeriveInput);
let ctx = match Ctx::try_from_derive_input(ast) {
Ok(ctx) => ctx,
Err(e) => return e.into_compile_error().into(),
}
.skip_on_error();

// top-level traits for which to attempt a derive
let derives: [(fn(&Ctx, Trait) -> _, _); 6] = [
(crate::derive::known_layout::derive, Trait::KnownLayout),
(crate::derive::derive_immutable, Trait::Immutable),
(crate::derive::from_bytes::derive_from_bytes, Trait::FromBytes),
(crate::derive::into_bytes::derive_into_bytes, Trait::IntoBytes),
(crate::derive::derive_split_at, Trait::SplitAt),
(crate::derive::unaligned::derive_unaligned, Trait::Unaligned),
];

let mut tokens = proc_macro2::TokenStream::new();
for (derive, t) in derives {
tokens.extend(derive(&ctx, t))
}

// We wrap in `const_block` as a backstop in case any derive fails
// to wrap its output in `const_block` (and thus fails to annotate)
// with the full set of `#[allow(...)]` attributes).
let ts = const_block([Some(tokens)]);
#[cfg(test)]
crate::util::testutil::check_hygiene(ts.clone());
ts.into()
}

/// Deprecated: prefer [`FromZeros`] instead.
#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
#[doc(hidden)]
Expand Down
32 changes: 25 additions & 7 deletions zerocopy/zerocopy-derive/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ impl Ctx {
}
}

pub(crate) fn skip_on_error(mut self) -> Self {
self.skip_on_error = true;
self
}

pub(crate) fn core_path(&self) -> TokenStream {
let zerocopy_crate = &self.zerocopy_crate;
quote!(#zerocopy_crate::util::macro_util::core_reexport)
Expand All @@ -104,20 +109,21 @@ impl Ctx {
pub(crate) fn cfg_compile_error(&self) -> TokenStream {
// By checking both during the compilation of the proc macro *and* in
// the generated code, we ensure that `--cfg
// zerocopy_unstable_derive_on_error` need only be passed *either* when
// zerocopy_unstable_linux` need only be passed *either* when
// compiling this crate *or* when compiling the user's crate. The former
// is preferable, but in some situations (such as when cross-compiling
// using `cargo build --target`), it doesn't get propagated to this
// crate's build by default.
if cfg!(zerocopy_unstable_derive_on_error) {
if cfg!(zerocopy_unstable_linux) {
quote!()
} else if let Some(span) = self.on_error_span {
let core = self.core_path();
let error_message = "`on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable";
let error_message =
"`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable";
quote::quote_spanned! {span=>
#[allow(unused_attributes, unexpected_cfgs)]
const _: () = {
#[cfg(not(zerocopy_unstable_derive_on_error))]
#[cfg(not(zerocopy_unstable_linux))]
#core::compile_error!(#error_message);
};
}
Expand Down Expand Up @@ -610,6 +616,20 @@ impl<'a> ImplBlockBuilder<'a> {
}
};

let zerocopy_bounds =
field_type_bounds
.into_iter()
.chain(padding_check_bound)
.chain(self_bounds)
.map(|bound| {
if self.ctx.skip_on_error {
parse_quote!(for<'zc> #bound)
} else {
bound.clone()
}
})
.collect::<Vec<_>>();

let bounds = self
.ctx
.ast
Expand All @@ -619,9 +639,7 @@ impl<'a> ImplBlockBuilder<'a> {
.map(|where_clause| where_clause.predicates.iter())
.into_iter()
.flatten()
.chain(field_type_bounds.iter())
.chain(padding_check_bound.iter())
.chain(self_bounds.iter());
.chain(zerocopy_bounds.iter());

// The parameters with trait bounds, but without type defaults.
let mut params: Vec<_> = self
Expand Down
128 changes: 110 additions & 18 deletions zerocopy/zerocopy-derive/tests/on_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,26 +144,118 @@ union BadIntoBytesUnionGeneric<T: imp::Copy> {

util_assert_not_impl_any!(BadIntoBytesUnionGeneric<u8>: imp::IntoBytes);

#[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
mod trivial_bounds {
use super::*;
#[derive(imp::FromBytes)]
#[zerocopy(on_error = "skip")]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(transparent)]
struct TrivialBounds(bool);

util_assert_not_impl_any!(TrivialBounds: imp::FromBytes);

#[derive(imp::IntoBytes)]
#[zerocopy(on_error = "skip")]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct BadIntoBytesStructPadding {
a: u8,
b: u16,
}

util_assert_not_impl_any!(BadIntoBytesStructPadding: imp::IntoBytes);

#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotFromBytes {
a: [bool],
}

util_assert_impl_all!(NotFromBytes:
imp::SplitAt,
imp::IntoBytes,
imp::KnownLayout,
imp::Unaligned,
imp::Immutable,
);
util_assert_not_impl_any!(NotFromBytes: imp::FromBytes);

#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotFromZeros {
a: [imp::core::num::NonZeroU8],
}

#[derive(imp::FromBytes)]
#[zerocopy(on_error = "skip")]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(transparent)]
struct TrivialBounds(bool);
util_assert_impl_all!(NotFromZeros:
imp::SplitAt,
imp::IntoBytes,
imp::KnownLayout,
imp::Unaligned,
imp::Immutable,
);
util_assert_not_impl_any!(NotFromZeros: imp::FromZeros);

util_assert_not_impl_any!(TrivialBounds: imp::FromBytes);
#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotUnaligned {
a: [u16],
}

#[derive(imp::IntoBytes)]
#[zerocopy(on_error = "skip")]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct BadIntoBytesStructPadding {
a: u8,
b: u16,
}
util_assert_impl_all!(NotUnaligned:
imp::FromBytes,
imp::IntoBytes,
imp::KnownLayout,
imp::Immutable,
imp::SplitAt,
);
util_assert_not_impl_any!(NotUnaligned: imp::Unaligned);

util_assert_not_impl_any!(BadIntoBytesStructPadding: imp::IntoBytes);
#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotIntoBytes {
a: [imp::core::mem::MaybeUninit<u8>],
}

util_assert_impl_all!(NotIntoBytes:
imp::FromBytes,
imp::KnownLayout,
imp::SplitAt,
imp::Unaligned,
imp::Immutable,
);
util_assert_not_impl_any!(NotIntoBytes: imp::IntoBytes);

#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotImmutable {
a: [imp::core::cell::UnsafeCell<u8>],
}

util_assert_impl_all!(NotImmutable:
imp::FromBytes,
imp::IntoBytes,
imp::KnownLayout,
imp::SplitAt,
imp::Unaligned,
);
util_assert_not_impl_any!(NotImmutable: imp::Immutable);

#[derive(imp::most_traits)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct NotSplit {
a: u8,
b: u8,
}

util_assert_impl_all!(NotSplit:
imp::FromBytes,
imp::IntoBytes,
imp::KnownLayout,
imp::Unaligned,
imp::Immutable,
);
util_assert_not_impl_any!(NotSplit: imp::SplitAt);
2 changes: 1 addition & 1 deletion zerocopy/zerocopy-derive/tests/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn ui() {
// present.
UiTestRunner::new()
.rustc_arg("--cfg=zerocopy_derive_union_into_bytes")
.rustc_arg("--cfg=zerocopy_unstable_derive_on_error")
.rustc_arg("--cfg=zerocopy_unstable_linux")
.rustc_arg("-Wwarnings") // To ensure .stderr files reflect typical user encounter
.run();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: `on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable
error: `on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable
--> $DIR/on_error.rs:13:10
|
13 | #[derive(FromBytes)]
Expand Down
Loading
Loading