use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
token::Comma,
visit::Visit,
Attribute, Data, DeriveInput, Expr, ExprLit, Field, Fields, Lit, Meta, Result, Variant,
WherePredicate,
};
/// Name of zeroize-related attributes const ZEROIZE_ATTR: &str = "zeroize";
/// Derive the `Zeroize` trait. /// /// Supports the following attributes: /// /// On the item level: /// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead /// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds /// inferred by zeroize-derive /// /// On the field level: /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` #[proc_macro_derive(Zeroize, attributes(zeroize))] pubfn derive_zeroize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_zeroize_impl(syn::parse_macro_input!(input as DeriveInput)).into()
}
fn derive_zeroize_impl(input: DeriveInput) -> TokenStream { let attributes = ZeroizeAttrs::parse(&input);
/// Derive the `ZeroizeOnDrop` trait. /// /// Supports the following attributes: /// /// On the field level: /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` #[proc_macro_derive(ZeroizeOnDrop, attributes(zeroize))] pubfn derive_zeroize_on_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_zeroize_on_drop_impl(syn::parse_macro_input!(input as DeriveInput)).into()
}
let (impl_gen, type_gen, where_) = input.generics.split_for_impl(); let name = input.ident.clone();
let drop_impl = quote! { impl#impl_gen Drop for#name#type_gen#where_ { fn drop(&mutself) { use ::zeroize::__internal::AssertZeroize; use ::zeroize::__internal::AssertZeroizeOnDrop; #zeroizers
}
}
}; let zeroize_on_drop_impl = impl_zeroize_on_drop(&input);
quote! { #drop_impl #zeroize_on_drop_impl
}
}
/// Custom derive attributes for `Zeroize` #[derive(Default)] struct ZeroizeAttrs { /// Derive a `Drop` impl which calls zeroize on this type
drop: bool, /// Custom bounds as defined by the user
bound: Option<Bounds>, /// Type parameters in use by fields
auto_params: Vec<Ident>,
}
/// Parsing helper for custom bounds struct Bounds(Punctuated<WherePredicate, Comma>);
impl<'ast> Visit<'ast> for BoundAccumulator<'ast> { fn visit_path(&mutself, path: &'ast syn::Path) { if path.segments.len() != 1 { return;
}
iflet Some(segment) = path.segments.first() { for param in &self.generics.params { iflet syn::GenericParam::Type(type_param) = param { if type_param.ident == segment.ident && !self.params.contains(&segment.ident) { self.params.push(type_param.ident.clone());
}
}
}
}
}
}
impl ZeroizeAttrs { /// Parse attributes from the incoming AST fn parse(input: &DeriveInput) -> Self { letmut result = Self::default(); letmut bound_accumulator = BoundAccumulator {
generics: &input.generics,
params: Vec::new(),
};
for attr in &input.attrs {
result.parse_attr(attr, None, None);
}
match &input.data {
syn::Data::Enum(enum_) => { for variant in &enum_.variants { for attr in &variant.attrs {
result.parse_attr(attr, Some(variant), None);
} for field in &variant.fields { for attr in &field.attrs {
result.parse_attr(attr, Some(variant), Some(field));
} if !attr_skip(&field.attrs) {
bound_accumulator.visit_type(&field.ty);
}
}
}
}
syn::Data::Struct(struct_) => { for field in &struct_.fields { for attr in &field.attrs {
result.parse_attr(attr, None, Some(field));
} if !attr_skip(&field.attrs) {
bound_accumulator.visit_type(&field.ty);
}
}
}
syn::Data::Union(union_) => panic!("Unsupported untagged union {:?}", union_),
}
result.auto_params = bound_accumulator.params;
result
}
/// Parse attribute and handle `#[zeroize(...)]` attributes fn parse_attr(&mutself, attr: &Attribute, variant: Option<&Variant>, binding: Option<&Field>) { let meta_list = match &attr.meta {
Meta::List(list) => list,
_ => return,
};
// Ignore any non-zeroize attributes if !meta_list.path.is_ident(ZEROIZE_ATTR) { return;
}
for meta in attr
.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
.unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e))
{ self.parse_meta(&meta, variant, binding);
}
}
match (variant, binding) {
(_variant, Some(_binding)) => { // structs don't have a variant prefix, and only structs have bindings outside of a variant let item_kind = match variant {
Some(_) => "enum",
None => "struct",
};
panic!(
concat!( "The #[zeroize(drop)] attribute is not allowed on {} fields. ", "Use it on the containing {} instead.",
),
item_kind, item_kind,
)
}
(Some(_variant), None) => panic!(concat!( "The #[zeroize(drop)] attribute is not allowed on enum variants. ", "Use it on the containing enum instead.",
)),
(None, None) => (),
};
match (variant, binding) {
(_variant, Some(_binding)) => { // structs don't have a variant prefix, and only structs have bindings outside of a variant let item_kind = match variant {
Some(_) => "enum",
None => "struct",
};
panic!(
concat!( "The #[zeroize(bound)] attribute is not allowed on {} fields. ", "Use it on the containing {} instead.",
),
item_kind, item_kind,
)
}
(Some(_variant), None) => panic!(concat!( "The #[zeroize(bound)] attribute is not allowed on enum variants. ", "Use it on the containing enum instead.",
)),
(None, None) => { iflet Meta::NameValue(meta_name_value) = meta { iflet Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = &meta_name_value.value
{ if lit.value().is_empty() { self.bound = Some(Bounds(Punctuated::new()));
} else { self.bound = Some(lit.parse().unwrap_or_else(|e| {
panic!("error parsing bounds: {:?} ({})", lit, e)
}));
}
return;
}
}
panic!(concat!( "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.", "E.g. #[zeroize(bound = \"T: MyTrait\")]."
))
}
}
} elseif meta.path().is_ident("skip") { if variant.is_none() && binding.is_none() {
panic!(concat!( "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. ", "Use it on a field or variant instead.",
))
}
} else {
panic!("unknown #[zeroize] attribute type: {:?}", meta.path());
}
}
}
#[test] #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")] fn zeroize_on_enum_variant() {
parse_zeroize_test(stringify!( enum Z { #[zeroize(drop)]
Variant,
}
));
}
#[test] #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")] fn zeroize_on_enum_second_variant() {
parse_zeroize_test(stringify!( enum Z {
Variant1, #[zeroize(drop)]
Variant2,
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
)] fn zeroize_skip_on_struct() {
parse_zeroize_test(stringify!( #[zeroize(skip)] struct Z {
a: String,
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
)] fn zeroize_skip_on_enum() {
parse_zeroize_test(stringify!( #[zeroize(skip)] enum Z {
Variant1,
Variant2,
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(bound)] attribute is not allowed on struct fields. Use it on the containing struct instead."
)] fn zeroize_bound_struct() {
parse_zeroize_test(stringify!( struct Z<T> { #[zeroize(bound = "T: MyTrait")]
a: T,
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(bound)] attribute is not allowed on enum variants. Use it on the containing enum instead."
)] fn zeroize_bound_enum() {
parse_zeroize_test(stringify!( enum Z<T> { #[zeroize(bound = "T: MyTrait")]
A(T),
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(bound)] attribute is not allowed on enum fields. Use it on the containing enum instead."
)] fn zeroize_bound_enum_variant_field() {
parse_zeroize_test(stringify!( enum Z<T> {
A { #[zeroize(bound = "T: MyTrait")]
a: T,
},
}
));
}
#[test] #[should_panic(
expected = "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.E.g. #[zeroize(bound = \"T: MyTrait\")]."
)] fn zeroize_bound_no_value() {
parse_zeroize_test(stringify!( #[zeroize(bound)] struct Z<T>(T);
));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.