1#![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
19const ZEROIZE_ATTR: &str = "zeroize";
21
22#[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#[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#[derive(Default)]
124struct ZeroizeAttrs {
125 drop: bool,
127 bound: Option<Bounds>,
129 auto_params: Vec<Ident>,
131}
132
133struct 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 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 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 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 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 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 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}