Skip to main content

zeroize_derive/
lib.rs

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