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}