anodized_core/instrument/
fns.rs1#[cfg(test)]
2#[path = "fns_tests.rs"]
3mod fns_tests;
4
5use proc_macro2::Span;
6use quote::{ToTokens, quote};
7use syn::{
8 Attribute, Block, Expr, Ident, Meta, Pat, Path, ReturnType, Signature, Stmt, Type,
9 parse::{Parse, Result},
10 parse_quote,
11};
12
13use crate::{
14 Capture, Condition, PostCondition, Spec,
15 instrument::{CheckSettings, Mode},
16 qualifiers::FnQualifiers,
17};
18
19impl Mode {
20 pub fn instrument_fn(&self, spec: &Spec, sig: &Signature, body: &mut Block) -> syn::Result<()> {
21 self.instrument_loops_in_fn_body(body)?;
22
23 let Mode::InjectChecks(check_config) = self else {
24 return Ok(());
25 };
26
27 let is_async = sig.asyncness.is_some();
28
29 let new_body = check_config.instrument_fn_body(spec, body, is_async, &sig.output)?;
31
32 *body = new_body;
34
35 Ok(())
36 }
37
38 pub fn build_precondition_fn_sig(prefix: &str, sig: &Signature) -> Signature {
39 Signature {
40 constness: sig.constness,
41 asyncness: sig.asyncness,
42 unsafety: sig.unsafety,
43 abi: sig.abi.clone(),
44 fn_token: sig.fn_token,
45 ident: syn::Ident::new(&format!("{prefix}_{}", sig.ident), sig.ident.span()),
46 generics: sig.generics.clone(),
47 paren_token: sig.paren_token,
48 inputs: sig.inputs.clone(),
49 variadic: sig.variadic.clone(),
50 output: parse_quote!(-> bool),
51 }
52 }
53
54 pub fn build_postcondition_fn_sig(prefix: &str, sig: &Signature) -> Signature {
55 let mut inputs = sig.inputs.clone();
56 let output_binder = match &sig.output {
57 ReturnType::Type(_, return_type) => parse_quote! { __anodized_output: #return_type },
58 ReturnType::Default => parse_quote! { __anodized_output: () },
59 };
60 inputs.push(output_binder);
61
62 Signature {
63 constness: sig.constness,
64 asyncness: sig.asyncness,
65 unsafety: sig.unsafety,
66 abi: sig.abi.clone(),
67 fn_token: sig.fn_token,
68 ident: syn::Ident::new(&format!("{prefix}_{}", sig.ident), sig.ident.span()),
69 generics: sig.generics.clone(),
70 paren_token: sig.paren_token,
71 inputs,
72 variadic: sig.variadic.clone(),
73 output: parse_quote!(-> bool),
74 }
75 }
76
77 pub fn build_qualifier_const_item<SomeConstItem: Parse>(
78 attrs: &[Attribute],
79 prefix: &str,
80 qualifiers: FnQualifiers,
81 fn_ident: &Ident,
82 ) -> SomeConstItem {
83 let qualifier_bits = qualifiers.bits();
84 let name: Ident = syn::Ident::new(&format!("{}_{}", prefix, fn_ident), fn_ident.span());
85 parse_quote! {
86 #(#attrs)*
87 const #name: u32 = #qualifier_bits;
88 }
89 }
90
91 pub fn build_qualifier_check_stmt(
92 fn_ident: &Ident,
93 impl_type: &Type,
94 trait_path: &Path,
95 ) -> Stmt {
96 let impl_const_name = Ident::new(
97 &format!("__anodized_fn_qualifiers_{}", fn_ident),
98 fn_ident.span(),
99 );
100
101 let trait_const_name = Ident::new(
102 &format!("__anodized_fn_qualifiers_trait_{}", fn_ident),
103 fn_ident.span(),
104 );
105
106 let message = format!(
107 "the qualifiers on the impl `{}::{fn_ident}` cannot be weaker than the qualifiers on the trait `{}::{fn_ident}`",
108 impl_type.to_token_stream(),
109 trait_path.to_token_stream(),
110 );
111
112 parse_quote! {
113 const {
114 assert!(
115 Self::#impl_const_name == Self::#trait_const_name | Self::#impl_const_name,
116 #message,
117 );
118 };
119 }
120 }
121
122 pub fn build_precondition_fn_body(requires: &[Condition], maintains: &[Condition]) -> Block {
123 let mut statements: Vec<Stmt> = vec![];
124 let mut clauses: Vec<Expr> = vec![];
125
126 for condition in requires.iter().chain(maintains) {
127 let i = clauses.len();
128 let name = Ident::new(&format!("__anodized_clause_{}", i + 1), Span::mixed_site());
129 let expr = &condition.expr;
130 statements.push(parse_quote! { let #name = (|| -> bool { #expr })(); });
131 clauses.push(parse_quote! { #name });
132 }
133
134 if clauses.is_empty() {
135 clauses.push(parse_quote!(true));
136 }
137
138 parse_quote! {
139 {
140 #(#statements)*
141 #(#clauses)&&*
142 }
143 }
144 }
145
146 pub fn build_postcondition_fn_body(
147 maintains: &[Condition],
148 captures: &[Capture],
149 ensures: &[PostCondition],
150 ) -> Result<Block> {
151 let mut statements: Vec<Stmt> = vec![];
152 let mut clauses: Vec<Expr> = vec![];
153
154 for condition in maintains {
155 let i = clauses.len();
156 let name = Ident::new(&format!("__anodized_clause_{}", i + 1), Span::mixed_site());
157 let expr = &condition.expr;
158 statements.push(parse_quote! { let #name = (|| -> bool { #expr })(); });
159 clauses.push(parse_quote! { #name });
160 }
161
162 {
163 let patterns = captures.iter().map(|capture| &capture.pat);
164 let values = captures.iter().map(|capture| -> Expr {
165 let expr = &capture.expr;
166 parse_quote! { (|| #expr)() }
168 });
169 statements.push(parse_quote! { let (#(#patterns),*) = (#(#values),*); });
170 }
171
172 for postcond in ensures {
173 let i = clauses.len();
174 let name = Ident::new(&format!("__anodized_clause_{}", i + 1), Span::mixed_site());
175 let expr = &postcond.expr;
176 if let Some(pat) = &postcond.pat {
177 statements.push(
178 parse_quote! { let #name = (|#pat| -> bool { #expr })(__anodized_output); },
179 );
180 } else {
181 statements.push(parse_quote! { let #name = (|| -> bool { #expr })(); });
182 }
183 clauses.push(parse_quote! { #name });
184 }
185
186 if clauses.is_empty() {
187 clauses.push(parse_quote!(true));
188 }
189
190 Ok(parse_quote! {
191 {
192 #(#statements)*
193 #(#clauses)&&*
194 }
195 })
196 }
197}
198
199impl CheckSettings {
200 fn instrument_fn_body(
201 &self,
202 spec: &Spec,
203 original_body: &Block,
204 is_async: bool,
205 return_type: &ReturnType,
206 ) -> Result<Block> {
207 let output_ident: Pat = parse_quote!(__anodized_output);
209
210 let mut precondition_clauses: Vec<Expr> = vec![];
212 for condition in spec.requires.iter().chain(&spec.maintains) {
213 let expr = &condition.expr;
214 let repr = expr.to_token_stream().to_string();
215 let expr = parse_quote! { __anodized_eval_pre(|| -> bool { #expr }) };
216 let clause = self.build_clause_eval(&condition.cfg, &expr, &repr);
217 precondition_clauses.push(clause);
218 }
219 if precondition_clauses.is_empty() {
220 precondition_clauses.push(parse_quote!(true));
221 }
222
223 let patterns = spec
226 .captures
227 .iter()
228 .map(|cb| &cb.pat)
229 .chain(std::iter::once(&output_ident));
230
231 let body_expr = if is_async {
232 quote! { (async || #return_type #original_body)().await }
233 } else {
234 quote! { (|| #return_type #original_body)() }
235 };
236 let values = spec
237 .captures
238 .iter()
239 .map(|cb| {
240 let expr = &cb.expr;
241 quote! { (|| #expr)() }
243 })
244 .chain(std::iter::once(body_expr));
245
246 let captures_and_output = quote! {
247 let (#(#patterns),*) = (#(#values),*);
248 };
249
250 let mut postcondition_clauses: Vec<Expr> = vec![];
252 for condition in &spec.maintains {
253 let expr = &condition.expr;
254 let repr = expr.to_token_stream().to_string();
255 let expr = parse_quote! { __anodized_eval_post(|| -> bool { #expr }) };
256 let clause = self.build_clause_eval(&condition.cfg, &expr, &repr);
257 postcondition_clauses.push(clause);
258 }
259 for postcond in &spec.ensures {
260 let expr = &postcond.expr;
261 let repr = expr.to_token_stream().to_string();
262 let expr = if let Some(pat) = &postcond.pat {
263 parse_quote! {
264 __anodized_eval_post(|| -> bool { let #pat = #output_ident; #expr })
265 }
266 } else {
267 parse_quote! { __anodized_eval_post(|| -> bool { #expr }) }
268 };
269 let clause = self.build_clause_eval(&postcond.cfg, &expr, &repr);
270 postcondition_clauses.push(clause);
271 }
272 if postcondition_clauses.is_empty() {
273 postcondition_clauses.push(parse_quote!(true));
274 }
275
276 let do_run_checks = self.does_print || self.does_panic.is_some();
277
278 let (output_expr, precond_fail_action, postcond_fail_action) =
279 if let Some(ref panic_settings) = self.does_panic
280 && panic_settings.has_try_fn
281 {
282 (
283 quote! { Ok(#output_ident) },
284 Some(parse_quote! {
285 return ::anodized::result::pre_err(__anodized_errors);
286 }),
287 Some(parse_quote! {
288 return ::anodized::result::post_err(#output_ident, __anodized_errors);
289 }),
290 )
291 } else {
292 (
293 quote! { #output_ident },
294 self.build_fail_action("precondition failed"),
295 self.build_fail_action("postcondition failed"),
296 )
297 };
298
299 Ok(parse_quote! {
300 {
301 if #do_run_checks {
302 fn __anodized_eval_pre(c: impl Fn() -> bool) -> bool { c() }
303 let mut __anodized_errors = ::std::string::String::new();
304 let __anodized_precond = #(#precondition_clauses)&*;
305 if !__anodized_precond {
306 #precond_fail_action
307 }
308 }
309 #captures_and_output
310 if #do_run_checks {
311 fn __anodized_eval_post(c: impl Fn() -> bool) -> bool { c() }
312 let mut __anodized_errors = ::std::string::String::new();
313 let __anodized_postcond = #(#postcondition_clauses)&*;
314 if !__anodized_postcond {
315 #postcond_fail_action
316 }
317 }
318 #output_expr
319 }
320 })
321 }
322
323 fn build_clause_eval(&self, cfg: &Option<Meta>, expr: &Expr, repr: &str) -> Expr {
324 if self.does_print {
325 let br_and_repr = format!("\n {repr}");
326 let cfg_guard = match cfg {
327 Some(meta) => quote! { !cfg!(#meta) || },
328 None => quote!(),
329 };
330 parse_quote! { ( #cfg_guard #expr || __anodized_errors.push_str(#br_and_repr) != () ) }
331 } else {
332 expr.clone()
333 }
334 }
335
336 fn build_fail_action(&self, message: &str) -> Option<Stmt> {
337 let message_and_errors = format!("{message}:{{__anodized_errors}}");
338 match (self.does_print, self.does_panic.is_some()) {
339 (true, true) => Some(parse_quote! { panic!(#message_and_errors); }),
340 (true, false) => Some(parse_quote! { eprintln!(#message_and_errors); }),
341 (false, true) => Some(parse_quote! { panic!(#message); }),
342 (false, false) => None,
343 }
344 }
345}
346
347pub(crate) fn make_try_fn_ident(ident: &Ident) -> Ident {
348 Ident::new(&format!("__anodized_fn_try_{ident}"), ident.span())
349}
350
351pub fn make_try_call(mut expr: Expr) -> Result<Expr> {
352 match &mut expr {
353 Expr::Call(fn_call) => {
354 if let Expr::Path(path) = fn_call.func.as_mut()
355 && (path.qself.is_some() || path.path.segments.len() > 1)
356 {
357 let last_segment = path.path.segments.last_mut().expect("last segment");
358 last_segment.ident = make_try_fn_ident(&last_segment.ident);
359 return Ok(expr);
360 }
361 }
362 Expr::MethodCall(method_call) => {
363 method_call.method = make_try_fn_ident(&method_call.method);
364 return Ok(expr);
365 }
366 _ => {}
367 }
368
369 Err(syn::Error::new_spanned(
370 expr,
371 "must be a method call or a qualified function call",
372 ))
373}