#![crate_type = "proc-macro"]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)]
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, Meta, NestedMeta};
use synstructure::{decl_derive, BindStyle, BindingInfo, VariantInfo};
decl_derive!(
[Zeroize, attributes(zeroize)] =>
derive_zeroize
);
const ZEROIZE_ATTR: &str = "zeroize";
fn derive_zeroize(s: synstructure::Structure<'_>) -> TokenStream {
let attributes = ZeroizeAttrs::parse(&s);
if attributes.drop {
derive_zeroize_with_drop(s)
} else {
derive_zeroize_without_drop(s)
}
}
#[derive(Default)]
struct ZeroizeAttrs {
drop: bool,
}
impl ZeroizeAttrs {
fn parse(s: &synstructure::Structure<'_>) -> Self {
let mut result = Self::default();
for attr in s.ast().attrs.iter() {
result.parse_attr(attr, None, None);
}
for v in s.variants().iter() {
if v.prefix.is_some() {
for attr in v.ast().attrs.iter() {
result.parse_attr(attr, Some(v), None);
}
}
for binding in v.bindings().iter() {
for attr in binding.ast().attrs.iter() {
result.parse_attr(attr, Some(v), Some(binding));
}
}
}
result
}
fn parse_attr(
&mut self,
attr: &Attribute,
variant: Option<&VariantInfo<'_>>,
binding: Option<&BindingInfo<'_>>,
) {
let meta_list = match attr
.parse_meta()
.unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e))
{
Meta::List(list) => list,
_ => return,
};
if !meta_list.path.is_ident(ZEROIZE_ATTR) {
return;
}
for nested_meta in &meta_list.nested {
if let NestedMeta::Meta(meta) = nested_meta {
self.parse_meta(meta, variant, binding);
} else {
panic!("malformed #[zeroize] attribute: {:?}", nested_meta);
}
}
}
fn parse_meta(
&mut self,
meta: &Meta,
variant: Option<&VariantInfo<'_>>,
binding: Option<&BindingInfo<'_>>,
) {
if meta.path().is_ident("drop") {
assert!(!self.drop, "duplicate #[zeroize] drop flags");
match (variant, binding) {
(_variant, Some(_binding)) => {
let item_kind = match variant.and_then(|variant| variant.prefix) {
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) => (),
};
self.drop = true;
} else if 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());
}
}
}
fn filter_skip(attrs: &[Attribute], start: bool) -> bool {
let mut result = start;
for attr in attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
if let Meta::List(list) = attr {
if list.path.is_ident(ZEROIZE_ATTR) {
for nested in list.nested {
if let NestedMeta::Meta(Meta::Path(path)) = nested {
if path.is_ident("skip") {
assert!(result, "duplicate #[zeroize] skip flags");
result = false;
}
}
}
}
}
}
result
}
fn derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream {
s.bind_with(|_| BindStyle::RefMut);
let zeroizers = s
.filter_variants(|vi| {
let result = filter_skip(vi.ast().attrs, true);
for field in vi.ast().fields {
filter_skip(&field.attrs, result);
}
result
})
.filter(|bi| filter_skip(&bi.ast().attrs, true))
.each(|bi| quote! { #bi.zeroize(); });
s.bound_impl(
quote!(zeroize::Zeroize),
quote! {
fn zeroize(&mut self) {
match self {
#zeroizers
}
}
},
)
}
fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream {
let drop_impl = s.gen_impl(quote! {
gen impl Drop for @Self {
fn drop(&mut self) {
self.zeroize();
}
}
});
let zeroize_impl = derive_zeroize_without_drop(s);
quote! {
#zeroize_impl
#[doc(hidden)]
#drop_impl
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_str;
use synstructure::{test_derive, Structure};
#[test]
fn zeroize_without_drop() {
test_derive! {
derive_zeroize_without_drop {
struct Z {
a: String,
b: Vec<u8>,
c: [u8; 3],
}
}
expands to {
#[allow(non_upper_case_globals)]
#[doc(hidden)]
const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
extern crate zeroize;
impl zeroize::Zeroize for Z {
fn zeroize(&mut self) {
match self {
Z {
a: ref mut __binding_0,
b: ref mut __binding_1,
c: ref mut __binding_2,
} => {
{ __binding_0.zeroize(); }
{ __binding_1.zeroize(); }
{ __binding_2.zeroize(); }
}
}
}
}
};
}
no_build }
}
#[test]
fn zeroize_with_drop() {
test_derive! {
derive_zeroize_with_drop {
struct Z {
a: String,
b: Vec<u8>,
c: [u8; 3],
}
}
expands to {
#[allow(non_upper_case_globals)]
#[doc(hidden)]
const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
extern crate zeroize;
impl zeroize::Zeroize for Z {
fn zeroize(&mut self) {
match self {
Z {
a: ref mut __binding_0,
b: ref mut __binding_1,
c: ref mut __binding_2,
} => {
{ __binding_0.zeroize(); }
{ __binding_1.zeroize(); }
{ __binding_2.zeroize(); }
}
}
}
}
};
#[doc(hidden)]
#[allow(non_upper_case_globals)]
const _DERIVE_Drop_FOR_Z: () = {
impl Drop for Z {
fn drop(&mut self) {
self.zeroize();
}
}
};
}
no_build }
}
#[test]
fn zeroize_with_skip() {
test_derive! {
derive_zeroize_without_drop {
struct Z {
a: String,
b: Vec<u8>,
#[zeroize(skip)]
c: [u8; 3],
}
}
expands to {
#[allow(non_upper_case_globals)]
#[doc(hidden)]
const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
extern crate zeroize;
impl zeroize::Zeroize for Z {
fn zeroize(&mut self) {
match self {
Z {
a: ref mut __binding_0,
b: ref mut __binding_1,
..
} => {
{ __binding_0.zeroize(); }
{ __binding_1.zeroize(); }
}
}
}
}
};
}
no_build }
}
#[test]
fn zeroize_on_struct() {
parse_zeroize_test(stringify!(
#[zeroize(drop)]
struct Z {
a: String,
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test]
fn zeroize_on_enum() {
parse_zeroize_test(stringify!(
#[zeroize(drop)]
enum Z {
Variant1 { a: String, b: Vec<u8>, c: [u8; 3] },
}
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
fn zeroize_on_struct_field() {
parse_zeroize_test(stringify!(
struct Z {
#[zeroize(drop)]
a: String,
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
fn zeroize_on_tuple_struct_field() {
parse_zeroize_test(stringify!(
struct Z(#[zeroize(drop)] String);
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
fn zeroize_on_second_field() {
parse_zeroize_test(stringify!(
struct Z {
a: String,
#[zeroize(drop)]
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
fn zeroize_on_tuple_enum_variant_field() {
parse_zeroize_test(stringify!(
enum Z {
Variant(#[zeroize(drop)] String),
}
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
fn zeroize_on_enum_variant_field() {
parse_zeroize_test(stringify!(
enum Z {
Variant {
#[zeroize(drop)]
a: String,
b: Vec<u8>,
c: [u8; 3],
},
}
));
}
#[test]
#[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
fn zeroize_on_enum_second_variant_field() {
parse_zeroize_test(stringify!(
enum Z {
Variant1 {
a: String,
b: Vec<u8>,
c: [u8; 3],
},
Variant2 {
#[zeroize(drop)]
a: String,
b: Vec<u8>,
c: [u8; 3],
},
}
));
}
#[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 = "duplicate #[zeroize] skip flags")]
fn zeroize_duplicate_skip() {
parse_zeroize_test(stringify!(
struct Z {
a: String,
#[zeroize(skip)]
#[zeroize(skip)]
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test]
#[should_panic(expected = "duplicate #[zeroize] skip flags")]
fn zeroize_duplicate_skip_list() {
parse_zeroize_test(stringify!(
struct Z {
a: String,
#[zeroize(skip, skip)]
b: Vec<u8>,
c: [u8; 3],
}
));
}
#[test]
#[should_panic(expected = "duplicate #[zeroize] skip flags")]
fn zeroize_duplicate_skip_enum() {
parse_zeroize_test(stringify!(
enum Z {
#[zeroize(skip)]
Variant {
a: String,
#[zeroize(skip)]
b: Vec<u8>,
c: [u8; 3],
},
}
));
}
fn parse_zeroize_test(unparsed: &str) -> TokenStream {
derive_zeroize(Structure::new(
&parse_str(unparsed).expect("Failed to parse test input"),
))
}
}