Skip to main content

anodized_core/instrument/
fns.rs

1#[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        // Generate the new, instrumented function body.
30        let new_body = check_config.instrument_fn_body(spec, body, is_async, &sig.output)?;
31
32        // Replace the old function body with the new one.
33        *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                // Wrap in closure to guard against `return`.
167                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        // The identifier for the return value binding.
208        let output_ident: Pat = parse_quote!(__anodized_output);
209
210        // Generate precondition checks.
211        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        // Bind capture values and function output in a single tuple assignment.
224        // This ensures captured values are inaccessible to the body.
225        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                // Evaluate expression in a closure to prevent early return.
242                quote! { (|| #expr)() }
243            })
244            .chain(std::iter::once(body_expr));
245
246        let captures_and_output = quote! {
247            let (#(#patterns),*) = (#(#values),*);
248        };
249
250        // Generate postcondition checks.
251        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}