checkers_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use std::num::NonZeroUsize;
6
7/// Run a `#[test]` function in checkers.
8///
9/// At the end of the test case checkers checks memory sanitation and reports
10/// any errors encountered.
11///
12/// # Attributes
13///
14/// The `test` macro has the following attributes:
15/// * `capacity` - Reserve capacity for the specified number of events
16///   beforehand. Checkers will otherwise grow it as necessary using the system
17///   allocator directly.
18/// * `verify` - Use a custom verification function (see below).
19///
20/// # Examples
21///
22/// ```rust
23/// #[global_allocator]
24/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
25///
26/// #[checkers::test]
27/// fn test_leaky_box() {
28///     let _ = Box::into_raw(Box::new(42));
29/// }
30/// ```
31///
32/// Reserve capacity for the specified number of events up front:
33///
34/// ```rust
35/// #[global_allocator]
36/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
37///
38/// #[checkers::test(capacity = 10_000)]
39/// fn test_custom_verify() {
40///     for i in 0..1000 {
41///         let v = Box::into_raw(vec![1, 2, 3, 4, 5].into_boxed_slice());
42///         let _ = unsafe { Box::from_raw(v) };
43///     }
44/// }
45/// ```
46///
47/// Using a custom verifier:
48///
49/// ```rust
50/// #[global_allocator]
51/// static ALLOCATOR: checkers::Allocator = checkers::Allocator::system();
52///
53/// fn verify_test_custom_verify(state: &mut checkers::State) {
54///    let mut violations = Vec::new();
55///    state.validate(&mut violations);
56///    assert_eq!(1, violations.len());
57///    assert!(violations[0].is_leaked_with(|region| region.size == 20 && region.align == 4));
58/// }
59///
60/// #[checkers::test(verify = "verify_test_custom_verify")]
61/// fn test_custom_verify() {
62///     let _ = Box::into_raw(vec![1, 2, 3, 4, 5].into_boxed_slice());
63/// }
64/// ```
65#[proc_macro_attribute]
66pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
67    let input = syn::parse_macro_input!(item as syn::ItemFn);
68    let args = syn::parse_macro_input!(args as syn::AttributeArgs);
69
70    let ret = &input.sig.output;
71    let name = &input.sig.ident;
72    let body = &input.block;
73    let attrs = &input.attrs;
74    let vis = input.vis;
75
76    for attr in attrs {
77        if attr.path.is_ident("test") {
78            let msg = "second test attribute is supplied";
79            return syn::Error::new_spanned(&attr, msg)
80                .to_compile_error()
81                .into();
82        }
83    }
84
85    if !input.sig.inputs.is_empty() {
86        let msg = "the test function cannot accept arguments";
87        return syn::Error::new_spanned(&input.sig.inputs, msg)
88            .to_compile_error()
89            .into();
90    }
91
92    let mut capacity = NonZeroUsize::new(1024).unwrap();
93    let mut verify = None::<syn::Ident>;
94
95    for arg in args {
96        if let syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) = arg {
97            let ident = namevalue.path.get_ident();
98            if ident.is_none() {
99                let msg = "Must have specified ident";
100                return syn::Error::new_spanned(namevalue.path, msg)
101                    .to_compile_error()
102                    .into();
103            }
104            match ident.unwrap().to_string().to_lowercase().as_str() {
105                "capacity" => match &namevalue.lit {
106                    syn::Lit::Int(expr) => {
107                        capacity = match expr.base10_parse::<NonZeroUsize>() {
108                            Ok(n) => n,
109                            _ => {
110                                return syn::Error::new_spanned(
111                                    expr,
112                                    "capacity argument is not valid",
113                                )
114                                .to_compile_error()
115                                .into();
116                            }
117                        }
118                    }
119                    _ => {
120                        return syn::Error::new_spanned(
121                            namevalue,
122                            "capacity argument must be an int",
123                        )
124                        .to_compile_error()
125                        .into();
126                    }
127                },
128                "verify" => match &namevalue.lit {
129                    syn::Lit::Str(expr) => {
130                        verify = Some(match expr.parse::<syn::Ident>() {
131                            Ok(ident) => ident,
132                            Err(..) => {
133                                return syn::Error::new_spanned(
134                                    expr,
135                                    "verify argument is not valid",
136                                )
137                                .to_compile_error()
138                                .into();
139                            }
140                        });
141                    }
142                    _ => {
143                        return syn::Error::new_spanned(
144                            namevalue,
145                            "verify argument must be a string",
146                        )
147                        .to_compile_error()
148                        .into();
149                    }
150                },
151                name => {
152                    let msg = format!("Unknown attribute {} is specified", name);
153                    return syn::Error::new_spanned(namevalue.path, msg)
154                        .to_compile_error()
155                        .into();
156                }
157            }
158        }
159    }
160
161    let capacity = capacity.get();
162
163    let verify = match verify {
164        Some(verify) => {
165            quote! {
166                #verify(state);
167            }
168        }
169        None => quote! {
170            checkers::verify!(state);
171        },
172    };
173
174    let result = quote! {
175        #[test]
176        #(#attrs)*
177        #vis fn #name() #ret {
178            checkers::with_state(|s| {
179                {
180                    let mut s = s.borrow_mut();
181                    s.clear();
182                    s.reserve(#capacity);
183                }
184
185                checkers::with_unmuted(|| #body);
186
187                let state = &mut *s.borrow_mut();
188                #verify
189            });
190        }
191    };
192
193    result.into()
194}