Skip to content

Commit 6478ece

Browse files
committed
add Zeroable derive macro
1 parent 0fd0f21 commit 6478ece

7 files changed

Lines changed: 139 additions & 10 deletions

File tree

pinned-init-macro/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod pin_data;
22
mod pinned_drop;
3+
mod zeroable;
34

45
use proc_macro::TokenStream;
56

@@ -78,3 +79,22 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream {
7879
pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
7980
pinned_drop::pinned_drop(args, input)
8081
}
82+
83+
/// Derives the [`Zeroable`] trait for the given struct.
84+
///
85+
/// This can only be used for structs where every field implements the [`Zeroable`] trait.
86+
///
87+
/// # Examples
88+
///
89+
/// ```rust,ignore
90+
/// #[derive(Zeroable)]
91+
/// pub struct DriverData {
92+
/// id: i64,
93+
/// buf_ptr: *mut u8,
94+
/// len: usize,
95+
/// }
96+
/// ```
97+
#[proc_macro_derive(Zeroable)]
98+
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
99+
zeroable::derive(input.into()).into()
100+
}

pinned-init-macro/src/pin_data.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
use proc_macro2::{Group, Punct, Spacing, TokenStream, TokenTree};
44
use quote::quote;
55

6-
struct Generics {
7-
decl_generics: Vec<TokenTree>,
8-
impl_generics: Vec<TokenTree>,
9-
ty_generics: Vec<TokenTree>,
6+
pub(crate) struct Generics {
7+
pub(crate) decl_generics: Vec<TokenTree>,
8+
pub(crate) impl_generics: Vec<TokenTree>,
9+
pub(crate) ty_generics: Vec<TokenTree>,
1010
}
1111

1212
/// Parses the given `TokenStream` into `Generics` and the rest.
1313
///
1414
/// The generics are not present in the rest, but a where clause might remain.
15-
fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
15+
pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
1616
// The generics with bounds and default values.
1717
let mut decl_generics = vec![];
1818
// `impl_generics`, the declared generics with their bounds.

pinned-init-macro/src/zeroable.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use crate::pin_data::{parse_generics, Generics};
4+
use proc_macro2::{TokenStream, TokenTree};
5+
use quote::quote;
6+
7+
pub(crate) fn derive(input: TokenStream) -> TokenStream {
8+
let (
9+
Generics {
10+
impl_generics,
11+
decl_generics: _,
12+
ty_generics,
13+
},
14+
mut rest,
15+
) = parse_generics(input);
16+
// This should be the body of the struct `{...}`.
17+
let last = rest.pop();
18+
// Now we insert `Zeroable` as a bound for every generic parameter in `impl_generics`.
19+
let mut new_impl_generics = Vec::with_capacity(impl_generics.len());
20+
// Are we inside of a generic where we want to add `Zeroable`?
21+
let mut in_generic = !impl_generics.is_empty();
22+
// Have we already inserted `Zeroable`?
23+
let mut inserted = false;
24+
// Level of `<>` nestings.
25+
let mut nested = 0;
26+
for tt in impl_generics {
27+
match &tt {
28+
// If we find a `,`, then we have finished a generic/constant/lifetime parameter.
29+
TokenTree::Punct(p) if nested == 0 && p.as_char() == ',' => {
30+
if in_generic && !inserted {
31+
new_impl_generics.extend(quote! { : ::pinned_init::Zeroable });
32+
}
33+
in_generic = true;
34+
inserted = false;
35+
new_impl_generics.push(tt);
36+
}
37+
// If we find `'`, then we are entering a lifetime.
38+
TokenTree::Punct(p) if nested == 0 && p.as_char() == '\'' => {
39+
in_generic = false;
40+
new_impl_generics.push(tt);
41+
}
42+
TokenTree::Punct(p) if nested == 0 && p.as_char() == ':' => {
43+
new_impl_generics.push(tt);
44+
if in_generic {
45+
new_impl_generics.extend(quote! { ::pinned_init::Zeroable + });
46+
inserted = true;
47+
}
48+
}
49+
TokenTree::Punct(p) if p.as_char() == '<' => {
50+
nested += 1;
51+
new_impl_generics.push(tt);
52+
}
53+
TokenTree::Punct(p) if p.as_char() == '>' => {
54+
assert!(nested > 0);
55+
nested -= 1;
56+
new_impl_generics.push(tt);
57+
}
58+
_ => new_impl_generics.push(tt),
59+
}
60+
}
61+
assert_eq!(nested, 0);
62+
if in_generic && !inserted {
63+
new_impl_generics.extend(quote! { : ::pinned_init::Zeroable });
64+
}
65+
quote! {
66+
::pinned_init::__derive_zeroable!(
67+
parse_input:
68+
@sig(#(#rest)*),
69+
@impl_generics(#(#new_impl_generics)*),
70+
@ty_generics(#(#ty_generics)*),
71+
@body(#last),
72+
);
73+
}
74+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ pub mod __internal;
269269
#[doc(hidden)]
270270
pub mod macros;
271271

272-
pub use pinned_init_macro::{pin_data, pinned_drop};
272+
pub use pinned_init_macro::{pin_data, pinned_drop, Zeroable};
273273

274274
/// Initialize and pin a type directly on the stack.
275275
///

src/macros.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,3 +1362,38 @@ macro_rules! __init_internal {
13621362
);
13631363
};
13641364
}
1365+
1366+
#[doc(hidden)]
1367+
#[macro_export]
1368+
macro_rules! __derive_zeroable {
1369+
(parse_input:
1370+
@sig(
1371+
$(#[$($struct_attr:tt)*])*
1372+
$vis:vis struct $name:ident
1373+
$(where $($whr:tt)*)?
1374+
),
1375+
@impl_generics($($impl_generics:tt)*),
1376+
@ty_generics($($ty_generics:tt)*),
1377+
@body({
1378+
$(
1379+
$(#[$($field_attr:tt)*])*
1380+
$field:ident : $field_ty:ty
1381+
),* $(,)?
1382+
}),
1383+
) => {
1384+
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1385+
#[automatically_derived]
1386+
unsafe impl<$($impl_generics)*> $crate::Zeroable for $name<$($ty_generics)*>
1387+
where
1388+
$($($whr)*)?
1389+
{}
1390+
const _: () = {
1391+
fn assert_zeroable<T: ?::core::marker::Sized + $crate::Zeroable>() {}
1392+
fn ensure_zeroable<$($impl_generics)*>()
1393+
where $($($whr)*)?
1394+
{
1395+
$(assert_zeroable::<$field_ty>();)*
1396+
}
1397+
};
1398+
};
1399+
}

tests/ui/missing_pin_data.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ error[E0599]: no associated item named `__pin_data` found for struct `Foo` in th
1212
candidate #1: `HasPinData`
1313
= note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info)
1414
help: there is an associated function `__init_data` with a similar name
15-
--> $SRC_DIR/src/lib.rs:618:35
15+
--> $SRC_DIR/src/lib.rs:616:35
1616
|
17-
618| @has_data(HasPinData, __init_data),
17+
616| @has_data(HasPinData, __init_data),
1818
| ~~~~~~~~~~~
1919

2020
error: aborting due to 1 previous error

tests/ui/no_pin_data_but_pinned_drop.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ error[E0277]: the trait bound `Foo: HasPinData` is not satisfied
55
| ^^^ the trait `HasPinData` is not implemented for `Foo`
66
|
77
note: required by a bound in `PinnedDrop`
8-
--> $SRC_DIR/src/lib.rs:1321:30
8+
--> $SRC_DIR/src/lib.rs:1319:30
99
|
10-
1321 | pub unsafe trait PinnedDrop: __internal::HasPinData {
10+
1319 | pub unsafe trait PinnedDrop: __internal::HasPinData {
1111
| ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PinnedDrop`
1212

1313
error: aborting due to 1 previous error

0 commit comments

Comments
 (0)