• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
7 use proc_macro2::TokenStream;
8 use quote::quote;
9 use syn::{
10     parse::{Parse, ParseStream},
11     punctuated::Punctuated,
12     token::Comma,
13     Attribute, Lit, Meta, NestedMeta, Result, WherePredicate,
14 };
15 use synstructure::{decl_derive, AddBounds, BindStyle, BindingInfo, VariantInfo};
16 
17 decl_derive!(
18     [Zeroize, attributes(zeroize)] =>
19 
20     /// Derive the `Zeroize` trait.
21     ///
22     /// Supports the following attributes:
23     ///
24     /// On the item level:
25     /// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead
26     /// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds
27     ///   inferred by zeroize-derive
28     ///
29     /// On the field level:
30     /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
31     derive_zeroize
32 );
33 
34 decl_derive!(
35     [ZeroizeOnDrop, attributes(zeroize)] =>
36 
37     /// Derive the `ZeroizeOnDrop` trait.
38     ///
39     /// Supports the following attributes:
40     ///
41     /// On the field level:
42     /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()`
43     derive_zeroize_on_drop
44 );
45 
46 /// Name of zeroize-related attributes
47 const ZEROIZE_ATTR: &str = "zeroize";
48 
49 /// Custom derive for `Zeroize`
derive_zeroize(mut s: synstructure::Structure<'_>) -> TokenStream50 fn derive_zeroize(mut s: synstructure::Structure<'_>) -> TokenStream {
51     let attributes = ZeroizeAttrs::parse(&s);
52 
53     if let Some(bounds) = attributes.bound {
54         s.add_bounds(AddBounds::None);
55 
56         for bound in bounds.0 {
57             s.add_where_predicate(bound);
58         }
59     }
60 
61     // NOTE: These are split into named functions to simplify testing with
62     // synstructure's `test_derive!` macro.
63     if attributes.drop {
64         derive_zeroize_with_drop(s)
65     } else {
66         derive_zeroize_without_drop(s)
67     }
68 }
69 
70 /// Custom derive for `ZeroizeOnDrop`
derive_zeroize_on_drop(mut s: synstructure::Structure<'_>) -> TokenStream71 fn derive_zeroize_on_drop(mut s: synstructure::Structure<'_>) -> TokenStream {
72     let zeroizers = generate_fields(&mut s, quote! { zeroize_or_on_drop });
73 
74     let drop_impl = s.add_bounds(AddBounds::None).gen_impl(quote! {
75         gen impl Drop for @Self {
76             fn drop(&mut self) {
77                 use zeroize::__internal::AssertZeroize;
78                 use zeroize::__internal::AssertZeroizeOnDrop;
79                 match self {
80                     #zeroizers
81                 }
82             }
83         }
84     });
85 
86     let zeroize_on_drop_impl = impl_zeroize_on_drop(&s);
87 
88     quote! {
89         #drop_impl
90 
91         #zeroize_on_drop_impl
92     }
93 }
94 
95 /// Custom derive attributes for `Zeroize`
96 #[derive(Default)]
97 struct ZeroizeAttrs {
98     /// Derive a `Drop` impl which calls zeroize on this type
99     drop: bool,
100     /// Custom bounds as defined by the user
101     bound: Option<Bounds>,
102 }
103 
104 /// Parsing helper for custom bounds
105 struct Bounds(Punctuated<WherePredicate, Comma>);
106 
107 impl Parse for Bounds {
parse(input: ParseStream<'_>) -> Result<Self>108     fn parse(input: ParseStream<'_>) -> Result<Self> {
109         Ok(Self(Punctuated::parse_terminated(input)?))
110     }
111 }
112 
113 impl ZeroizeAttrs {
114     /// Parse attributes from the incoming AST
parse(s: &synstructure::Structure<'_>) -> Self115     fn parse(s: &synstructure::Structure<'_>) -> Self {
116         let mut result = Self::default();
117 
118         for attr in s.ast().attrs.iter() {
119             result.parse_attr(attr, None, None);
120         }
121         for v in s.variants().iter() {
122             // only process actual enum variants here, as we don't want to process struct attributes twice
123             if v.prefix.is_some() {
124                 for attr in v.ast().attrs.iter() {
125                     result.parse_attr(attr, Some(v), None);
126                 }
127             }
128             for binding in v.bindings().iter() {
129                 for attr in binding.ast().attrs.iter() {
130                     result.parse_attr(attr, Some(v), Some(binding));
131                 }
132             }
133         }
134 
135         result
136     }
137 
138     /// Parse attribute and handle `#[zeroize(...)]` attributes
parse_attr( &mut self, attr: &Attribute, variant: Option<&VariantInfo<'_>>, binding: Option<&BindingInfo<'_>>, )139     fn parse_attr(
140         &mut self,
141         attr: &Attribute,
142         variant: Option<&VariantInfo<'_>>,
143         binding: Option<&BindingInfo<'_>>,
144     ) {
145         let meta_list = match attr
146             .parse_meta()
147             .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e))
148         {
149             Meta::List(list) => list,
150             _ => return,
151         };
152 
153         // Ignore any non-zeroize attributes
154         if !meta_list.path.is_ident(ZEROIZE_ATTR) {
155             return;
156         }
157 
158         for nested_meta in &meta_list.nested {
159             if let NestedMeta::Meta(meta) = nested_meta {
160                 self.parse_meta(meta, variant, binding);
161             } else {
162                 panic!("malformed #[zeroize] attribute: {:?}", nested_meta);
163             }
164         }
165     }
166 
167     /// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`)
parse_meta( &mut self, meta: &Meta, variant: Option<&VariantInfo<'_>>, binding: Option<&BindingInfo<'_>>, )168     fn parse_meta(
169         &mut self,
170         meta: &Meta,
171         variant: Option<&VariantInfo<'_>>,
172         binding: Option<&BindingInfo<'_>>,
173     ) {
174         if meta.path().is_ident("drop") {
175             assert!(!self.drop, "duplicate #[zeroize] drop flags");
176 
177             match (variant, binding) {
178                 (_variant, Some(_binding)) => {
179                     // structs don't have a variant prefix, and only structs have bindings outside of a variant
180                     let item_kind = match variant.and_then(|variant| variant.prefix) {
181                         Some(_) => "enum",
182                         None => "struct",
183                     };
184                     panic!(
185                         concat!(
186                             "The #[zeroize(drop)] attribute is not allowed on {} fields. ",
187                             "Use it on the containing {} instead.",
188                         ),
189                         item_kind, item_kind,
190                     )
191                 }
192                 (Some(_variant), None) => panic!(concat!(
193                     "The #[zeroize(drop)] attribute is not allowed on enum variants. ",
194                     "Use it on the containing enum instead.",
195                 )),
196                 (None, None) => (),
197             };
198 
199             self.drop = true;
200         } else if meta.path().is_ident("bound") {
201             assert!(self.bound.is_none(), "duplicate #[zeroize] bound flags");
202 
203             match (variant, binding) {
204                 (_variant, Some(_binding)) => {
205                     // structs don't have a variant prefix, and only structs have bindings outside of a variant
206                     let item_kind = match variant.and_then(|variant| variant.prefix) {
207                         Some(_) => "enum",
208                         None => "struct",
209                     };
210                     panic!(
211                         concat!(
212                             "The #[zeroize(bound)] attribute is not allowed on {} fields. ",
213                             "Use it on the containing {} instead.",
214                         ),
215                         item_kind, item_kind,
216                     )
217                 }
218                 (Some(_variant), None) => panic!(concat!(
219                     "The #[zeroize(bound)] attribute is not allowed on enum variants. ",
220                     "Use it on the containing enum instead.",
221                 )),
222                 (None, None) => {
223                     if let Meta::NameValue(meta_name_value) = meta {
224                         if let Lit::Str(lit) = &meta_name_value.lit {
225                             if lit.value().is_empty() {
226                                 self.bound = Some(Bounds(Punctuated::new()));
227                             } else {
228                                 self.bound = Some(lit.parse().unwrap_or_else(|e| {
229                                     panic!("error parsing bounds: {:?} ({})", lit, e)
230                                 }));
231                             }
232 
233                             return;
234                         }
235                     }
236 
237                     panic!(concat!(
238                         "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.",
239                         "E.g. #[zeroize(bound = \"T: MyTrait\")]."
240                     ))
241                 }
242             }
243         } else if meta.path().is_ident("skip") {
244             if variant.is_none() && binding.is_none() {
245                 panic!(concat!(
246                     "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. ",
247                     "Use it on a field or variant instead.",
248                 ))
249             }
250         } else {
251             panic!("unknown #[zeroize] attribute type: {:?}", meta.path());
252         }
253     }
254 }
255 
generate_fields(s: &mut synstructure::Structure<'_>, method: TokenStream) -> TokenStream256 fn generate_fields(s: &mut synstructure::Structure<'_>, method: TokenStream) -> TokenStream {
257     s.bind_with(|_| BindStyle::RefMut);
258 
259     s.filter_variants(|vi| {
260         let result = filter_skip(vi.ast().attrs, true);
261 
262         // check for duplicate `#[zeroize(skip)]` attributes in nested variants
263         for field in vi.ast().fields {
264             filter_skip(&field.attrs, result);
265         }
266 
267         result
268     })
269     .filter(|bi| filter_skip(&bi.ast().attrs, true))
270     .each(|bi| quote! { #bi.#method(); })
271 }
272 
filter_skip(attrs: &[Attribute], start: bool) -> bool273 fn filter_skip(attrs: &[Attribute], start: bool) -> bool {
274     let mut result = start;
275 
276     for attr in attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
277         if let Meta::List(list) = attr {
278             if list.path.is_ident(ZEROIZE_ATTR) {
279                 for nested in list.nested {
280                     if let NestedMeta::Meta(Meta::Path(path)) = nested {
281                         if path.is_ident("skip") {
282                             assert!(result, "duplicate #[zeroize] skip flags");
283                             result = false;
284                         }
285                     }
286                 }
287             }
288         }
289     }
290 
291     result
292 }
293 
294 /// Custom derive for `Zeroize` (without `Drop`)
derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream295 fn derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream {
296     let zeroizers = generate_fields(&mut s, quote! { zeroize });
297 
298     s.bound_impl(
299         quote!(zeroize::Zeroize),
300         quote! {
301             fn zeroize(&mut self) {
302                 match self {
303                     #zeroizers
304                 }
305             }
306         },
307     )
308 }
309 
310 /// Custom derive for `Zeroize` and `Drop`
derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream311 fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream {
312     let drop_impl = s.gen_impl(quote! {
313         gen impl Drop for @Self {
314             fn drop(&mut self) {
315                 self.zeroize();
316             }
317         }
318     });
319 
320     let zeroize_impl = derive_zeroize_without_drop(s);
321 
322     quote! {
323         #zeroize_impl
324 
325         #[doc(hidden)]
326         #drop_impl
327     }
328 }
329 
impl_zeroize_on_drop(s: &synstructure::Structure<'_>) -> TokenStream330 fn impl_zeroize_on_drop(s: &synstructure::Structure<'_>) -> TokenStream {
331     #[allow(unused_qualifications)]
332     s.bound_impl(quote!(zeroize::ZeroizeOnDrop), Option::<TokenStream>::None)
333 }
334 
335 #[cfg(test)]
336 mod tests {
337     use super::*;
338     use syn::parse_str;
339     use synstructure::{test_derive, Structure};
340 
341     #[test]
zeroize_without_drop()342     fn zeroize_without_drop() {
343         test_derive! {
344             derive_zeroize_without_drop {
345                 struct Z {
346                     a: String,
347                     b: Vec<u8>,
348                     c: [u8; 3],
349                 }
350             }
351             expands to {
352                 #[allow(non_upper_case_globals)]
353                 #[doc(hidden)]
354                 const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
355                     extern crate zeroize;
356                     impl zeroize::Zeroize for Z {
357                         fn zeroize(&mut self) {
358                             match self {
359                                 Z {
360                                     a: ref mut __binding_0,
361                                     b: ref mut __binding_1,
362                                     c: ref mut __binding_2,
363                                 } => {
364                                     { __binding_0.zeroize(); }
365                                     { __binding_1.zeroize(); }
366                                     { __binding_2.zeroize(); }
367                                 }
368                             }
369                         }
370                     }
371                 };
372             }
373             no_build // tests the code compiles are in the `zeroize` crate
374         }
375     }
376 
377     #[test]
zeroize_with_drop()378     fn zeroize_with_drop() {
379         test_derive! {
380             derive_zeroize_with_drop {
381                 struct Z {
382                     a: String,
383                     b: Vec<u8>,
384                     c: [u8; 3],
385                 }
386             }
387             expands to {
388                 #[allow(non_upper_case_globals)]
389                 #[doc(hidden)]
390                 const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
391                     extern crate zeroize;
392                     impl zeroize::Zeroize for Z {
393                         fn zeroize(&mut self) {
394                             match self {
395                                 Z {
396                                     a: ref mut __binding_0,
397                                     b: ref mut __binding_1,
398                                     c: ref mut __binding_2,
399                                 } => {
400                                     { __binding_0.zeroize(); }
401                                     { __binding_1.zeroize(); }
402                                     { __binding_2.zeroize(); }
403                                 }
404                             }
405                         }
406                     }
407                 };
408                 #[doc(hidden)]
409                 #[allow(non_upper_case_globals)]
410                 const _DERIVE_Drop_FOR_Z: () = {
411                     impl Drop for Z {
412                         fn drop(&mut self) {
413                             self.zeroize();
414                         }
415                     }
416                 };
417             }
418             no_build // tests the code compiles are in the `zeroize` crate
419         }
420     }
421 
422     #[test]
zeroize_with_skip()423     fn zeroize_with_skip() {
424         test_derive! {
425             derive_zeroize_without_drop {
426                 struct Z {
427                     a: String,
428                     b: Vec<u8>,
429                     #[zeroize(skip)]
430                     c: [u8; 3],
431                 }
432             }
433             expands to {
434                 #[allow(non_upper_case_globals)]
435                 #[doc(hidden)]
436                 const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
437                     extern crate zeroize;
438                     impl zeroize::Zeroize for Z {
439                         fn zeroize(&mut self) {
440                             match self {
441                                 Z {
442                                     a: ref mut __binding_0,
443                                     b: ref mut __binding_1,
444                                     ..
445                                 } => {
446                                     { __binding_0.zeroize(); }
447                                     { __binding_1.zeroize(); }
448                                 }
449                             }
450                         }
451                     }
452                 };
453             }
454             no_build // tests the code compiles are in the `zeroize` crate
455         }
456     }
457 
458     #[test]
zeroize_with_bound()459     fn zeroize_with_bound() {
460         test_derive! {
461             derive_zeroize {
462                 #[zeroize(bound = "T: MyTrait")]
463                 struct Z<T>(T);
464             }
465             expands to {
466                 #[allow(non_upper_case_globals)]
467                 #[doc(hidden)]
468                 const _DERIVE_zeroize_Zeroize_FOR_Z: () = {
469                     extern crate zeroize;
470                     impl<T> zeroize::Zeroize for Z<T>
471                     where T: MyTrait
472                     {
473                         fn zeroize(&mut self) {
474                             match self {
475                                 Z(ref mut __binding_0,) => {
476                                     { __binding_0.zeroize(); }
477                                 }
478                             }
479                         }
480                     }
481                 };
482             }
483             no_build // tests the code compiles are in the `zeroize` crate
484         }
485     }
486 
487     #[test]
zeroize_only_drop()488     fn zeroize_only_drop() {
489         test_derive! {
490             derive_zeroize_on_drop {
491                 struct Z {
492                     a: String,
493                     b: Vec<u8>,
494                     c: [u8; 3],
495                 }
496             }
497             expands to {
498                 #[allow(non_upper_case_globals)]
499                 const _DERIVE_Drop_FOR_Z: () = {
500                     impl Drop for Z {
501                         fn drop(&mut self) {
502                             use zeroize::__internal::AssertZeroize;
503                             use zeroize::__internal::AssertZeroizeOnDrop;
504                             match self {
505                                 Z {
506                                     a: ref mut __binding_0,
507                                     b: ref mut __binding_1,
508                                     c: ref mut __binding_2,
509                                 } => {
510                                     { __binding_0.zeroize_or_on_drop(); }
511                                     { __binding_1.zeroize_or_on_drop(); }
512                                     { __binding_2.zeroize_or_on_drop(); }
513                                 }
514                             }
515                         }
516                     }
517                 };
518                 #[allow(non_upper_case_globals)]
519                 #[doc(hidden)]
520                 const _DERIVE_zeroize_ZeroizeOnDrop_FOR_Z: () = {
521                     extern crate zeroize;
522                     impl zeroize::ZeroizeOnDrop for Z {}
523                 };
524             }
525             no_build // tests the code compiles are in the `zeroize` crate
526         }
527     }
528 
529     #[test]
zeroize_on_struct()530     fn zeroize_on_struct() {
531         parse_zeroize_test(stringify!(
532             #[zeroize(drop)]
533             struct Z {
534                 a: String,
535                 b: Vec<u8>,
536                 c: [u8; 3],
537             }
538         ));
539     }
540 
541     #[test]
zeroize_on_enum()542     fn zeroize_on_enum() {
543         parse_zeroize_test(stringify!(
544             #[zeroize(drop)]
545             enum Z {
546                 Variant1 { a: String, b: Vec<u8>, c: [u8; 3] },
547             }
548         ));
549     }
550 
551     #[test]
552     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_struct_field()553     fn zeroize_on_struct_field() {
554         parse_zeroize_test(stringify!(
555             struct Z {
556                 #[zeroize(drop)]
557                 a: String,
558                 b: Vec<u8>,
559                 c: [u8; 3],
560             }
561         ));
562     }
563 
564     #[test]
565     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_tuple_struct_field()566     fn zeroize_on_tuple_struct_field() {
567         parse_zeroize_test(stringify!(
568             struct Z(#[zeroize(drop)] String);
569         ));
570     }
571 
572     #[test]
573     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")]
zeroize_on_second_field()574     fn zeroize_on_second_field() {
575         parse_zeroize_test(stringify!(
576             struct Z {
577                 a: String,
578                 #[zeroize(drop)]
579                 b: Vec<u8>,
580                 c: [u8; 3],
581             }
582         ));
583     }
584 
585     #[test]
586     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_tuple_enum_variant_field()587     fn zeroize_on_tuple_enum_variant_field() {
588         parse_zeroize_test(stringify!(
589             enum Z {
590                 Variant(#[zeroize(drop)] String),
591             }
592         ));
593     }
594 
595     #[test]
596     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_enum_variant_field()597     fn zeroize_on_enum_variant_field() {
598         parse_zeroize_test(stringify!(
599             enum Z {
600                 Variant {
601                     #[zeroize(drop)]
602                     a: String,
603                     b: Vec<u8>,
604                     c: [u8; 3],
605                 },
606             }
607         ));
608     }
609 
610     #[test]
611     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")]
zeroize_on_enum_second_variant_field()612     fn zeroize_on_enum_second_variant_field() {
613         parse_zeroize_test(stringify!(
614             enum Z {
615                 Variant1 {
616                     a: String,
617                     b: Vec<u8>,
618                     c: [u8; 3],
619                 },
620                 Variant2 {
621                     #[zeroize(drop)]
622                     a: String,
623                     b: Vec<u8>,
624                     c: [u8; 3],
625                 },
626             }
627         ));
628     }
629 
630     #[test]
631     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
zeroize_on_enum_variant()632     fn zeroize_on_enum_variant() {
633         parse_zeroize_test(stringify!(
634             enum Z {
635                 #[zeroize(drop)]
636                 Variant,
637             }
638         ));
639     }
640 
641     #[test]
642     #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")]
zeroize_on_enum_second_variant()643     fn zeroize_on_enum_second_variant() {
644         parse_zeroize_test(stringify!(
645             enum Z {
646                 Variant1,
647                 #[zeroize(drop)]
648                 Variant2,
649             }
650         ));
651     }
652 
653     #[test]
654     #[should_panic(
655         expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
656     )]
zeroize_skip_on_struct()657     fn zeroize_skip_on_struct() {
658         parse_zeroize_test(stringify!(
659             #[zeroize(skip)]
660             struct Z {
661                 a: String,
662                 b: Vec<u8>,
663                 c: [u8; 3],
664             }
665         ));
666     }
667 
668     #[test]
669     #[should_panic(
670         expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead."
671     )]
zeroize_skip_on_enum()672     fn zeroize_skip_on_enum() {
673         parse_zeroize_test(stringify!(
674             #[zeroize(skip)]
675             enum Z {
676                 Variant1,
677                 Variant2,
678             }
679         ));
680     }
681 
682     #[test]
683     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip()684     fn zeroize_duplicate_skip() {
685         parse_zeroize_test(stringify!(
686             struct Z {
687                 a: String,
688                 #[zeroize(skip)]
689                 #[zeroize(skip)]
690                 b: Vec<u8>,
691                 c: [u8; 3],
692             }
693         ));
694     }
695 
696     #[test]
697     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip_list()698     fn zeroize_duplicate_skip_list() {
699         parse_zeroize_test(stringify!(
700             struct Z {
701                 a: String,
702                 #[zeroize(skip, skip)]
703                 b: Vec<u8>,
704                 c: [u8; 3],
705             }
706         ));
707     }
708 
709     #[test]
710     #[should_panic(expected = "duplicate #[zeroize] skip flags")]
zeroize_duplicate_skip_enum()711     fn zeroize_duplicate_skip_enum() {
712         parse_zeroize_test(stringify!(
713             enum Z {
714                 #[zeroize(skip)]
715                 Variant {
716                     a: String,
717                     #[zeroize(skip)]
718                     b: Vec<u8>,
719                     c: [u8; 3],
720                 },
721             }
722         ));
723     }
724 
725     #[test]
726     #[should_panic(expected = "duplicate #[zeroize] bound flags")]
zeroize_duplicate_bound()727     fn zeroize_duplicate_bound() {
728         parse_zeroize_test(stringify!(
729             #[zeroize(bound = "T: MyTrait")]
730             #[zeroize(bound = "")]
731             struct Z<T>(T);
732         ));
733     }
734 
735     #[test]
736     #[should_panic(expected = "duplicate #[zeroize] bound flags")]
zeroize_duplicate_bound_list()737     fn zeroize_duplicate_bound_list() {
738         parse_zeroize_test(stringify!(
739             #[zeroize(bound = "T: MyTrait", bound = "")]
740             struct Z<T>(T);
741         ));
742     }
743 
744     #[test]
745     #[should_panic(
746         expected = "The #[zeroize(bound)] attribute is not allowed on struct fields. Use it on the containing struct instead."
747     )]
zeroize_bound_struct()748     fn zeroize_bound_struct() {
749         parse_zeroize_test(stringify!(
750             struct Z<T> {
751                 #[zeroize(bound = "T: MyTrait")]
752                 a: T,
753             }
754         ));
755     }
756 
757     #[test]
758     #[should_panic(
759         expected = "The #[zeroize(bound)] attribute is not allowed on enum variants. Use it on the containing enum instead."
760     )]
zeroize_bound_enum()761     fn zeroize_bound_enum() {
762         parse_zeroize_test(stringify!(
763             enum Z<T> {
764                 #[zeroize(bound = "T: MyTrait")]
765                 A(T),
766             }
767         ));
768     }
769 
770     #[test]
771     #[should_panic(
772         expected = "The #[zeroize(bound)] attribute is not allowed on enum fields. Use it on the containing enum instead."
773     )]
zeroize_bound_enum_variant_field()774     fn zeroize_bound_enum_variant_field() {
775         parse_zeroize_test(stringify!(
776             enum Z<T> {
777                 A {
778                     #[zeroize(bound = "T: MyTrait")]
779                     a: T,
780                 },
781             }
782         ));
783     }
784 
785     #[test]
786     #[should_panic(
787         expected = "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.E.g. #[zeroize(bound = \"T: MyTrait\")]."
788     )]
zeroize_bound_no_value()789     fn zeroize_bound_no_value() {
790         parse_zeroize_test(stringify!(
791             #[zeroize(bound)]
792             struct Z<T>(T);
793         ));
794     }
795 
796     #[test]
797     #[should_panic(expected = "error parsing bounds: LitStr { token: \"T\" } (expected `:`)")]
zeroize_bound_no_where_predicate()798     fn zeroize_bound_no_where_predicate() {
799         parse_zeroize_test(stringify!(
800             #[zeroize(bound = "T")]
801             struct Z<T>(T);
802         ));
803     }
804 
parse_zeroize_test(unparsed: &str) -> TokenStream805     fn parse_zeroize_test(unparsed: &str) -> TokenStream {
806         derive_zeroize(Structure::new(
807             &parse_str(unparsed).expect("Failed to parse test input"),
808         ))
809     }
810 }
811