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