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