1 //! Custom derive support for `zeroize`
2 
3 #![crate_type = "proc-macro"]
4 #![forbid(unsafe_code)]
5 #![warn(rust_2018_idioms, trivial_casts, unused_qualifications)]
6 extern crate proc_macro;
7 
8 use proc_macro2::{Ident, TokenStream};
9 use quote::{format_ident, quote};
10 use syn::{
11     parse::{Parse, ParseStream},
12     parse_quote,
13     punctuated::Punctuated,
14     token::Comma,
15     visit::Visit,
16     Attribute, Data, DeriveInput, Expr, ExprLit, Field, Fields, Lit, Meta, Result, Variant,
17     WherePredicate,
18 };
19 
20 /// Name of zeroize-related attributes
21 const ZEROIZE_ATTR: &str = "zeroize";
22 
23 /// Derive the `Zeroize` trait.
24 ///
25 /// Supports the following attributes:
26 ///
27 /// On the item level:
28 /// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead
29 /// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds
30 ///   inferred by zeroize-derive
31 ///
32 /// On the field level:
33 /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
34 #[proc_macro_derive(Zeroize, attributes(zeroize))]
derive_zeroize(input: proc_macro::TokenStream) -> proc_macro::TokenStream35 pub fn derive_zeroize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36     derive_zeroize_impl(syn::parse_macro_input!(input as DeriveInput)).into()
37 }
38 
derive_zeroize_impl(input: DeriveInput) -> TokenStream39 fn derive_zeroize_impl(input: DeriveInput) -> TokenStream {
40     let attributes = ZeroizeAttrs::parse(&input);
41 
42     let mut generics = input.generics.clone();
43 
44     let extra_bounds = match attributes.bound {
45         Some(bounds) => bounds.0,
46         None => attributes
47             .auto_params
48             .iter()
49             .map(|type_param| -> WherePredicate {
50                 parse_quote! {#type_param: Zeroize}
51             })
52             .collect(),
53     };
54 
55     generics.make_where_clause().predicates.extend(extra_bounds);
56 
57     let ty_name = &input.ident;
58 
59     let (impl_gen, type_gen, where_) = generics.split_for_impl();
60 
61     let drop_impl = if attributes.drop {
62         quote! {
63             #[doc(hidden)]
64             impl #impl_gen Drop for #ty_name #type_gen #where_ {
65                 fn drop(&mut self) {
66                     self.zeroize()
67                 }
68             }
69         }
70     } else {
71         quote! {}
72     };
73 
74     let zeroizers = generate_fields(&input, quote! { zeroize });
75     let zeroize_impl = quote! {
76         impl #impl_gen ::zeroize::Zeroize for #ty_name #type_gen #where_ {
77             fn zeroize(&mut self) {
78                 #zeroizers
79             }
80         }
81     };
82 
83     quote! {
84         #zeroize_impl
85         #drop_impl
86     }
87 }
88 
89 /// Derive the `ZeroizeOnDrop` trait.
90 ///
91 /// Supports the following attributes:
92 ///
93 /// On the field level:
94 /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
95 #[proc_macro_derive(ZeroizeOnDrop, attributes(zeroize))]
derive_zeroize_on_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream96 pub fn derive_zeroize_on_drop(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
97     derive_zeroize_on_drop_impl(syn::parse_macro_input!(input as DeriveInput)).into()
98 }
99 
derive_zeroize_on_drop_impl(input: DeriveInput) -> TokenStream100 fn derive_zeroize_on_drop_impl(input: DeriveInput) -> TokenStream {
101     let zeroizers = generate_fields(&input, quote! { zeroize_or_on_drop });
102 
103     let (impl_gen, type_gen, where_) = input.generics.split_for_impl();
104     let name = input.ident.clone();
105 
106     let drop_impl = quote! {
107         impl #impl_gen Drop for #name #type_gen #where_ {
108             fn drop(&mut self) {
109                 use ::zeroize::__internal::AssertZeroize;
110                 use ::zeroize::__internal::AssertZeroizeOnDrop;
111                 #zeroizers
112             }
113         }
114     };
115     let zeroize_on_drop_impl = impl_zeroize_on_drop(&input);
116 
117     quote! {
118         #drop_impl
119         #zeroize_on_drop_impl
120     }
121 }
122 
123 /// Custom derive attributes for `Zeroize`
124 #[derive(Default)]
125 struct ZeroizeAttrs {
126     /// Derive a `Drop` impl which calls zeroize on this type
127     drop: bool,
128     /// Custom bounds as defined by the user
129     bound: Option<Bounds>,
130     /// Type parameters in use by fields
131     auto_params: Vec<Ident>,
132 }
133 
134 /// Parsing helper for custom bounds
135 struct Bounds(Punctuated<WherePredicate, Comma>);
136 
137 impl Parse for Bounds {
parse(input: ParseStream<'_>) -> Result<Self>138     fn parse(input: ParseStream<'_>) -> Result<Self> {
139         Ok(Self(Punctuated::parse_terminated(input)?))
140     }
141 }
142 
143 struct BoundAccumulator<'a> {
144     generics: &'a syn::Generics,
145     params: Vec<Ident>,
146 }
147 
148 impl<'ast> Visit<'ast> for BoundAccumulator<'ast> {
visit_path(&mut self, path: &'ast syn::Path)149     fn visit_path(&mut self, path: &'ast syn::Path) {
150         if path.segments.len() != 1 {
151             return;
152         }
153 
154         if let Some(segment) = path.segments.first() {
155             for param in &self.generics.params {
156                 if let syn::GenericParam::Type(type_param) = param {
157                     if type_param.ident == segment.ident && !self.params.contains(&segment.ident) {
158                         self.params.push(type_param.ident.clone());
159                     }
160                 }
161             }
162         }
163     }
164 }
165 
166 impl ZeroizeAttrs {
167     /// Parse attributes from the incoming AST
parse(input: &DeriveInput) -> Self168     fn parse(input: &DeriveInput) -> Self {
169         let mut result = Self::default();
170         let mut bound_accumulator = BoundAccumulator {
171             generics: &input.generics,
172             params: Vec::new(),
173         };
174 
175         for attr in &input.attrs {
176             result.parse_attr(attr, None, None);
177         }
178 
179         match &input.data {
180             syn::Data::Enum(enum_) => {
181                 for variant in &enum_.variants {
182                     for attr in &variant.attrs {
183                         result.parse_attr(attr, Some(variant), None);
184                     }
185                     for field in &variant.fields {
186                         for attr in &field.attrs {
187                             result.parse_attr(attr, Some(variant), Some(field));
188                         }
189                         if !attr_skip(&field.attrs) {
190                             bound_accumulator.visit_type(&field.ty);
191                         }
192                     }
193                 }
194             }
195             syn::Data::Struct(struct_) => {
196                 for field in &struct_.fields {
197                     for attr in &field.attrs {
198                         result.parse_attr(attr, None, Some(field));
199                     }
200                     if !attr_skip(&field.attrs) {
201                         bound_accumulator.visit_type(&field.ty);
202                     }
203                 }
204             }
205             syn::Data::Union(union_) => panic!("Unsupported untagged union {:?}", union_),
206         }
207 
208         result.auto_params = bound_accumulator.params;
209 
210         result
211     }
212 
213     /// Parse attribute and handle `#[zeroize(...)]` attributes
parse_attr(&mut self, attr: &Attribute, variant: Option<&Variant>, binding: Option<&Field>)214     fn parse_attr(&mut self, attr: &Attribute, variant: Option<&Variant>, binding: Option<&Field>) {
215         let meta_list = match &attr.meta {
216             Meta::List(list) => list,
217             _ => return,
218         };
219 
220         // Ignore any non-zeroize attributes
221         if !meta_list.path.is_ident(ZEROIZE_ATTR) {
222             return;
223         }
224 
225         for meta in attr
226             .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
227             .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e))
228         {
229             self.parse_meta(&meta, variant, binding);
230         }
231     }
232 
233     /// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`)
parse_meta(&mut self, meta: &Meta, variant: Option<&Variant>, binding: Option<&Field>)234     fn parse_meta(&mut self, meta: &Meta, variant: Option<&Variant>, binding: Option<&Field>) {
235         if meta.path().is_ident("drop") {
236             assert!(!self.drop, "duplicate #[zeroize] drop flags");
237 
238             match (variant, binding) {
239                 (_variant, Some(_binding)) => {
240                     // structs don't have a variant prefix, and only structs have bindings outside of a variant
241                     let item_kind = match variant {
242                         Some(_) => "enum",
243                         None => "struct",
244                     };
245                     panic!(
246                         concat!(
247                             "The #[zeroize(drop)] attribute is not allowed on {} fields. ",
248                             "Use it on the containing {} instead.",
249                         ),
250                         item_kind, item_kind,
251                     )
252                 }
253                 (Some(_variant), None) => panic!(concat!(
254                     "The #[zeroize(drop)] attribute is not allowed on enum variants. ",
255                     "Use it on the containing enum instead.",
256                 )),
257                 (None, None) => (),
258             };
259 
260             self.drop = true;
261         } else if meta.path().is_ident("bound") {
262             assert!(self.bound.is_none(), "duplicate #[zeroize] bound flags");
263 
264             match (variant, binding) {
265                 (_variant, Some(_binding)) => {
266                     // structs don't have a variant prefix, and only structs have bindings outside of a variant
267                     let item_kind = match variant {
268                         Some(_) => "enum",
269                         None => "struct",
270                     };
271                     panic!(
272                         concat!(
273                             "The #[zeroize(bound)] attribute is not allowed on {} fields. ",
274                             "Use it on the containing {} instead.",
275                         ),
276                         item_kind, item_kind,
277                     )
278                 }
279                 (Some(_variant), None) => panic!(concat!(
280                     "The #[zeroize(bound)] attribute is not allowed on enum variants. ",
281                     "Use it on the containing enum instead.",
282                 )),
283                 (None, None) => {
284                     if let Meta::NameValue(meta_name_value) = meta {
285                         if let Expr::Lit(ExprLit {
286                             lit: Lit::Str(lit), ..
287                         }) = &meta_name_value.value
288                         {
289                             if lit.value().is_empty() {
290                                 self.bound = Some(Bounds(Punctuated::new()));
291                             } else {
292                                 self.bound = Some(lit.parse().unwrap_or_else(|e| {
293                                     panic!("error parsing bounds: {:?} ({})", lit, e)
294                                 }));
295                             }
296 
297                             return;
298                         }
299                     }
300 
301                     panic!(concat!(
302                         "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.",
303                         "E.g. #[zeroize(bound = \"T: MyTrait\")]."
304                     ))
305                 }
306             }
307         } else if meta.path().is_ident("skip") {
308             if variant.is_none() && binding.is_none() {
309                 panic!(concat!(
310                     "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. ",
311                     "Use it on a field or variant instead.",
312                 ))
313             }
314         } else {
315             panic!("unknown #[zeroize] attribute type: {:?}", meta.path());
316         }
317     }
318 }
319 
field_ident(n: usize, field: &Field) -> Ident320 fn field_ident(n: usize, field: &Field) -> Ident {
321     if let Some(ref name) = field.ident {
322         name.clone()
323     } else {
324         format_ident!("__zeroize_field_{}", n)
325     }
326 }
327 
generate_fields(input: &DeriveInput, method: TokenStream) -> TokenStream328 fn generate_fields(input: &DeriveInput, method: TokenStream) -> TokenStream {
329     let input_id = &input.ident;
330     let fields: Vec<_> = match input.data {
331         Data::Enum(ref enum_) => enum_
332             .variants
333             .iter()
334             .filter_map(|variant| {
335                 if attr_skip(&variant.attrs) {
336                     if variant.fields.iter().any(|field| attr_skip(&field.attrs)) {
337                         panic!("duplicate #[zeroize] skip flags")
338                     }
339                     None
340                 } else {
341                     let variant_id = &variant.ident;
342                     Some((quote! { #input_id :: #variant_id }, &variant.fields))
343                 }
344             })
345             .collect(),
346         Data::Struct(ref struct_) => vec![(quote! { #input_id }, &struct_.fields)],
347         Data::Union(ref union_) => panic!("Cannot generate fields for untagged union {:?}", union_),
348     };
349 
350     let arms = fields.into_iter().map(|(name, fields)| {
351         let method_field = fields.iter().enumerate().filter_map(|(n, field)| {
352             if attr_skip(&field.attrs) {
353                 None
354             } else {
355                 let name = field_ident(n, field);
356                 Some(quote! { #name.#method() })
357             }
358         });
359 
360         let field_bindings = fields
361             .iter()
362             .enumerate()
363             .map(|(n, field)| field_ident(n, field));
364 
365         let binding = match fields {
366             Fields::Named(_) => quote! {
367                 #name { #(#field_bindings),* }
368             },
369             Fields::Unnamed(_) => quote! {
370                 #name ( #(#field_bindings),* )
371             },
372             Fields::Unit => quote! {
373                 #name
374             },
375         };
376 
377         quote! {
378             #[allow(unused_variables)]
379             #binding => {
380                 #(#method_field);*
381             }
382         }
383     });
384 
385     quote! {
386         match self {
387             #(#arms),*
388             _ => {}
389         }
390     }
391 }
392 
attr_skip(attrs: &[Attribute]) -> bool393 fn attr_skip(attrs: &[Attribute]) -> bool {
394     let mut result = false;
395     for attr in attrs.iter().map(|attr| &attr.meta) {
396         if let Meta::List(list) = attr {
397             if list.path.is_ident(ZEROIZE_ATTR) {
398                 for meta in list
399                     .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
400                     .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", list, e))
401                 {
402                     if let Meta::Path(path) = meta {
403                         if path.is_ident("skip") {
404                             assert!(!result, "duplicate #[zeroize] skip flags");
405                             result = true;
406                         }
407                     }
408                 }
409             }
410         }
411     }
412     result
413 }
414 
impl_zeroize_on_drop(input: &DeriveInput) -> TokenStream415 fn impl_zeroize_on_drop(input: &DeriveInput) -> TokenStream {
416     let name = input.ident.clone();
417     let (impl_gen, type_gen, where_) = input.generics.split_for_impl();
418     quote! {
419         #[doc(hidden)]
420         impl #impl_gen ::zeroize::ZeroizeOnDrop for #name #type_gen #where_ {}
421     }
422 }
423 
424 #[cfg(test)]
425 mod tests {
426     use super::*;
427 
428     #[track_caller]
test_derive( f: impl Fn(DeriveInput) -> TokenStream, input: TokenStream, expected_output: TokenStream, )429     fn test_derive(
430         f: impl Fn(DeriveInput) -> TokenStream,
431         input: TokenStream,
432         expected_output: TokenStream,
433     ) {
434         let output = f(syn::parse2(input).unwrap());
435         assert_eq!(format!("{output}"), format!("{expected_output}"));
436     }
437 
438     #[track_caller]
parse_zeroize_test(unparsed: &str) -> TokenStream439     fn parse_zeroize_test(unparsed: &str) -> TokenStream {
440         derive_zeroize_impl(syn::parse_str(unparsed).expect("Failed to parse test input"))
441     }
442 
443     #[test]
zeroize_without_drop()444     fn zeroize_without_drop() {
445         test_derive(
446             derive_zeroize_impl,
447             quote! {
448                 struct Z {
449                     a: String,
450                     b: Vec<u8>,
451                     c: [u8; 3],
452                 }
453             },
454             quote! {
455                 impl ::zeroize::Zeroize for Z {
456                     fn zeroize(&mut self) {
457                         match self {
458                             #[allow(unused_variables)]
459                             Z { a, b, c } => {
460                                 a.zeroize();
461                                 b.zeroize();
462                                 c.zeroize()
463                             }
464                             _ => {}
465                         }
466                     }
467                 }
468             },
469         )
470     }
471 
472     #[test]
zeroize_with_drop()473     fn zeroize_with_drop() {
474         test_derive(
475             derive_zeroize_impl,
476             quote! {
477                 #[zeroize(drop)]
478                 struct Z {
479                     a: String,
480                     b: Vec<u8>,
481                     c: [u8; 3],
482                 }
483             },
484             quote! {
485                 impl ::zeroize::Zeroize for Z {
486                     fn zeroize(&mut self) {
487                         match self {
488                             #[allow(unused_variables)]
489                             Z { a, b, c } => {
490                                 a.zeroize();
491                                 b.zeroize();
492                                 c.zeroize()
493                             }
494                             _ => {}
495                         }
496                     }
497                 }
498                 #[doc(hidden)]
499                 impl Drop for Z {
500                     fn drop(&mut self) {
501                         self.zeroize()
502                     }
503                 }
504             },
505         )
506     }
507 
508     #[test]
zeroize_with_skip()509     fn zeroize_with_skip() {
510         test_derive(
511             derive_zeroize_impl,
512             quote! {
513                 struct Z {
514                     a: String,
515                     b: Vec<u8>,
516                     #[zeroize(skip)]
517                     c: [u8; 3],
518                 }
519             },
520             quote! {
521                 impl ::zeroize::Zeroize for Z {
522                     fn zeroize(&mut self) {
523                         match self {
524                             #[allow(unused_variables)]
525                             Z { a, b, c } => {
526                                 a.zeroize();
527                                 b.zeroize()
528                             }
529                             _ => {}
530                         }
531                     }
532                 }
533             },
534         )
535     }
536 
537     #[test]
zeroize_with_bound()538     fn zeroize_with_bound() {
539         test_derive(
540             derive_zeroize_impl,
541             quote! {
542                 #[zeroize(bound = "T: MyTrait")]
543                 struct Z<T>(T);
544             },
545             quote! {
546                 impl<T> ::zeroize::Zeroize for Z<T> where T: MyTrait {
547                     fn zeroize(&mut self) {
548                         match self {
549                             #[allow(unused_variables)]
550                             Z(__zeroize_field_0) => {
551                                 __zeroize_field_0.zeroize()
552                             }
553                             _ => {}
554                         }
555                     }
556                 }
557             },
558         )
559     }
560 
561     #[test]
zeroize_only_drop()562     fn zeroize_only_drop() {
563         test_derive(
564             derive_zeroize_on_drop_impl,
565             quote! {
566                 struct Z {
567                     a: String,
568                     b: Vec<u8>,
569                     c: [u8; 3],
570                 }
571             },
572             quote! {
573                 impl Drop for Z {
574                     fn drop(&mut self) {
575                         use ::zeroize::__internal::AssertZeroize;
576                         use ::zeroize::__internal::AssertZeroizeOnDrop;
577                         match self {
578                             #[allow(unused_variables)]
579                             Z { a, b, c } => {
580                                 a.zeroize_or_on_drop();
581                                 b.zeroize_or_on_drop();
582                                 c.zeroize_or_on_drop()
583                             }
584                             _ => {}
585                         }
586                     }
587                 }
588                 #[doc(hidden)]
589                 impl ::zeroize::ZeroizeOnDrop for Z {}
590             },
591         )
592     }
593 
594     #[test]
zeroize_on_struct()595     fn zeroize_on_struct() {
596         parse_zeroize_test(stringify!(
597             #[zeroize(drop)]
598             struct Z {
599                 a: String,
600                 b: Vec<u8>,
601                 c: [u8; 3],
602             }
603         ));
604     }
605 
606     #[test]
zeroize_on_enum()607     fn zeroize_on_enum() {
608         parse_zeroize_test(stringify!(
609             #[zeroize(drop)]
610             enum Z {
611                 Variant1 { a: String, b: Vec<u8>, c: [u8; 3] },
612             }
613         ));
614     }
615 
616     #[test]
617     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_struct_field()618     fn zeroize_on_struct_field() {
619         parse_zeroize_test(stringify!(
620             struct Z {
621                 #[zeroize(drop)]
622                 a: String,
623                 b: Vec<u8>,
624                 c: [u8; 3],
625             }
626         ));
627     }
628 
629     #[test]
630     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_tuple_struct_field()631     fn zeroize_on_tuple_struct_field() {
632         parse_zeroize_test(stringify!(
633             struct Z(#[zeroize(drop)] String);
634         ));
635     }
636 
637     #[test]
638     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_second_field()639     fn zeroize_on_second_field() {
640         parse_zeroize_test(stringify!(
641             struct Z {
642                 a: String,
643                 #[zeroize(drop)]
644                 b: Vec<u8>,
645                 c: [u8; 3],
646             }
647         ));
648     }
649 
650     #[test]
651     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_tuple_enum_variant_field()652     fn zeroize_on_tuple_enum_variant_field() {
653         parse_zeroize_test(stringify!(
654             enum Z {
655                 Variant(#[zeroize(drop)] String),
656             }
657         ));
658     }
659 
660     #[test]
661     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_enum_variant_field()662     fn zeroize_on_enum_variant_field() {
663         parse_zeroize_test(stringify!(
664             enum Z {
665                 Variant {
666                     #[zeroize(drop)]
667                     a: String,
668                     b: Vec<u8>,
669                     c: [u8; 3],
670                 },
671             }
672         ));
673     }
674 
675     #[test]
676     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_enum_second_variant_field()677     fn zeroize_on_enum_second_variant_field() {
678         parse_zeroize_test(stringify!(
679             enum Z {
680                 Variant1 {
681                     a: String,
682                     b: Vec<u8>,
683                     c: [u8; 3],
684                 },
685                 Variant2 {
686                     #[zeroize(drop)]
687                     a: String,
688                     b: Vec<u8>,
689                     c: [u8; 3],
690                 },
691             }
692         ));
693     }
694 
695     #[test]
696     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
zeroize_on_enum_variant()697     fn zeroize_on_enum_variant() {
698         parse_zeroize_test(stringify!(
699             enum Z {
700                 #[zeroize(drop)]
701                 Variant,
702             }
703         ));
704     }
705 
706     #[test]
707     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
zeroize_on_enum_second_variant()708     fn zeroize_on_enum_second_variant() {
709         parse_zeroize_test(stringify!(
710             enum Z {
711                 Variant1,
712                 #[zeroize(drop)]
713                 Variant2,
714             }
715         ));
716     }
717 
718     #[test]
719     #[should_panic(
720         expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
721     )]
zeroize_skip_on_struct()722     fn zeroize_skip_on_struct() {
723         parse_zeroize_test(stringify!(
724             #[zeroize(skip)]
725             struct Z {
726                 a: String,
727                 b: Vec<u8>,
728                 c: [u8; 3],
729             }
730         ));
731     }
732 
733     #[test]
734     #[should_panic(
735         expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
736     )]
zeroize_skip_on_enum()737     fn zeroize_skip_on_enum() {
738         parse_zeroize_test(stringify!(
739             #[zeroize(skip)]
740             enum Z {
741                 Variant1,
742                 Variant2,
743             }
744         ));
745     }
746 
747     #[test]
748     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip()749     fn zeroize_duplicate_skip() {
750         parse_zeroize_test(stringify!(
751             struct Z {
752                 a: String,
753                 #[zeroize(skip)]
754                 #[zeroize(skip)]
755                 b: Vec<u8>,
756                 c: [u8; 3],
757             }
758         ));
759     }
760 
761     #[test]
762     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip_list()763     fn zeroize_duplicate_skip_list() {
764         parse_zeroize_test(stringify!(
765             struct Z {
766                 a: String,
767                 #[zeroize(skip, skip)]
768                 b: Vec<u8>,
769                 c: [u8; 3],
770             }
771         ));
772     }
773 
774     #[test]
775     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip_enum()776     fn zeroize_duplicate_skip_enum() {
777         parse_zeroize_test(stringify!(
778             enum Z {
779                 #[zeroize(skip)]
780                 Variant {
781                     a: String,
782                     #[zeroize(skip)]
783                     b: Vec<u8>,
784                     c: [u8; 3],
785                 },
786             }
787         ));
788     }
789 
790     #[test]
791     #[should_panic(expected = "duplicate #[zeroize] bound flags")]
zeroize_duplicate_bound()792     fn zeroize_duplicate_bound() {
793         parse_zeroize_test(stringify!(
794             #[zeroize(bound = "T: MyTrait")]
795             #[zeroize(bound = "")]
796             struct Z<T>(T);
797         ));
798     }
799 
800     #[test]
801     #[should_panic(expected = "duplicate #[zeroize] bound flags")]
zeroize_duplicate_bound_list()802     fn zeroize_duplicate_bound_list() {
803         parse_zeroize_test(stringify!(
804             #[zeroize(bound = "T: MyTrait", bound = "")]
805             struct Z<T>(T);
806         ));
807     }
808 
809     #[test]
810     #[should_panic(
811         expected = "The #[zeroize(bound)] attribute is not allowed on struct fields. Use it on the containing struct instead."
812     )]
zeroize_bound_struct()813     fn zeroize_bound_struct() {
814         parse_zeroize_test(stringify!(
815             struct Z<T> {
816                 #[zeroize(bound = "T: MyTrait")]
817                 a: T,
818             }
819         ));
820     }
821 
822     #[test]
823     #[should_panic(
824         expected = "The #[zeroize(bound)] attribute is not allowed on enum variants. Use it on the containing enum instead."
825     )]
zeroize_bound_enum()826     fn zeroize_bound_enum() {
827         parse_zeroize_test(stringify!(
828             enum Z<T> {
829                 #[zeroize(bound = "T: MyTrait")]
830                 A(T),
831             }
832         ));
833     }
834 
835     #[test]
836     #[should_panic(
837         expected = "The #[zeroize(bound)] attribute is not allowed on enum fields. Use it on the containing enum instead."
838     )]
zeroize_bound_enum_variant_field()839     fn zeroize_bound_enum_variant_field() {
840         parse_zeroize_test(stringify!(
841             enum Z<T> {
842                 A {
843                     #[zeroize(bound = "T: MyTrait")]
844                     a: T,
845                 },
846             }
847         ));
848     }
849 
850     #[test]
851     #[should_panic(
852         expected = "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.E.g. #[zeroize(bound = \"T: MyTrait\")]."
853     )]
zeroize_bound_no_value()854     fn zeroize_bound_no_value() {
855         parse_zeroize_test(stringify!(
856             #[zeroize(bound)]
857             struct Z<T>(T);
858         ));
859     }
860 
861     #[test]
862     #[should_panic(expected = "error parsing bounds: LitStr { token: \"T\" } (expected `:`)")]
zeroize_bound_no_where_predicate()863     fn zeroize_bound_no_where_predicate() {
864         parse_zeroize_test(stringify!(
865             #[zeroize(bound = "T")]
866             struct Z<T>(T);
867         ));
868     }
869 }
870