868 lines
26 KiB
Rust
868 lines
26 KiB
Rust
//! Custom derive support for `zeroize`
|
|
|
|
#![crate_type = "proc-macro"]
|
|
#![forbid(unsafe_code)]
|
|
#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)]
|
|
|
|
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))]
|
|
pub fn 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);
|
|
|
|
let mut generics = input.generics.clone();
|
|
|
|
let extra_bounds = match attributes.bound {
|
|
Some(bounds) => bounds.0,
|
|
None => attributes
|
|
.auto_params
|
|
.iter()
|
|
.map(|type_param| -> WherePredicate {
|
|
parse_quote! {#type_param: Zeroize}
|
|
})
|
|
.collect(),
|
|
};
|
|
|
|
generics.make_where_clause().predicates.extend(extra_bounds);
|
|
|
|
let ty_name = &input.ident;
|
|
|
|
let (impl_gen, type_gen, where_) = generics.split_for_impl();
|
|
|
|
let drop_impl = if attributes.drop {
|
|
quote! {
|
|
#[doc(hidden)]
|
|
impl #impl_gen Drop for #ty_name #type_gen #where_ {
|
|
fn drop(&mut self) {
|
|
self.zeroize()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
quote! {}
|
|
};
|
|
|
|
let zeroizers = generate_fields(&input, quote! { zeroize });
|
|
let zeroize_impl = quote! {
|
|
impl #impl_gen ::zeroize::Zeroize for #ty_name #type_gen #where_ {
|
|
fn zeroize(&mut self) {
|
|
#zeroizers
|
|
}
|
|
}
|
|
};
|
|
|
|
quote! {
|
|
#zeroize_impl
|
|
#drop_impl
|
|
}
|
|
}
|
|
|
|
/// 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))]
|
|
pub fn derive_zeroize_on_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
derive_zeroize_on_drop_impl(syn::parse_macro_input!(input as DeriveInput)).into()
|
|
}
|
|
|
|
fn derive_zeroize_on_drop_impl(input: DeriveInput) -> TokenStream {
|
|
let zeroizers = generate_fields(&input, quote! { zeroize_or_on_drop });
|
|
|
|
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(&mut self) {
|
|
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 Parse for Bounds {
|
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
|
Ok(Self(Punctuated::parse_terminated(input)?))
|
|
}
|
|
}
|
|
|
|
struct BoundAccumulator<'a> {
|
|
generics: &'a syn::Generics,
|
|
params: Vec<Ident>,
|
|
}
|
|
|
|
impl<'ast> Visit<'ast> for BoundAccumulator<'ast> {
|
|
fn visit_path(&mut self, path: &'ast syn::Path) {
|
|
if path.segments.len() != 1 {
|
|
return;
|
|
}
|
|
|
|
if let Some(segment) = path.segments.first() {
|
|
for param in &self.generics.params {
|
|
if let 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 {
|
|
let mut result = Self::default();
|
|
let mut 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(&mut self, 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);
|
|
}
|
|
}
|
|
|
|
/// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`)
|
|
fn parse_meta(&mut self, meta: &Meta, variant: Option<&Variant>, binding: Option<&Field>) {
|
|
if meta.path().is_ident("drop") {
|
|
assert!(!self.drop, "duplicate #[zeroize] drop flags");
|
|
|
|
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) => (),
|
|
};
|
|
|
|
self.drop = true;
|
|
} else if meta.path().is_ident("bound") {
|
|
assert!(self.bound.is_none(), "duplicate #[zeroize] bound flags");
|
|
|
|
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) => {
|
|
if let Meta::NameValue(meta_name_value) = meta {
|
|
if let 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\")]."
|
|
))
|
|
}
|
|
}
|
|
} 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 field_ident(n: usize, field: &Field) -> Ident {
|
|
if let Some(ref name) = field.ident {
|
|
name.clone()
|
|
} else {
|
|
format_ident!("__zeroize_field_{}", n)
|
|
}
|
|
}
|
|
|
|
fn generate_fields(input: &DeriveInput, method: TokenStream) -> TokenStream {
|
|
let input_id = &input.ident;
|
|
let fields: Vec<_> = match input.data {
|
|
Data::Enum(ref enum_) => enum_
|
|
.variants
|
|
.iter()
|
|
.filter_map(|variant| {
|
|
if attr_skip(&variant.attrs) {
|
|
if variant.fields.iter().any(|field| attr_skip(&field.attrs)) {
|
|
panic!("duplicate #[zeroize] skip flags")
|
|
}
|
|
None
|
|
} else {
|
|
let variant_id = &variant.ident;
|
|
Some((quote! { #input_id :: #variant_id }, &variant.fields))
|
|
}
|
|
})
|
|
.collect(),
|
|
Data::Struct(ref struct_) => vec![(quote! { #input_id }, &struct_.fields)],
|
|
Data::Union(ref union_) => panic!("Cannot generate fields for untagged union {:?}", union_),
|
|
};
|
|
|
|
let arms = fields.into_iter().map(|(name, fields)| {
|
|
let method_field = fields.iter().enumerate().filter_map(|(n, field)| {
|
|
if attr_skip(&field.attrs) {
|
|
None
|
|
} else {
|
|
let name = field_ident(n, field);
|
|
Some(quote! { #name.#method() })
|
|
}
|
|
});
|
|
|
|
let field_bindings = fields
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(n, field)| field_ident(n, field));
|
|
|
|
let binding = match fields {
|
|
Fields::Named(_) => quote! {
|
|
#name { #(#field_bindings),* }
|
|
},
|
|
Fields::Unnamed(_) => quote! {
|
|
#name ( #(#field_bindings),* )
|
|
},
|
|
Fields::Unit => quote! {
|
|
#name
|
|
},
|
|
};
|
|
|
|
quote! {
|
|
#[allow(unused_variables)]
|
|
#binding => {
|
|
#(#method_field);*
|
|
}
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
match self {
|
|
#(#arms),*
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn attr_skip(attrs: &[Attribute]) -> bool {
|
|
let mut result = false;
|
|
for attr in attrs.iter().map(|attr| &attr.meta) {
|
|
if let Meta::List(list) = attr {
|
|
if list.path.is_ident(ZEROIZE_ATTR) {
|
|
for meta in list
|
|
.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
|
|
.unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", list, e))
|
|
{
|
|
if let Meta::Path(path) = meta {
|
|
if path.is_ident("skip") {
|
|
assert!(!result, "duplicate #[zeroize] skip flags");
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
fn impl_zeroize_on_drop(input: &DeriveInput) -> TokenStream {
|
|
let name = input.ident.clone();
|
|
let (impl_gen, type_gen, where_) = input.generics.split_for_impl();
|
|
quote! {
|
|
#[doc(hidden)]
|
|
impl #impl_gen ::zeroize::ZeroizeOnDrop for #name #type_gen #where_ {}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[track_caller]
|
|
fn test_derive(
|
|
f: impl Fn(DeriveInput) -> TokenStream,
|
|
input: TokenStream,
|
|
expected_output: TokenStream,
|
|
) {
|
|
let output = f(syn::parse2(input).unwrap());
|
|
assert_eq!(format!("{output}"), format!("{expected_output}"));
|
|
}
|
|
|
|
#[track_caller]
|
|
fn parse_zeroize_test(unparsed: &str) -> TokenStream {
|
|
derive_zeroize_impl(syn::parse_str(unparsed).expect("Failed to parse test input"))
|
|
}
|
|
|
|
#[test]
|
|
fn zeroize_without_drop() {
|
|
test_derive(
|
|
derive_zeroize_impl,
|
|
quote! {
|
|
struct Z {
|
|
a: String,
|
|
b: Vec<u8>,
|
|
c: [u8; 3],
|
|
}
|
|
},
|
|
quote! {
|
|
impl ::zeroize::Zeroize for Z {
|
|
fn zeroize(&mut self) {
|
|
match self {
|
|
#[allow(unused_variables)]
|
|
Z { a, b, c } => {
|
|
a.zeroize();
|
|
b.zeroize();
|
|
c.zeroize()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn zeroize_with_drop() {
|
|
test_derive(
|
|
derive_zeroize_impl,
|
|
quote! {
|
|
#[zeroize(drop)]
|
|
struct Z {
|
|
a: String,
|
|
b: Vec<u8>,
|
|
c: [u8; 3],
|
|
}
|
|
},
|
|
quote! {
|
|
impl ::zeroize::Zeroize for Z {
|
|
fn zeroize(&mut self) {
|
|
match self {
|
|
#[allow(unused_variables)]
|
|
Z { a, b, c } => {
|
|
a.zeroize();
|
|
b.zeroize();
|
|
c.zeroize()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
#[doc(hidden)]
|
|
impl Drop for Z {
|
|
fn drop(&mut self) {
|
|
self.zeroize()
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn zeroize_with_skip() {
|
|
test_derive(
|
|
derive_zeroize_impl,
|
|
quote! {
|
|
struct Z {
|
|
a: String,
|
|
b: Vec<u8>,
|
|
#[zeroize(skip)]
|
|
c: [u8; 3],
|
|
}
|
|
},
|
|
quote! {
|
|
impl ::zeroize::Zeroize for Z {
|
|
fn zeroize(&mut self) {
|
|
match self {
|
|
#[allow(unused_variables)]
|
|
Z { a, b, c } => {
|
|
a.zeroize();
|
|
b.zeroize()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn zeroize_with_bound() {
|
|
test_derive(
|
|
derive_zeroize_impl,
|
|
quote! {
|
|
#[zeroize(bound = "T: MyTrait")]
|
|
struct Z<T>(T);
|
|
},
|
|
quote! {
|
|
impl<T> ::zeroize::Zeroize for Z<T> where T: MyTrait {
|
|
fn zeroize(&mut self) {
|
|
match self {
|
|
#[allow(unused_variables)]
|
|
Z(__zeroize_field_0) => {
|
|
__zeroize_field_0.zeroize()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn zeroize_only_drop() {
|
|
test_derive(
|
|
derive_zeroize_on_drop_impl,
|
|
quote! {
|
|
struct Z {
|
|
a: String,
|
|
b: Vec<u8>,
|
|
c: [u8; 3],
|
|
}
|
|
},
|
|
quote! {
|
|
impl Drop for Z {
|
|
fn drop(&mut self) {
|
|
use ::zeroize::__internal::AssertZeroize;
|
|
use ::zeroize::__internal::AssertZeroizeOnDrop;
|
|
match self {
|
|
#[allow(unused_variables)]
|
|
Z { a, b, c } => {
|
|
a.zeroize_or_on_drop();
|
|
b.zeroize_or_on_drop();
|
|
c.zeroize_or_on_drop()
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
#[doc(hidden)]
|
|
impl ::zeroize::ZeroizeOnDrop for Z {}
|
|
},
|
|
)
|
|
}
|
|
|
|
#[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],
|
|
},
|
|
}
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "duplicate #[zeroize] bound flags")]
|
|
fn zeroize_duplicate_bound() {
|
|
parse_zeroize_test(stringify!(
|
|
#[zeroize(bound = "T: MyTrait")]
|
|
#[zeroize(bound = "")]
|
|
struct Z<T>(T);
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "duplicate #[zeroize] bound flags")]
|
|
fn zeroize_duplicate_bound_list() {
|
|
parse_zeroize_test(stringify!(
|
|
#[zeroize(bound = "T: MyTrait", bound = "")]
|
|
struct Z<T>(T);
|
|
));
|
|
}
|
|
|
|
#[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);
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "error parsing bounds: LitStr { token: \"T\" } (expected `:`)")]
|
|
fn zeroize_bound_no_where_predicate() {
|
|
parse_zeroize_test(stringify!(
|
|
#[zeroize(bound = "T")]
|
|
struct Z<T>(T);
|
|
));
|
|
}
|
|
}
|