Skip to content

Commit d94174a

Browse files
committed
zeroize: Improved attribute parser (fixes #237)
The previous attribute parser used a hack where it would compare to the s-exp representation of zeroize attributes to determine of they are `drop` or `no_drop`. This was a bit of a hack that broke with a recent nightly, which removed the spaces surrounding the ident. This commit rewrites the attribute parser to properly walk the attribute `syn::Meta` nodes in the AST until it arrives at a `syn::Ident` containing either `drop` or `no_drop`. All other AST nodes will result in a panic. This relaxes the previous restriction that any `#[derive(Zeroize)]` MUST be annotated with either `drop` or `no_drop`. This was put in place because `zeroize` v0.9 switched from default drop to requiring an explicit attribute. However v0.8 was the only version with implict drop, and has been yanked for several weeks. The change is fully backwards compatible and should have no effect on any existing v0.9 users.
1 parent 1c16876 commit d94174a

File tree

3 files changed

+51
-22
lines changed

3 files changed

+51
-22
lines changed

zeroize/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This crate provides a safe<sup>†</sup>, portable access to cross-platform
1313
intrinsics for securely zeroing memory which are specifically documented as
1414
guaranteeing they won't be "optimized away".
1515

16-
The [`Zeroize` trait] is the crate's primary (and only) API.
16+
The [`Zeroize` trait] is the crate's primary API.
1717

1818
[Documentation]
1919

zeroize/src/lib.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,13 @@
5252
//!
5353
//! ## Custom Derive Support
5454
//!
55-
//! **NOTICE**: Previous versions of `zeroize` automatically derived
56-
//! `Drop`. This has been *REMOVED* and you now *MUST* explicitly specify
57-
//! either `zeroize(drop)` or `zeroize(no_drop)` (see below).
58-
//!
5955
//! This crate has custom derive support for the `Zeroize` trait, which
6056
//! automatically calls `zeroize()` on all members of a struct or tuple struct.
6157
//!
62-
//! Additionally it supports the following attributes (you *MUST* pick one):
58+
//! Additionally it supports the following attributes:
6359
//!
64-
//! - `#[zeroize(no_drop)]`: derive only `Zeroize` without adding a `Drop` impl
6560
//! - `#[zeroize(drop)]`: call `zeroize()` when this item is dropped
61+
//! - `#[zeroize(no_drop)]`: legacy attribute which will be removed in `zeroize` 1.0
6662
//!
6763
//! Example which derives `Drop`:
6864
//!
@@ -82,7 +78,6 @@
8278
//!
8379
//! // This struct will *NOT* be zeroized on drop
8480
//! #[derive(Copy, Clone, Zeroize)]
85-
//! #[zeroize(no_drop)]
8681
//! struct MyStruct([u8; 32]);
8782
//! ```
8883
//!

zeroize_derive/src/lib.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,89 @@ extern crate proc_macro;
88

99
use proc_macro2::TokenStream;
1010
use quote::quote;
11+
use syn::{Attribute, Ident, Meta, NestedMeta};
1112
use synstructure::{decl_derive, BindStyle};
1213

1314
/// Name of zeroize-related attributes
1415
const ZEROIZE_ATTR: &str = "zeroize";
1516

1617
/// Custom derive for `Zeroize`
1718
fn derive_zeroize(s: synstructure::Structure) -> TokenStream {
18-
let attributes = ZeroizeDeriveAttrs::parse(&s);
19+
let attributes = DeriveAttrs::parse(&s);
1920

2021
match attributes.drop {
2122
Some(true) => derive_zeroize_with_drop(s),
22-
Some(false) => derive_zeroize_without_drop(s),
23-
None => panic!("must specify either zeroize(drop) or zeroize(no_drop) attribute"),
23+
Some(false) | None => derive_zeroize_without_drop(s),
2424
}
2525
}
2626
decl_derive!([Zeroize, attributes(zeroize)] => derive_zeroize);
2727

2828
/// Custom derive attributes for `Zeroize`
29-
struct ZeroizeDeriveAttrs {
29+
struct DeriveAttrs {
3030
/// Derive a `Drop` impl which calls zeroize on this type
3131
drop: Option<bool>,
3232
}
3333

34-
impl Default for ZeroizeDeriveAttrs {
34+
impl Default for DeriveAttrs {
3535
fn default() -> Self {
3636
Self { drop: None }
3737
}
3838
}
3939

40-
impl ZeroizeDeriveAttrs {
40+
impl DeriveAttrs {
4141
/// Parse attributes from the incoming AST
4242
fn parse(s: &synstructure::Structure) -> Self {
4343
let mut result = Self::default();
4444

4545
for v in s.variants().iter() {
4646
for attr in v.ast().attrs.iter() {
47-
if attr.path.is_ident(ZEROIZE_ATTR) {
48-
// TODO(tarcieri): hax, but probably good enough for now
49-
match attr.tts.to_string().as_ref() {
50-
"( drop )" => result.drop = Some(true),
51-
"( no_drop )" => result.drop = Some(false),
52-
other => panic!("unknown zeroize attribute: {}", other),
53-
}
54-
}
47+
result.parse_attr(attr);
5548
}
5649
}
5750

5851
result
5952
}
53+
54+
/// Parse attribute and handle `#[zeroize(...)]` attributes
55+
fn parse_attr(&mut self, attr: &Attribute) {
56+
let meta = attr
57+
.parse_meta()
58+
.unwrap_or_else(|e| panic!("error parsing attribute: {} ({})", attr.tts, e));
59+
60+
if let Meta::List(list) = meta {
61+
if list.ident != ZEROIZE_ATTR {
62+
return;
63+
}
64+
65+
for nested_meta in &list.nested {
66+
if let NestedMeta::Meta(Meta::Word(ident)) = nested_meta {
67+
self.parse_attr_ident(ident);
68+
} else {
69+
panic!("malformed #[zeroize] attribute: {:?}", nested_meta);
70+
}
71+
}
72+
}
73+
}
74+
75+
/// Parse a `#[zeroize(...)]` attribute containing a single ident (e.g. `drop`)
76+
fn parse_attr_ident(&mut self, ident: &Ident) {
77+
if ident == "drop" {
78+
self.set_drop_flag(true);
79+
} else if ident == "no_drop" {
80+
self.set_drop_flag(false);
81+
} else {
82+
panic!("unknown #[zeroize] attribute type: {}", ident);
83+
}
84+
}
85+
86+
/// Set the value of the `drop` flag
87+
fn set_drop_flag(&mut self, value: bool) {
88+
if self.drop.is_some() {
89+
panic!("duplicate #[zeroize] drop/no_drop flags");
90+
} else {
91+
self.drop = Some(value);
92+
}
93+
}
6094
}
6195

6296
/// Custom derive for `Zeroize` (without `Drop`)

0 commit comments

Comments
 (0)