anodized_core/
lib.rs

1//! Core interoperability for the Anodized correctness ecosystem.
2//!
3#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
4
5use proc_macro2::Span;
6use quote::{ToTokens, quote};
7use syn::{
8    Attribute, Block, Expr, ExprClosure, Ident, Meta, Pat, Token,
9    parse::{Parse, ParseStream, Result},
10    parse_quote,
11    punctuated::Punctuated,
12    spanned::Spanned,
13};
14
15/// A spec specifies the intended behavior of a function or method.
16#[derive(Debug)]
17pub struct Spec {
18    /// Preconditions: conditions that must hold when the function is called.
19    pub requires: Vec<Condition>,
20    /// Invariants: conditions that must hold both when the function is called and when it returns.
21    pub maintains: Vec<Condition>,
22    /// Postconditions: conditions that must hold when the function returns.
23    pub ensures: Vec<ConditionClosure>,
24}
25
26/// A condition represented by a `bool`-valued expression.
27#[derive(Debug)]
28pub struct Condition {
29    /// The expression.
30    pub expr: Expr,
31    /// **Static analyzers can safely ignore this field.**
32    ///
33    /// Build configuration filter to decide whether to add runtime checks.
34    /// Passed to a `cfg!()` guard in the instrumented function.
35    pub cfg: Option<Meta>,
36}
37
38/// A condition represented by a `bool`-valued closure.
39#[derive(Debug)]
40pub struct ConditionClosure {
41    /// The closure.
42    pub closure: ExprClosure,
43    /// **Static analyzers can safely ignore this field.**
44    ///
45    /// Build configuration filter to decide whether to add runtime checks.
46    /// Passed to a `cfg!()` guard in the instrumented function.
47    pub cfg: Option<Meta>,
48}
49
50impl Parse for Spec {
51    fn parse(input: ParseStream) -> Result<Self> {
52        let args = Punctuated::<SpecArg, Token![,]>::parse_terminated(input)?;
53
54        let mut last_arg_order: Option<ArgOrder> = None;
55        let mut requires: Vec<Condition> = vec![];
56        let mut maintains: Vec<Condition> = vec![];
57        let mut binds_pattern: Option<Pat> = None;
58        let mut ensures_exprs: Vec<Condition> = vec![];
59
60        for arg in args {
61            let current_arg_order = arg.get_order();
62            if let Some(last_order) = last_arg_order {
63                if current_arg_order < last_order {
64                    return Err(syn::Error::new(
65                        arg.get_keyword_span(),
66                        "parameters are out of order: their order must be `requires`, `maintains`, `binds`, `ensures`",
67                    ));
68                }
69            }
70            last_arg_order = Some(current_arg_order);
71
72            match arg {
73                SpecArg::Requires { cfg, expr, .. } => {
74                    if let Expr::Array(conditions) = expr {
75                        requires.extend(conditions.elems.into_iter().map(|expr| Condition {
76                            expr,
77                            cfg: cfg.clone(),
78                        }));
79                    } else {
80                        requires.push(Condition { expr, cfg });
81                    }
82                }
83                SpecArg::Maintains { cfg, expr, .. } => {
84                    if let Expr::Array(conditions) = expr {
85                        maintains.extend(conditions.elems.into_iter().map(|expr| Condition {
86                            expr,
87                            cfg: cfg.clone(),
88                        }));
89                    } else {
90                        maintains.push(Condition { expr, cfg });
91                    }
92                }
93                SpecArg::Binds { keyword, pattern } => {
94                    if binds_pattern.is_some() {
95                        return Err(syn::Error::new(
96                            keyword.span(),
97                            "multiple `binds` parameters are not allowed",
98                        ));
99                    }
100                    binds_pattern = Some(pattern);
101                }
102                SpecArg::Ensures { cfg, expr, .. } => {
103                    if let Expr::Array(conditions) = expr {
104                        ensures_exprs.extend(conditions.elems.into_iter().map(|expr| Condition {
105                            expr,
106                            cfg: cfg.clone(),
107                        }));
108                    } else {
109                        ensures_exprs.push(Condition { expr, cfg });
110                    }
111                }
112            }
113        }
114
115        let default_closure_pattern = binds_pattern
116            .as_ref()
117            .map(|p| p.to_token_stream())
118            .unwrap_or_else(|| quote! { output });
119
120        let ensures = ensures_exprs
121            .into_iter()
122            .map(|condition| {
123                let closure: ExprClosure = if let Expr::Closure(closure) = condition.expr {
124                    closure
125                } else {
126                    // Convert "naked" postcondition to closure
127                    let inner_condition = condition.expr;
128                    parse_quote! { |#default_closure_pattern| #inner_condition }
129                };
130                Ok(ConditionClosure {
131                    closure,
132                    cfg: condition.cfg,
133                })
134            })
135            .collect::<Result<Vec<ConditionClosure>>>()?;
136
137        Ok(Spec {
138            requires,
139            maintains,
140            ensures,
141        })
142    }
143}
144
145#[derive(PartialEq, PartialOrd, Clone, Copy, Debug)]
146enum ArgOrder {
147    Requires,
148    Maintains,
149    Binds,
150    Ensures,
151}
152
153/// An intermediate enum to help parse either a condition or a `binds` setting.
154enum SpecArg {
155    Requires {
156        keyword: kw::requires,
157        cfg: Option<Meta>,
158        expr: Expr,
159    },
160    Ensures {
161        keyword: kw::ensures,
162        cfg: Option<Meta>,
163        expr: Expr,
164    },
165    Maintains {
166        keyword: kw::maintains,
167        cfg: Option<Meta>,
168        expr: Expr,
169    },
170    Binds {
171        keyword: kw::binds,
172        pattern: Pat,
173    },
174}
175
176impl SpecArg {
177    fn get_order(&self) -> ArgOrder {
178        match self {
179            SpecArg::Requires { .. } => ArgOrder::Requires,
180            SpecArg::Maintains { .. } => ArgOrder::Maintains,
181            SpecArg::Binds { .. } => ArgOrder::Binds,
182            SpecArg::Ensures { .. } => ArgOrder::Ensures,
183        }
184    }
185
186    fn get_keyword_span(&self) -> Span {
187        match self {
188            SpecArg::Requires { keyword, .. } => keyword.span,
189            SpecArg::Ensures { keyword, .. } => keyword.span,
190            SpecArg::Maintains { keyword, .. } => keyword.span,
191            SpecArg::Binds { keyword, .. } => keyword.span,
192        }
193    }
194}
195
196impl Parse for SpecArg {
197    fn parse(input: ParseStream) -> Result<Self> {
198        let attrs = input.call(Attribute::parse_outer)?;
199        let cfg = parse_cfg_attribute(&attrs)?;
200
201        let lookahead = input.lookahead1();
202        if lookahead.peek(kw::binds) {
203            if cfg.is_some() {
204                return Err(syn::Error::new(
205                    attrs[0].span(),
206                    "`cfg` attribute is not supported on `binds`",
207                ));
208            }
209
210            // Parse `binds: <pattern>`
211            let keyword = input.parse::<kw::binds>()?;
212            input.parse::<Token![:]>()?;
213            Ok(SpecArg::Binds {
214                keyword,
215                pattern: Pat::parse_single(input)?,
216            })
217        } else if lookahead.peek(kw::requires) {
218            // Parse `requires: <conditions>`
219            let keyword = input.parse::<kw::requires>()?;
220            input.parse::<Token![:]>()?;
221            Ok(SpecArg::Requires {
222                keyword,
223                cfg,
224                expr: input.parse()?,
225            })
226        } else if lookahead.peek(kw::maintains) {
227            // Parse `maintains: <conditions>`
228            let keyword = input.parse::<kw::maintains>()?;
229            input.parse::<Token![:]>()?;
230            Ok(SpecArg::Maintains {
231                keyword,
232                cfg,
233                expr: input.parse()?,
234            })
235        } else if lookahead.peek(kw::ensures) {
236            // Parse `ensures: <conditions>`
237            let keyword = input.parse::<kw::ensures>()?;
238            input.parse::<Token![:]>()?;
239            Ok(SpecArg::Ensures {
240                keyword,
241                cfg,
242                expr: input.parse()?,
243            })
244        } else {
245            Err(lookahead.error())
246        }
247    }
248}
249
250fn parse_cfg_attribute(attrs: &[Attribute]) -> Result<Option<Meta>> {
251    let mut cfg_attrs: Vec<Meta> = vec![];
252
253    for attr in attrs {
254        if attr.path().is_ident("cfg") {
255            cfg_attrs.push(attr.parse_args()?);
256        } else {
257            return Err(syn::Error::new(
258                attr.span(),
259                "unsupported attribute; only `cfg` is allowed",
260            ));
261        }
262    }
263
264    if cfg_attrs.len() > 1 {
265        return Err(syn::Error::new(
266            cfg_attrs[1].span(),
267            "multiple `cfg` attributes are not supported",
268        ));
269    }
270
271    Ok(cfg_attrs.pop())
272}
273
274/// Custom keywords for parsing. This allows us to use `requires`, `ensures`, etc.,
275/// as if they were built-in Rust keywords during parsing.
276mod kw {
277    syn::custom_keyword!(requires);
278    syn::custom_keyword!(maintains);
279    syn::custom_keyword!(binds);
280    syn::custom_keyword!(ensures);
281}
282
283/// Takes the spec and the original body and returns a new instrumented function body.
284pub fn instrument_fn_body(spec: &Spec, original_body: &Block, is_async: bool) -> Result<Block> {
285    // The identifier for the return value binding. It's hygienic to prevent collisions.
286    let binding_ident = Ident::new("__anodized_output", Span::mixed_site());
287
288    // --- Generate Precondition Checks ---
289    let preconditions = spec
290        .requires
291        .iter()
292        .map(|condition| {
293            let expr = &condition.expr;
294            let msg = format!("Precondition failed: {}", expr.to_token_stream());
295            let assert = quote! { assert!(#expr, #msg); };
296            if let Some(cfg) = &condition.cfg {
297                quote! { if cfg!(#cfg) { #assert } }
298            } else {
299                assert
300            }
301        })
302        .chain(spec.maintains.iter().map(|condition| {
303            let expr = &condition.expr;
304            let msg = format!("Pre-invariant failed: {}", expr.to_token_stream());
305            let assert = quote! { assert!(#expr, #msg); };
306            if let Some(cfg) = &condition.cfg {
307                quote! { if cfg!(#cfg) { #assert } }
308            } else {
309                assert
310            }
311        }));
312
313    // --- Generate Postcondition Checks ---
314    let postconditions = spec
315        .maintains
316        .iter()
317        .map(|condition| {
318            let expr = &condition.expr;
319            let msg = format!("Post-invariant failed: {}", expr.to_token_stream());
320            let assert = quote! { assert!(#expr, #msg); };
321            if let Some(cfg) = &condition.cfg {
322                quote! { if cfg!(#cfg) { #assert } }
323            } else {
324                assert
325            }
326        })
327        .chain(spec.ensures.iter().map(|condition_closure| {
328            let closure = &condition_closure.closure;
329            let msg = format!("Postcondition failed: {}", closure.to_token_stream());
330            let assert = quote! { assert!((#closure)(#binding_ident), #msg); };
331            if let Some(cfg) = &condition_closure.cfg {
332                quote! { if cfg!(#cfg) { #assert } }
333            } else {
334                assert
335            }
336        }));
337
338    // --- Construct the New Body ---
339    let body_expr = if is_async {
340        quote! { async #original_body.await }
341    } else {
342        quote! { #original_body }
343    };
344
345    Ok(parse_quote! {
346        {
347            #(#preconditions)*
348            let #binding_ident = #body_expr;
349            #(#postconditions)*
350            #binding_ident
351        }
352    })
353}
354
355#[cfg(test)]
356mod test_parse_spec;
357
358#[cfg(test)]
359mod test_instrument_fn;
360
361#[cfg(test)]
362mod test_util;